PageSourceSearch

https://www.wenthink.com/js/d2048e2d8a1650896795dcf0a98cf656.js?orig=%2Fpublic%2Fjavascripts%2Fgov.js

js wenthink.com collected 2026-10-05 22:12:40 UTC 84,796 bytes, 2,412 lines download raw bytes

1(function (global, factory) {
2	typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
3	typeof define === 'function' && define.amd ? define('GOVUKFrontend', ['exports'], factory) :
4	(factory((global.GOVUKFrontend = {})));
5}(this, (function (exports) { 'use strict';
6
7/**
8 * TODO: Ideally this would be a NodeList.prototype.forEach polyfill
9 * This seems to fail in IE8, requires more investigation.
10 * See: https://github.com/imagitama/nodelist-foreach-polyfill
11 */
12function nodeListForEach (nodes, callback) {
13  if (window.NodeList.prototype.forEach) {
14    return nodes.forEach(callback)
15  }
16  for (var i = 0; i < nodes.length; i++) {
17    callback.call(window, nodes[i], i, nodes);
18  }
19}
20
21// Used to generate a unique string, allows multiple instances of the component without
22// Them conflicting with each other.
23// https://stackoverflow.com/a/8809472
24function generateUniqueID () {
25  var d = new Date().getTime();
26  if (typeof window.performance !== 'undefined' && typeof window.performance.now === 'function') {
27    d += window.performance.now(); // use high-precision timer if available
28  }
29  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
30    var r = (d + Math.random() * 16) % 16 | 0;
31    d = Math.floor(d / 16);
32    return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16)
33  })
34}
35
36(function(undefined) {
37
38// Detection from https://github.com/Financial-Times/polyfill-service/blob/master/packages/polyfill-library/polyfills/Object/defineProperty/detect.js
39var detect = (
40  // In IE8, defineProperty could only act on DOM elements, so full support
41  // for the feature requires the ability to set a property on an arbitrary object
42  'defineProperty' in Object && (function() {
43  	try {
44  		var a = {};
45  		Object.defineProperty(a, 'test', {value:42});
46  		return true;
47  	} catch(e) {
48  		return false
49  	}
50  }())
51);
52
53if (detect) return
54
55// Polyfill from https://cdn.polyfill.io/v2/polyfill.js?features=Object.defineProperty&flags=always
56(function (nativeDefineProperty) {
57
58	var supportsAccessors = Object.prototype.hasOwnProperty('__defineGetter__');
59	var ERR_ACCESSORS_NOT_SUPPORTED = 'Getters & setters cannot be defined on this javascript engine';
60	var ERR_VALUE_ACCESSORS = 'A property cannot both have accessors and be writable or have a value';
61
62	Object.defineProperty = function defineProperty(object, property, descriptor) {
63
64		// Where native support exists, assume it
65		if (nativeDefineProperty && (object === window || object === document || object === Element.prototype || object instanceof Element)) {
66			return nativeDefineProperty(object, property, descriptor);
67		}
68
69		if (object === null || !(object instanceof Object || typeof object === 'object')) {
70			throw new TypeError('Object.defineProperty called on non-object');
71		}
72
73		if (!(descriptor instanceof Object)) {
74			throw new TypeError('Property description must be an object');
75		}
76
77		var propertyString = String(property);
78		var hasValueOrWritable = 'value' in descriptor || 'writable' in descriptor;
79		var getterType = 'get' in descriptor && typeof descriptor.get;
80		var setterType = 'set' in descriptor && typeof descriptor.set;
81
82		// handle descriptor.get
83		if (getterType) {
84			if (getterType !== 'function') {
85				throw new TypeError('Getter must be a function');
86			}
87			if (!supportsAccessors) {
88				throw new TypeError(ERR_ACCESSORS_NOT_SUPPORTED);
89			}
90			if (hasValueOrWritable) {
91				throw new TypeError(ERR_VALUE_ACCESSORS);
92			}
93			Object.__defineGetter__.call(object, propertyString, descriptor.get);
94		} else {
95			object[propertyString] = descriptor.value;
96		}
97
98		// handle descriptor.set
99		if (setterType) {
100			if (setterType !== 'function') {
101				throw new TypeError('Setter must be a function');
102			}
103			if (!supportsAccessors) {
104				throw new TypeError(ERR_ACCESSORS_NOT_SUPPORTED);
105			}
106			if (hasValueOrWritable) {
107				throw new TypeError(ERR_VALUE_ACCESSORS);
108			}
109			Object.__defineSetter__.call(object, propertyString, descriptor.set);
110		}
111
112		// OK to define value unconditionally - if a getter has been specified as well, an error would be thrown above
113		if ('value' in descriptor) {
114			object[propertyString] = descriptor.value;
115		}
116
117		return object;
118	};
119}(Object.defineProperty));
120})
121.call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
122
123(function(undefined) {
124  // Detection from https://github.com/Financial-Times/polyfill-service/blob/master/packages/polyfill-library/polyfills/Function/prototype/bind/detect.js
125  var detect = 'bind' in Function.prototype;
126
127  if (detect) return
128
129  // Polyfill from https://cdn.polyfill.io/v2/polyfill.js?features=Function.prototype.bind&flags=always
130  Object.defineProperty(Function.prototype, 'bind', {
131      value: function bind(that) { // .length is 1
132          // add necessary es5-shim utilities
133          var $Array = Array;
134          var $Object = Object;
135          var ObjectPrototype = $Object.prototype;
136          var ArrayPrototype = $Array.prototype;
137          var Empty = function Empty() {};
138          var to_string = ObjectPrototype.toString;
139          var hasToStringTag = typeof Symbol === 'function' && typeof Symbol.toStringTag === 'symbol';
140          var isCallable; /* inlined from https://npmjs.com/is-callable */ var fnToStr = Function.prototype.toString, tryFunctionObject = function tryFunctionObject(value) { try { fnToStr.call(value); return true; } catch (e) { return false; } }, fnClass = '[object Function]', genClass = '[object GeneratorFunction]'; isCallable = function isCallable(value) { if (typeof value !== 'function') { return false; } if (hasToStringTag) { return tryFunctionObject(value); } var strClass = to_string.call(value); return strClass === fnClass || strClass === genClass; };
141          var array_slice = ArrayPrototype.slice;
142          var array_concat = ArrayPrototype.concat;
143          var array_push = ArrayPrototype.push;
144          var max = Math.max;
145          // /add necessary es5-shim utilities
146
147          // 1. Let Target be the this value.
148          var target = this;
149          // 2. If IsCallable(Target) is false, throw a TypeError exception.
150          if (!isCallable(target)) {
151              throw new TypeError('Function.prototype.bind called on incompatible ' + target);
152          }
153          // 3. Let A be a new (possibly empty) internal list of all of the
154          //   argument values provided after thisArg (arg1, arg2 etc), in order.
155          // XXX slicedArgs will stand in for "A" if used
156          var args = array_slice.call(arguments, 1); // for normal call
157          // 4. Let F be a new native ECMAScript object.
158          // 11. Set the [[Prototype]] internal property of F to the standard
159          //   built-in Function prototype object as specified in 15.3.3.1.
160          // 12. Set the [[Call]] internal property of F as described 
160in
161          //   15.3.4.5.1.
162          // 13. Set the [[Construct]] internal property of F as described in
163          //   15.3.4.5.2.
164          // 14. Set the [[HasInstance]] internal property of F as described in
165          //   15.3.4.5.3.
166          var bound;
167          var binder = function () {
168
169              if (this instanceof bound) {
170                  // 15.3.4.5.2 [[Construct]]
171                  // When the [[Construct]] internal method of a function object,
172                  // F that was created using the bind function is called with a
173                  // list of arguments ExtraArgs, the following steps are taken:
174                  // 1. Let target be the value of F's [[TargetFunction]]
175                  //   internal property.
176                  // 2. If target has no [[Construct]] internal method, a
177                  //   TypeError exception is thrown.
178                  // 3. Let boundArgs be the value of F's [[BoundArgs]] internal
179                  //   property.
180                  // 4. Let args be a new list containing the same values as the
181                  //   list boundArgs in the same order followed by the same
182                  //   values as the list ExtraArgs in the same order.
183                  // 5. Return the result of calling the [[Construct]] internal
184                  //   method of target providing args as the arguments.
185
186                  var result = target.apply(
187                      this,
188                      array_concat.call(args, array_slice.call(arguments))
189                  );
190                  if ($Object(result) === result) {
191                      return result;
192                  }
193                  return this;
194
195              } else {
196                  // 15.3.4.5.1 [[Call]]
197                  // When the [[Call]] internal method of a function object, F,
198                  // which was created using the bind function is called with a
199                  // this value and a list of arguments ExtraArgs, the following
200                  // steps are taken:
201                  // 1. Let boundArgs be the value of F's [[BoundArgs]] internal
202                  //   property.
203                  // 2. Let boundThis be the value of F's [[BoundThis]] internal
204                  //   property.
205                  // 3. Let target be the value of F's [[TargetFunction]] internal
206                  //   property.
207                  // 4. Let args be a new list containing the same values as the
208                  //   list boundArgs in the same order followed by the same
209                  //   values as the list ExtraArgs in the same order.
210                  // 5. Return the result of calling the [[Call]] internal method
211                  //   of target providing boundThis as the this value and
212                  //   providing args as the arguments.
213
214                  // equiv: target.call(this, ...boundArgs, ...args)
215                  return target.apply(
216                      that,
217                      array_concat.call(args, array_slice.call(arguments))
218                  );
219
220              }
221
222          };
223
224          // 15. If the [[Class]] internal property of Target is "Function", then
225          //     a. Let L be the length property of Target minus the length of A.
226          //     b. Set the length own property of F to either 0 or L, whichever is
227          //       larger.
228          // 16. Else set the length own property of F to 0.
229
230          var boundLength = max(0, target.length - args.length);
231
232          // 17. Set the attributes of the length own property of F to the values
233          //   specified in 15.3.5.1.
234          var boundArgs = [];
235          for (var i = 0; i < boundLength; i++) {
236              array_push.call(boundArgs, '$' + i);
237          }
238
239          // XXX Build a dynamic function with desired amount of arguments is the only
240          // way to set the length property of a function.
241          // In environments where Content Security Policies enabled (Chrome extensions,
242          // for ex.) all use of eval or Function costructor throws an exception.
243          // However in all of these environments Function.prototype.bind exists
244          // and so this code will never be executed.
245          bound = Function('binder', 'return function (' + boundArgs.join(',') + '){ return binder.apply(this, arguments); }')(binder);
246
247          if (target.prototype) {
248              Empty.prototype = target.prototype;
249              bound.prototype = new Empty();
250              // Clean up dangling references.
251              Empty.prototype = null;
252          }
253
254          // TODO
255          // 18. Set the [[Extensible]] internal property of F to true.
256
257          // TODO
258          // 19. Let thrower be the [[ThrowTypeError]] function Object (13.2.3).
259          // 20. Call the [[DefineOwnProperty]] internal method of F with
260          //   arguments "caller", PropertyDescriptor {[[Get]]: thrower, [[Set]]:
261          //   thrower, [[Enumerable]]: false, [[Configurable]]: false}, and
262          //   false.
263          // 21. Call the [[DefineOwnProperty]] internal method of F with
264          //   arguments "arguments", PropertyDescriptor {[[Get]]: thrower,
265          //   [[Set]]: thrower, [[Enumerable]]: false, [[Configurable]]: false},
266          //   and false.
267
268          // TODO
269          // NOTE Function objects created using Function.prototype.bind do not
270          // have a prototype property or the [[Code]], [[FormalParameters]], and
271          // [[Scope]] internal properties.
272          // XXX can't delete prototype in pure-js.
273
274          // 22. Return F.
275          return bound;
276      }
277  });
278})
279.call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
280
281(function(undefined) {
282
283    // Detection from https://raw.githubusercontent.com/Financial-Times/polyfill-service/master/packages/polyfill-library/polyfills/DOMTokenList/detect.js
284    var detect = (
285      'DOMTokenList' in this && (function (x) {
286        return 'classList' in x ? !x.classList.toggle('x', false) && !x.className : true;
287      })(document.createElement('x'))
288    );
289
290    if (detect) return
291
292    // Polyfill from https://raw.githubusercontent.com/Financial-Times/polyfill-service/master/packages/polyfill-library/polyfills/DOMTokenList/polyfill.js
293    (function (global) {
294      var nativeImpl = "DOMTokenList" in global && global.DOMTokenList;
295
296      if (
297          !nativeImpl ||
298          (
299            !!document.createElementNS &&
300            !!document.createElementNS('http://www.w3.org/2000/svg', 'svg') &&
301            !(document.createElementNS("http://www.w3.org/2000/svg", "svg").classList instanceof DOMTokenList)
302          )
303        ) {
304        global.DOMTokenList = (function() { // eslint-disable-line no-unused-vars
305          var dpSupport = true;
306          var defineGetter = function (object, name, fn, configurable) {
307            if (Object.defineProperty)
308              Object.defineProperty(object, name, {
309                configurable: false === dpSupport ? true : !!configurable,
310                get: fn
311              });
312
313            else object.__defineGetter__(name, fn);
314          };
315
316          /** Ensure the browser allows Object.defineProperty to be used on native JavaScript objects. */
317          try {
318            defineGetter({}, "support");
319          }
320          catch (e) {
321            dpSupport = false;
322          }
323
324
325          var _DOMTokenList = function (el, prop) {
326            var that = this;
327            var tokens = [];
328            var tokenMap = {};
329            var length = 0;
330            var maxLength = 0;
331            var addIndexGetter = function (i) {
332              defineGetter(that, i, function () {
333                preop();
334                return tokens[i];
335              }, false);
336
337            };
338            var reindex = function () {
339
340              /** Define getter functions for array-like access to the tokenList's contents. */
341              if (length >= maxLength)
342                for (; maxLength < length; ++maxLength) {
343                  addIndexGetter(maxLength);
344                }
345            };
346
347            /** Helper function called at the start of each class method. Internal use only. */
348            var preop = function () {
349              var error;
350              var i;
351              var args = arguments;
352              var rSpace = /\s+/;
353
354              /** Validate the token/s passed to an instance method, if any. */
355              if (args.length)
356                for (i = 0; i < args.length; ++i)
357                  if (rSpace.test(args[i])) {
358                    error = new SyntaxError('String "' + args[i] + '" ' + "contains" + ' an invali
358d character');
359                    error.code = 5;
360                    error.name = "InvalidCharacterError";
361                    throw error;
362                  }
363
364
365              /** Split the new value apart by whitespace*/
366              if (typeof el[prop] === "object") {
367                tokens = ("" + el[prop].baseVal).replace(/^\s+|\s+$/g, "").split(rSpace);
368              } else {
369                tokens = ("" + el[prop]).replace(/^\s+|\s+$/g, "").split(rSpace);
370              }
371
372              /** Avoid treating blank strings as single-item token lists */
373              if ("" === tokens[0]) tokens = [];
374
375              /** Repopulate the internal token lists */
376              tokenMap = {};
377              for (i = 0; i < tokens.length; ++i)
378                tokenMap[tokens[i]] = true;
379              length = tokens.length;
380              reindex();
381            };
382
383            /** Populate our internal token list if the targeted attribute of the subject element isn't empty. */
384            preop();
385
386            /** Return the number of tokens in the underlying string. Read-only. */
387            defineGetter(that, "length", function () {
388              preop();
389              return length;
390            });
391
392            /** Override the default toString/toLocaleString methods to return a space-delimited list of tokens when typecast. */
393            that.toLocaleString =
394              that.toString = function () {
395                preop();
396                return tokens.join(" ");
397              };
398
399            that.item = function (idx) {
400              preop();
401              return tokens[idx];
402            };
403
404            that.contains = function (token) {
405              preop();
406              return !!tokenMap[token];
407            };
408
409            that.add = function () {
410              preop.apply(that, args = arguments);
411
412              for (var args, token, i = 0, l = args.length; i < l; ++i) {
413                token = args[i];
414                if (!tokenMap[token]) {
415                  tokens.push(token);
416                  tokenMap[token] = true;
417                }
418              }
419
420              /** Update the targeted attribute of the attached element if the token list's changed. */
421              if (length !== tokens.length) {
422                length = tokens.length >>> 0;
423                if (typeof el[prop] === "object") {
424                  el[prop].baseVal = tokens.join(" ");
425                } else {
426                  el[prop] = tokens.join(" ");
427                }
428                reindex();
429              }
430            };
431
432            that.remove = function () {
433              preop.apply(that, args = arguments);
434
435              /** Build a hash of token names to compare against when recollecting our token list. */
436              for (var args, ignore = {}, i = 0, t = []; i < args.length; ++i) {
437                ignore[args[i]] = true;
438                delete tokenMap[args[i]];
439              }
440
441              /** Run through our tokens list and reassign only those that aren't defined in the hash declared above. */
442              for (i = 0; i < tokens.length; ++i)
443                if (!ignore[tokens[i]]) t.push(tokens[i]);
444
445              tokens = t;
446              length = t.length >>> 0;
447
448              /** Update the targeted attribute of the attached element. */
449              if (typeof el[prop] === "object") {
450                el[prop].baseVal = tokens.join(" ");
451              } else {
452                el[prop] = tokens.join(" ");
453              }
454              reindex();
455            };
456
457            that.toggle = function (token, force) {
458              preop.apply(that, [token]);
459
460              /** Token state's being forced. */
461              if (undefined !== force) {
462                if (force) {
463                  that.add(token);
464                  return true;
465                } else {
466                  that.remove(token);
467                  return false;
468                }
469              }
470
471              /** Token already exists in tokenList. Remove it, and return FALSE. */
472              if (tokenMap[token]) {
473                that.remove(token);
474                return false;
475              }
476
477              /** Otherwise, add the token and return TRUE. */
478              that.add(token);
479              return true;
480            };
481
482            return that;
483          };
484
485          return _DOMTokenList;
486        }());
487      }
488
489      // Add second argument to native DOMTokenList.toggle() if necessary
490      (function () {
491        var e = document.createElement('span');
492        if (!('classList' in e)) return;
493        e.classList.toggle('x', false);
494        if (!e.classList.contains('x')) return;
495        e.classList.constructor.prototype.toggle = function toggle(token /*, force*/) {
496          var force = arguments[1];
497          if (force === undefined) {
498            var add = !this.contains(token);
499            this[add ? 'add' : 'remove'](token);
500            return add;
501          }
502          force = !!force;
503          this[force ? 'add' : 'remove'](token);
504          return force;
505        };
506      }());
507
508      // Add multiple arguments to native DOMTokenList.add() if necessary
509      (function () {
510        var e = document.createElement('span');
511        if (!('classList' in e)) return;
512        e.classList.add('a', 'b');
513        if (e.classList.contains('b')) return;
514        var native = e.classList.constructor.prototype.add;
515        e.classList.constructor.prototype.add = function () {
516          var args = arguments;
517          var l = arguments.length;
518          for (var i = 0; i < l; i++) {
519            native.call(this, args[i]);
520          }
521        };
522      }());
523
524      // Add multiple arguments to native DOMTokenList.remove() if necessary
525      (function () {
526        var e = document.createElement('span');
527        if (!('classList' in e)) return;
528        e.classList.add('a');
529        e.classList.add('b');
530        e.classList.remove('a', 'b');
531        if (!e.classList.contains('b')) return;
532        var native = e.classList.constructor.prototype.remove;
533        e.classList.constructor.prototype.remove = function () {
534          var args = arguments;
535          var l = arguments.length;
536          for (var i = 0; i < l; i++) {
537            native.call(this, args[i]);
538          }
539        };
540      }());
541
542    }(this));
543
544}).call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
545
546(function(undefined) {
547
548// Detection from https://github.com/Financial-Times/polyfill-service/blob/master/packages/polyfill-library/polyfills/Document/detect.js
549var detect = ("Document" in this);
550
551if (detect) return
552
553// Polyfill from https://cdn.polyfill.io/v2/polyfill.js?features=Document&flags=always
554if ((typeof WorkerGlobalScope === "undefined") && (typeof importScripts !== "function")) {
555
556	if (this.HTMLDocument) { // IE8
557
558		// HTMLDocument is an extension of Document.  If the browser has HTMLDocument but not Document, the former will suffice as an alias for the latter.
559		this.Document = this.HTMLDocument;
560
561	} else {
562
563		// Create an empty function to act as the missing constructor for the document object, attach the document object as its prototype.  The function needs to be anonymous else it is hoisted and causes the feature detect to prematurely pass, preventing the assignments below being made.
564		this.Document = this.HTMLDocument = document.constructor = (new Function('return function Document() {}')());
565		this.Document.prototype = document;
566	}
567}
568
569
570})
571.call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
572
573(function(undefined) {
574
575// Detection from https://github.com/Financial-Times/polyfill-service/blob/master/packages/polyfill-library/polyfills/Element/detect.js
576var detect = ('Element' in this && 'HTMLElement' in this);
577
578if (detect) return
579
580// Polyfill from https://cdn.polyfill.io/v2/polyfill.js?features=Element&flags=always
581(function () {
582
583	// IE8
584	if (window.Element && !window.HTMLElement) {
585		window.HTMLElement = window.Element;
586		return;
587	}
588
589	// create Element constructor
590	window.Element = window.HTMLElement = new Function('return function Element() {}')();
591
592	// generate sandboxed iframe
593	var vbody = document.appendChild(document.createElement('body'));
594	var frame = vbody.appendChild(document.createElement('iframe'));
595
596	// use sandboxed iframe to replicate Element functionality
597	var frameDocument = frame.contentWindow.document;
598	var prototype = Element.prototype = frameDocument.appendChild(frameDocument.createElement('*'));
599	var cache = {};
600
601	// polyfill Element.prototype on an element
602	var shiv = function (element, deep) {
603		var
604		childNodes = element.childNodes || [],
605		index = -1,
606		key, value, childNode;
607
608		if (element.nodeType === 1 && element.constructor !== Element) {
609			element.constructor = Element;
610
611			for (key in cache) {
612				value = cache[key];
613				element[key] = value;
614			}
615		}
616
617		while (childNode = deep && childNodes[++index]) {
618			shiv(childNode, deep);
619		}
620
621		return element;
622	};
623
624	var elements = document.getElementsByTagName('*');
625	var nativeCreateElement = document.createElement;
626	var interval;
627	var loopLimit = 100;
628
629	prototype.attachEvent('onpropertychange', function (event) {
630		var
631		propertyName = event.propertyName,
632		nonValue = !cache.hasOwnProperty(propertyName),
633		newValue = prototype[propertyName],
634		oldValue = cache[propertyName],
635		index = -1,
636		element;
637
638		while (element = elements[++index]) {
639			if (element.nodeType === 1) {
640				if (nonValue || element[propertyName] === oldValue) {
641					element[propertyName] = newValue;
642				}
643			}
644		}
645
646		cache[propertyName] = newValue;
647	});
648
649	prototype.constructor = Element;
650
651	if (!prototype.hasAttribute) {
652		// <Element>.hasAttribute
653		prototype.hasAttribute = function hasAttribute(name) {
654			return this.getAttribute(name) !== null;
655		};
656	}
657
658	// Apply Element prototype to the pre-existing DOM as soon as the body element appears.
659	function bodyCheck() {
660		if (!(loopLimit--)) clearTimeout(interval);
661		if (document.body && !document.body.prototype && /(complete|interactive)/.test(document.readyState)) {
662			shiv(document, true);
663			if (interval && document.body.prototype) clearTimeout(interval);
664			return (!!document.body.prototype);
665		}
666		return false;
667	}
668	if (!bodyCheck()) {
669		document.onreadystatechange = bodyCheck;
670		interval = setInterval(bodyCheck, 25);
671	}
672
673	// Apply to any new elements created after load
674	document.createElement = function createElement(nodeName) {
675		var element = nativeCreateElement(String(nodeName).toLowerCase());
676		return shiv(element);
677	};
678
679	// remove sandboxed iframe
680	document.removeChild(vbody);
681}());
682
683})
684.call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
685
686(function(undefined) {
687
688    // Detection from https://raw.githubusercontent.com/Financial-Times/polyfill-service/8717
688a9e04ac7aff99b4980fbedead98036b0929a/packages/polyfill-library/polyfills/Element/prototype/classList/detect.js
689    var detect = (
690      'document' in this && "classList" in document.documentElement && 'Element' in this && 'classList' in Element.prototype && (function () {
691        var e = document.createElement('span');
692        e.classList.add('a', 'b');
693        return e.classList.contains('b');
694      }())
695    );
696
697    if (detect) return
698
699    // Polyfill from https://cdn.polyfill.io/v2/polyfill.js?features=Element.prototype.classList&flags=always
700    (function (global) {
701      var dpSupport = true;
702      var defineGetter = function (object, name, fn, configurable) {
703        if (Object.defineProperty)
704          Object.defineProperty(object, name, {
705            configurable: false === dpSupport ? true : !!configurable,
706            get: fn
707          });
708
709        else object.__defineGetter__(name, fn);
710      };
711      /** Ensure the browser allows Object.defineProperty to be used on native JavaScript objects. */
712      try {
713        defineGetter({}, "support");
714      }
715      catch (e) {
716        dpSupport = false;
717      }
718      /** Polyfills a property with a DOMTokenList */
719      var addProp = function (o, name, attr) {
720
721        defineGetter(o.prototype, name, function () {
722          var tokenList;
723
724          var THIS = this,
725
726          /** Prevent this from firing twice for some reason. What the hell, IE. */
727          gibberishProperty = "__defineGetter__" + "DEFINE_PROPERTY" + name;
728          if(THIS[gibberishProperty]) return tokenList;
729          THIS[gibberishProperty] = true;
730
731          /**
732           * IE8 can't define properties on native JavaScript objects, so we'll use a dumb hack instead.
733           *
734           * What this is doing is creating a dummy element ("reflection") inside a detached phantom node ("mirror")
735           * that serves as the target of Object.defineProperty instead. While we could simply use the subject HTML
736           * element instead, this would conflict with element types which use indexed properties (such as forms and
737           * select lists).
738           */
739          if (false === dpSupport) {
740
741            var visage;
742            var mirror = addProp.mirror || document.createElement("div");
743            var reflections = mirror.childNodes;
744            var l = reflections.length;
745
746            for (var i = 0; i < l; ++i)
747              if (reflections[i]._R === THIS) {
748                visage = reflections[i];
749                break;
750              }
751
752            /** Couldn't find an element's reflection inside the mirror. Materialise one. */
753            visage || (visage = mirror.appendChild(document.createElement("div")));
754
755            tokenList = DOMTokenList.call(visage, THIS, attr);
756          } else tokenList = new DOMTokenList(THIS, attr);
757
758          defineGetter(THIS, name, function () {
759            return tokenList;
760          });
761          delete THIS[gibberishProperty];
762
763          return tokenList;
764        }, true);
765      };
766
767      addProp(global.Element, "classList", "className");
768      addProp(global.HTMLElement, "classList", "className");
769      addProp(global.HTMLLinkElement, "relList", "rel");
770      addProp(global.HTMLAnchorElement, "relList", "rel");
771      addProp(global.HTMLAreaElement, "relList", "rel");
772    }(this));
773
774}).call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
775
776function Accordion ($module) {
777  this.$module = $module;
778  this.moduleId = $module.getAttribute('id');
779  this.$sections = $module.querySelectorAll('.govuk-accordion__section');
780  this.$openAllButton = '';
781  this.browserSupportsSessionStorage = helper.checkForSessionStorage();
782
783  this.controlsClass = 'govuk-accordion__controls';
784  this.openAllClass = 'govuk-accordion__open-all';
785  this.iconClass = 'govuk-accordion__icon';
786
787  this.sectionHeaderClass = 'govuk-accordion__section-header';
788  this.sectionHeaderFocusedClass = 'govuk-accordion__section-header--focused';
789  this.sectionHeadingClass = 'govuk-accordion__section-heading';
790  this.sectionSummaryClass = 'govuk-accordion__section-summary';
791  this.sectionButtonClass = 'govuk-accordion__section-button';
792  this.sectionExpandedClass = 'govuk-accordion__section--expanded';
793}
794
795// Initialize component
796Accordion.prototype.init = function () {
797  // Check for module
798  if (!this.$module) {
799    return
800  }
801
802  this.initControls();
803
804  this.initSectionHeaders();
805
806  // See if "Open all" button text should be updated
807  var areAllSectionsOpen = this.checkIfAllSectionsOpen();
808  this.updateOpenAllButton(areAllSectionsOpen);
809};
810
811// Initialise controls and set attributes
812Accordion.prototype.initControls = function () {
813  // Create "Open all" button and set attributes
814  this.$openAllButton = document.createElement('button');
815  this.$openAllButton.setAttribute('type', 'button');
816  this.$openAllButton.innerHTML = 'Open all <span class="govuk-visually-hidden">sections</span>';
817  this.$openAllButton.setAttribute('class', this.openAllClass);
818  this.$openAllButton.setAttribute('aria-expanded', 'false');
819  this.$openAllButton.setAttribute('type', 'button');
820
821  // Create control wrapper and add controls to it
822  var accordionControls = document.createElement('div');
823  accordionControls.setAttribute('class', this.controlsClass);
824  accordionControls.appendChild(this.$openAllButton);
825  this.$module.insertBefore(accordionControls, this.$module.firstChild);
826
827  // Handle events for the controls
828  this.$openAllButton.addEventListener('click', this.onOpenOrCloseAllToggle.bind(this));
829};
830
831// Initialise section headers
832Accordion.prototype.initSectionHeaders = function () {
833  // Loop through section headers
834  nodeListForEach(this.$sections, function ($section, i) {
835    // Set header attributes
836    var header = $section.querySelector('.' + this.sectionHeaderClass);
837    this.initHeaderAttributes(header, i);
838
839    this.setExpanded(this.isExpanded($section), $section);
840
841    // Handle events
842    header.addEventListener('click', this.onSectionToggle.bind(this, $section));
843
844    // See if there is any state stored in sessionStorage and set the sections to
845    // open or closed.
846    this.setInitialState($section);
847  }.bind(this));
848};
849
850// Set individual header attributes
851Accordion.prototype.initHeaderAttributes = function ($headerWrapper, index) {
852  var $module = this;
853  var $span = $headerWrapper.querySelector('.' + this.sectionButtonClass);
854  var $heading = $headerWrapper.querySelector('.' + this.sectionHeadingClass);
855  var $summary = $headerWrapper.querySelector('.' + this.sectionSummaryClass);
856
857  // Copy existing span element to an actual button element, for improved accessibility.
858  var $button = document.createElement('button');
859  $button.setAttribute('type', 'button');
860  $button.setAttribute('id', this.moduleId + '-heading-' + (index + 1));
861  $button.setAttribute('aria-controls', this.moduleId + '-content-' + (index + 1));
862
863  // Copy all attributes (https://developer.mozilla.org/en-US/docs/Web/API/Element/attributes) from $span to $button
864  for (var i = 0; i < $span.attributes.length; i++) {
865    var attr = $span.attributes.item(i);
866    $button.setAttribute(attr.nodeName, attr.nodeValue);
867  }
868
869  $button.addEventListener('focusin', function (e) {
870    if (!$headerWrapper.classList.contains($module.sectionHeaderFocusedClass)) {
871      $headerWrapper.className += ' ' + $module.sectionHeaderFocusedClass;
872    }
873  });
874
875  $button.addEventListener('blur', function (e) {
876    $headerWrapper.classList.remove($module.sectionHeaderFocusedClass);
877  });
878
879  // if (typeof ($summary) !== 'undefined' && $summary !== null) {
880  //   $button.setAttribute('aria-describedby', this.moduleId + '-summary-' + (index + 1));
881  // }
882
883  // $span could contain HTML elements (see https://www.w3.org/TR/2011/WD-html5-20110525/content-models.html#phrasing-content)
884  $button.innerHTML = $span.innerHTML;
885
886  $heading.removeChild($span);
887  $heading.appendChild($button);
888
889  // Add "+/-" icon
890  var icon = document.createElement('span');
891  icon.className = this.iconClass;
892  icon.setAttribute('aria-hidden', 'true');
893
894  $heading.appendChild(icon);
895};
896
897// When section toggled, set and store state
898Accordion.prototype.onSectionToggle = function ($section) {
899  var expanded = this.isExpanded($section);
900  this.setExpanded(!expanded, $section);
901
902  // Store the state in sessionStorage when a change is triggered
903  this.storeState($section);
904};
905
906// When Open/Close All toggled, set and store state
907Accordion.prototype.onOpenOrCloseAllToggle = function () {
908  var $module = this;
909  var $sections = this.$sections;
910
911  var nowExpanded = !this.checkIfAllSectionsOpen();
912
913  nodeListForEach($sections, function ($section) {
914    $module.setExpanded(nowExpanded, $section);
915    // Store the state in sessionStorage when a change is triggered
916    $module.storeState($section);
917  });
918
919  $module.updateOpenAllButton(nowExpanded);
920};
921
922// Set section attributes when opened/closed
923Accordion.prototype.setExpanded = function (expanded, $section) {
924  var $button = $section.querySelector('.' + this.sectionButtonClass);
925  $button.setAttribute('aria-expanded', expanded);
926
927  if (expanded) {
928    $section.classList.add(this.sectionExpandedClass);
929  } else {
930    $section.classList.remove(this.sectionExpandedClass);
931  }
932
933  // See if "Open all" button text should be updated
934  var areAllSectionsOpen = this.checkIfAllSectionsOpen();
935  this.updateOpenAllButton(areAllSectionsOpen);
936};
937
938// Get state of section
939Accordion.prototype.isExpanded = function ($section) {
940  return $section.classList.contains(this.sectionExpandedClass)
941};
942
943// Check if all sections are open
944Accordion.prototype.checkIfAllSectionsOpen = function () {
945  // Get a count of all the Accordion sections
946  var sectionsCount = this.$sections.length;
947  // Get a count of all Accordion sections that are expanded
948  var expandedSectionCount = this.$module.querySelectorAll('.' + this.sectionExpandedClass).length;
949  var areAllSectionsOpen = sectionsCount === expandedSectionCount;
950
951  return areAllSectionsOpen
952};
953
954// Update "Open all" button
955Accordion.prototype.updateOpenAllButton = function (expanded) {
956  var newButtonText = expanded ? 'Close all' : 'Open all';
957  newButtonText += '<span class="govuk-visually-hidden"> sections</span>';
958  this.$openAllButton.setAttribute('aria-expanded', expanded);
959  this.$openAllButton.innerHTML = newButtonText;
960};
961
962// Check for `window.sessionStorage`, and that it actually works.
963var helper = {
964  checkForSessionStorage: function () {
965    var testString = 'this is the test string';
966    var result;
967    try {
968      window.sessionStorage.setItem(testString, testString);
969      result = window.sessionStorage.getItem(testString) === testString.toString();
970      window.sessionStorage.removeItem(testString);
971      return result
972    } catch (exception) {
973      if ((typeof console === 'undefined' || typeof console.log === 'undefined')) {
974        console.log('Notice: sessionStorage not available.');
975      }
976    }
977  }
978};
979
980// Set the state of the accordions in sessionStorage
981Accordion.prototype.storeState = function ($section) {
982  if (this.browserSupportsSessionStorage) {
983    // We need a unique way of identifying each content in the accordion. Since
984    // an `#id` should be unique and an `id` is required for `aria-` attributes
985    // `id` can be safely used.
986    var $button = $section.querySelector('.' + this.sectionButtonClass);
987
988    if ($button) {
989      var contentId = $button.getAttribute('aria-controls');
990      var contentState = $button.getAttribute('aria-expanded');
991
992      if (typeof contentId === 'undefined' && (typeof console === 'undefined' || typeof console.log === 'undefined')) {
993        console.error(new Error('No aria controls present in accordion section heading.'));
994      }
995
996      if (typeof contentState === 'undefined' && (typeof console === 'undefined' || typeof console.log === 'undefined')) {
997        console.error(new Error('No aria expanded present in accordion section heading.'));
998      }
999
1000      // Only set the state when both `contentId` and `contentState` are taken from the DOM.
1001      if (contentId && contentState) {
1002        window.sessionStorage.setItem(contentId, contentState);
1003      }
1004    }
1005  }
1006};
1007
1008// Read the state of the accordions from sessionStorage
1009Accordion.prototype.setInitialState = function ($section) {
1010  if (this.browserSupportsSessionStorage) {
1011    var $button = $section.querySelector('.' + this.sectionButtonClass);
1012
1013    if ($button) {
1014      var contentId = $button.getAttribute('aria-controls');
1015      var contentState = contentId ? window.sessionStorage.getItem(contentId) : null;
1016
1017      if (contentState !== null) {
1018        this.setExpanded(contentState === 'true', $section);
1019      }
1020    }
1021  }
1022};
1023
1024(function(undefined) {
1025
1026// Detection from https://github.com/Financial-Times/polyfill-service/blob/master/packages/polyfill-library/polyfills/Window/detect.js
1027var detect = ('Window' in this);
1028
1029if (detect) return
1030
1031// Polyfill from https://cdn.polyfill.io/v2/polyfill.js?features=Window&flags=always
1032if ((typeof WorkerGlobalScope === "undefined") && (typeof importScripts !== "function")) {
1033	(function (global) {
1034		if (global.constructor) {
1035			global.Window = global.constructor;
1036		} else {
1037			(global.Window = global.constructor = new Function('return function Window() {}')()).prototype = this;
1038		}
1039	}(this));
1040}
1041
1042})
1043.call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
1044
1045(function(undefined) {
1046
1047// Detection from https://github.com/Financial-Times/polyfill-service/blob/master/packages/polyfill-library/polyfills/Event/detect.js
1048var detect = (
1049  (function(global) {
1050
1051  	if (!('Event' in global)) return false;
1052  	if (typeof global.Event === 'function') return true;
1053
1054  	try {
1055
1056  		// In IE 9-11, the Event object exists but cannot be instantiated
1057  		new Event('click');
1058  		return true;
1059  	} catch(e) {
1060  		return false;
1061  	}
1062  }(this))
1063);
1064
1065if (detect) return
1066
1067// Polyfill from https://cdn.polyfill.io/v2/polyfill.js?features=Event&flags=always
1068(function () {
1069	var unlistenableWindowEvents = {
1070		click: 1,
1071		dblclick: 1,
1072		keyup: 1,
1073		keypress: 1,
1074		keydown: 1,
1075		mousedown: 1,
1076		mouseup: 1,
1077		mousemove: 1,
1078		mouseover: 1,
1079		mouseenter: 1,
1080		mouseleave: 1,
1081		mouseout: 1,
1082		storage: 1,
1083		storagecommit: 1,
1084		textinput: 1
1085	};
1086
1087	// This polyfill depends on availability of `document` so will not run in a worker
1088	// However, we asssume there are no browsers with worker support that lack proper
1089	// support for `Event` within the worker
1090	if (typeof document === 'undefined' || typeof window === 'undefined') return;
1091
1092	function indexOf(array, element) {
1093		var
1094		index = -1,
1095		length = array.length;
1096
1097		while (++index < length) {
1098			if (index in array && array[index] === element) {
1099				return index;
1100			}
1101		}
1102
1103		return -1;
1104	}
1105
1106	var existingProto = (window.Event && window.Event.prototype) || null;
1107	window.Event = Window.prototype.Event = function Event(type, eventInitDict) {
1108		if (!type) {
1109			throw new Error('Not enough arguments');
1110		}
1111
1112		var event;
1113		// Shortcut if browser supports createEvent
1114		if ('createEvent' in document) {
1115			event = document.createEvent('Event');
1116			var bubbles = eventInitDict && eventInitDict.bubbles !== undefined ? eventInitDict.bubbles : false;
1117			var cancelable = eventInitDict && eventInitDict.cancelable !== undefined ? eventInitDict.cancelable : false;
1118
1119			event.initEvent(type, bubbles, cancelable);
1120
1121			return event;
1122		}
1123
1124		event = document.createEventObject();
1125
1126		event.type = type;
1127		event.bubbles = eventInitDict && eventInitDict.bubbles !== undefined ? eventInitDict.bubbles : false;
1128		event.cancelable = eventInitDict && eventInitDict.cancelable !== undefined ? eventInitDict.cancelable : false;
1129
1130		return event;
1131	};
1132	if (existingProto) {
1133		Object.defineProperty(window.Event, 'prototype', {
1134			configurable: false,
1135			enumerable: false,
1136			writable: true,
1137			value: existingProto
1138		});
1139	}
1140
1141	if (!('createEvent' in document)) {
1142		window.addEventListener = Window.prototype.addEventListener = Document.prototype.addEventListener = Element.prototype.addEventListener = function addEventListener() {
1143			var
1144			element = this,
1145			type = arguments[0],
1146			listener = arguments[1];
1147
1148			if (element === window && type in unlistenableWindowEvents) {
1149				throw new Error('In IE8 the event: ' + type + ' is not available on the window object. Please see https://github.com/Financial-Times/polyfill-service/issues/317 for more information.');
1150			}
1151
1152			if (!element._events) {
1153				element._events = {};
1154			}
1155
1156			if (!element._events[type]) {
1157				element._events[type] = function (event) {
1158					var
1159					list = element._events[event.type].list,
1160					events = list.slice(),
1161					index = -1,
1162					length = events.length,
1163					eventElement;
1164
1165					event.preventDefault = function preventDefault() {
1166						if (event.cancelable !== false) {
1167							event.returnValue = false;
1168						}
1169					};
1170
1171					event.stopPropagation = function stopPropagation() {
1172						event.cancelBubble = true;
1173					};
1174
1175					event.stopImmediatePropagation = function stopImmediatePropagation() {
1176						event.cancelBubble = true;
1177						event.cancelImmediate = true;
1178					};
1179
1180					event.currentTarget = element;
1181					event.relatedTarget = event.fromElement || null;
1182					event.target = event.target || event.srcElement || element;
1183					event.timeStamp = new Date().getTime();
1184
1185					if (event.clientX) {
1186						event.pageX = event.clientX + document.documentElement.scrollLeft;
1187						event.pageY = event.clientY + document.documentElement.scrollTop;
1188					}
1189
1190					while (++index < length && !event.cancelImmediate) {
1191						if (index in events) {
1192							eventElement = events[index];
1193
1194							if (indexOf(list, eventElement) !== -1 && typeof eventElement === 'function') {
1195								eventElement.call(element, event);
1196							}
1197						}
1198					}
1199				};
1200
1201				element._events[type].list = [];
1202
1203				if (element.attachEvent) {
1204					element.attachEvent('on' + type, element._events[type]);
1205				}
1206			}
1207
1208			element._events[type].list.push(listener);
1209		};
1210
1211		window.removeEventListener = Window.prototype.removeEventListener = Document.prototype.removeEventListener = Element.prototype.removeEventListener = function removeEventListener() {
1212			var
1213			element = this,
1214			type = arguments[0],
1215			listener = arguments[1],
1216			index;
1217
1218			if (element._events && element._events[type] && element._events[type].list) {
1219				index = indexOf(element._events[type].list, listener);
1220
1221				if (index !== -1) {
1222					element._events[type].list.splice(index, 1);
1223
1224					if (!element._events[type].list.length) {
1225						if (element.detachEvent) {
1226							element.detachEvent('on' + type, element._events[type]);
1227						}
1228						delete element._events[type];
1229					}
1230				}
1231			}
1232		};
1233
1234		window.dispatchEvent = Window.prototype.dispatchEvent = Document.prototype.dispatchEvent = Element.prototype.dispatchEvent = function dispatchEvent(event) {
1235			if (!arguments.length) {
1236				throw new Error('Not enough arguments');
1237			}
1238
1239			if (!event || typeof event.type !== 'string') {
1240				throw new Error('DOM Events Exception 0');
1241			}
1242
1243			var element = this, type = event.type;
1244
1245			try {
1246				if (!event.bubbles) {
1247					event.cancelBubble = true;
1248
1249					var cancelBubbleEvent = function (event) {
1250						event.cancelBubble = true;
1251
1252						(element || window).detachEvent('on' + type, cancelBubbleEvent);
1253					};
1254
1255					this.attachEvent('on' + type, cancelBubbleEvent);
1256				}
1257
1258				this.fireEvent('on' + type, event);
1259			} catch (error) {
1260				event.target = element;
1261
1262				do {
1263					event.currentTarget = element;
1264
1265					if ('_events' in element && typeof element._events[type] === 'function') {
1266						element._events[type].call(element, event);
1267					}
1268
1269					if (typeof element['on' + type] === 'function') {
1270						element['on' + type].call(element, event);
1271					}
1272
1273					element = element.nodeType === 9 ? element.parentWindow : element.parentNode;
1274				} while (element && !event.cancelBubble);
1275			}
1276
1277			return true;
1278		};
1279
1280		// Add the DOMContentLoaded Event
1281		document.attachEvent('onreadystatechange', function() {
1282			if (document.readyState === 'complete') {
1283				document.dispatchEvent(new Event('DOMContentLoaded', {
1284					bubbles: true
1285				}));
1286			}
1287		});
1288	}
1289}());
1290
1291})
1292.call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
1293
1294var KEY_SPACE = 32;
1295var DEBOUNCE_TIMEOUT_IN_SECONDS = 1;
1296
1297function Button ($module) {
1298  this.$module = $module;
1299  this.debounceFormSubmitTimer = null;
1300}
1301
1302/**
1303* JavaScript 'shim' to trigger the click event of element(s) when the space key is pressed.
1304*
1305* Created since some Assistive Technologies (for example some Screenreaders)
1306* will tell a user to press space on a 'button', so this functionality needs to be shimmed
1307* See https://github.com/alphagov/govuk_elements/pull/272#issuecomment-233028270
1308*
1309* @param {object} event event
1310*/
1311Button.prototype.handleKeyDown = function (event) {
1312  // get the target element
1313  var target = event.target;
1314  // if the element has a role='button' and the pressed key is a space, we'll simulate a click
1315  if (target.getAttribute('role') === 'button' && event.keyCode === KEY_SPACE) {
1316    event.preventDefault();
1317    // trigger the target's click event
1318    target.click();
1319  }
1320};
1321
1322/**
1323* If the click quickly succeeds a previous click then nothing will happen.
1324* This stops people accidentally causing multiple form submissions by
1325* double clicking buttons.
1326*/
1327Button.prototype.debounce = function (event) {
1328  var target = event.target;
1329  // Check the button that is clicked on has the preventDoubleClick feature enabled
1330  if (target.getAttribute('data-prevent-double-click') !== 'true') {
1331    return
1332  }
1333
1334  // If the timer is still running then we want to prevent the click from submitting the form
1335  if (this.debounceFormSubmitTimer) {
1336    event.preventDefault();
1337    return false
1338  }
1339
1340  this.debounceFormSubmitTimer = setTimeout(function () {
1341    this.debounceFormSubmitTimer = null;
1342  }.bind(this), DEBOUNCE_TIMEOUT_IN_SECONDS * 1000);
1343};
1344
1345/**
1346* Initialise an event listener for keydown at document level
1347* this will help listening for later inserted elements with a role="button"
1348*/
1349Button.prototype.init = function () {
1350  this.$module.addEventListener('keydown', this.handleKeyDown);
1351  this.$module.addEventListener('click', this.debounce);
1352};
1353
1354/**
1355 * JavaScript 'polyfill' for HTML5's <details> and <summary> elements
1356 * and 'shim' to add accessiblity enhancements for all browsers
1357 *
1358 * http://caniuse.com/#feat=details
1359 */
1360
1361var KEY_ENTER = 13;
1362var KEY_SPACE$1 = 32;
1363
1364// Create a flag to know if the browser supports navtive details
1365var NATIVE_DETAILS = typeof document.createElement('details').open === 'boolean';
1366
1367function Details ($module) {
1368  this.$module = $module;
1369}
1370
1371/**
1372* Handle cross-modal click events
1373* @param {object} node element
1374* @param {function} callback function
1375*/
1376Details.prototype.handleInputs = function (node, callback) {
1377  node.addEventListener('keypress', function (event) {
1378    var target = event.target;
1379    // When the key gets pressed - check if it is enter or space
1380    if (event.keyCode === KEY_ENTER || event.keyCode === KEY_SPACE$1) {
1381      if (target.nodeName.toLowerCase() === 'summary') {
1382        // Prevent space from scrolling the page
1383        // and enter from submitting a form
1384        event.preventDefault();
1385        // Click to let the click event do all the necessary action
1386        if (target.click) {
1387          target.click();
1388        } else {
1389          // except Safari 5.1 and under don't support .click() here
1390          callback(event);
1391        }
1392      }
1393    }
1394  });
1395
1396  // Prevent keyup to prevent clicking twice in Firefox when using space key
1397  node.addEventListener('keyup', function (event) {
1398    var target = event.target;
1399    if (event.keyCode === KEY_SPACE$1) {
1400      if (target.nodeName.toLowerCase() === 'summary') {
1401        event.preventDefault();
1402      }
1403    }
1404  });
1405
1406  node.addEventListener('click', callback);
1407};
1408
1409Details.prototype.init = function () {
1410  var $module = this.$module;
1411
1412  if (!$module) {
1413    return
1414  }
1415
1416  // Save shortcuts to the inner summary and content elements
1417  var $summary = this.$summary = $module.getElementsByTagName('summary').item(0);
1418  var $content = this.$content = $module.getElementsByTagName('div').item(0);
1419
1420  // If <details> doesn't have a <summary> and a <div> representing the content
1421  // it means the required HTML structure is not met so the script will stop
1422  if (!$summary || !$content) {
1423    return
1424  }
1425
1426  // If the content doesn't have an ID, assign it one now
1427  // which we'll need for the summary's aria-controls assignment
1428  if (!$content.id) {
1429    $content.id = 'details-content-' + generateUniqueID();
1430  }
1431
1432  // Add ARIA role="group" to details
1433  $module.setAttribute('role', 'group');
1434
1435  // Add role=button to summary
1436  // $summary.setAttribute('role', 'button');
1437  // Removed for WCAG 2.2
1438
1439  // Add aria-controls
1440  $summary.setAttribute('aria-controls', $content.id);
1441
1442  // Set tabIndex so the summary is keyboard accessible for non-native elements
1443  //
1444  // We have to use the camelcase `tabIndex` property as there is a bug in IE6/IE7 when we set the correct attribute lowercase:
1445  // See http://web.archive.org/web/20170120194036/http://www.salience
1445s.com/browserBugs/tabIndex.html for more information.
1446  if (!NATIVE_DETAILS) {
1447    $summary.tabIndex = 0;
1448  }
1449
1450  // Detect initial open state
1451  var openAttr = $module.getAttribute('open') !== null;
1452  if (openAttr === true) {
1453    $summary.setAttribute('aria-expanded', 'true');
1454    $content.setAttribute('aria-hidden', 'false');
1455  } else {
1456    $summary.setAttribute('aria-expanded', 'false');
1457    $content.setAttribute('aria-hidden', 'true');
1458    if (!NATIVE_DETAILS) {
1459      $content.style.display = 'none';
1460    }
1461  }
1462
1463  // Bind an event to handle summary elements
1464  this.handleInputs($summary, this.setAttributes.bind(this));
1465};
1466
1467/**
1468* Define a statechange function that updates aria-expanded and style.display
1469* @param {object} summary element
1470*/
1471Details.prototype.setAttributes = function () {
1472  var $module = this.$module;
1473  var $summary = this.$summary;
1474  var $content = this.$content;
1475
1476  var expanded = $summary.getAttribute('aria-expanded') === 'true';
1477  var hidden = $content.getAttribute('aria-hidden') === 'true';
1478
1479  $summary.setAttribute('aria-expanded', (expanded ? 'false' : 'true'));
1480  $content.setAttribute('aria-hidden', (hidden ? 'false' : 'true'));
1481
1482  if (!NATIVE_DETAILS) {
1483    $content.style.display = (expanded ? 'none' : '');
1484
1485    var hasOpenAttr = $module.getAttribute('open') !== null;
1486    if (!hasOpenAttr) {
1487      $module.setAttribute('open', 'open');
1488    } else {
1489      $module.removeAttribute('open');
1490    }
1491  }
1492  return true
1493};
1494
1495/**
1496* Remove the click event from the node element
1497* @param {object} node element
1498*/
1499Details.prototype.destroy = function (node) {
1500  node.removeEventListener('keypress');
1501  node.removeEventListener('keyup');
1502  node.removeEventListener('click');
1503};
1504
1505function CharacterCount ($module) {
1506  this.$module = $module;
1507  this.$textarea = $module.querySelector('.govuk-js-character-count');
1508}
1509
1510CharacterCount.prototype.defaults = {
1511  characterCountAttribute: 'data-maxlength',
1512  wordCountAttribute: 'data-maxwords'
1513};
1514
1515// Initialize component
1516CharacterCount.prototype.init = function () {
1517  // Check for module
1518  var $module = this.$module;
1519  var $textarea = this.$textarea;
1520  if (!$textarea) {
1521    return
1522  }
1523
1524  // Read options set using dataset ('data-' values)
1525  this.options = this.getDataset($module);
1526
1527  // Determine the limit attribute (characters or words)
1528  var countAttribute = this.defaults.characterCountAttribute;
1529  if (this.options.maxwords) {
1530    countAttribute = this.defaults.wordCountAttribute;
1531  }
1532
1533  // Save the element limit
1534  this.maxLength = $module.getAttribute(countAttribute);
1535
1536  // Check for limit
1537  if (!this.maxLength) {
1538    return
1539  }
1540
1541  // Generate and reference message
1542  var boundCreateCountMessage = this.createCountMessage.bind(this);
1543  this.countMessage = boundCreateCountMessage();
1544
1545  // If there's a maximum length defined and the count message exists
1546  if (this.countMessage) {
1547    // Remove hard limit if set
1548    $module.removeAttribute('maxlength');
1549
1550    // Bind event changes to the textarea
1551    var boundChangeEvents = this.bindChangeEvents.bind(this);
1552    boundChangeEvents();
1553
1554    // Update count message
1555    var boundUpdateCountMessage = this.updateCountMessage.bind(this);
1556    boundUpdateCountMessage();
1557  }
1558};
1559
1560// Read data attributes
1561CharacterCount.prototype.getDataset = function (element) {
1562  var dataset = {};
1563  var attributes = element.attributes;
1564  if (attributes) {
1565    for (var i = 0; i < attributes.length; i++) {
1566      var attribute = attributes[i];
1567      var match = attribute.name.match(/^data-(.+)/);
1568      if (match) {
1569        dataset[match[1]] = attribute.value;
1570      }
1571    }
1572  }
1573  return dataset
1574};
1575
1576// Counts characters or words in text
1577CharacterCount.prototype.count = function (text) {
1578  var length;
1579  if (this.options.maxwords) {
1580    var tokens = text.match(/\S+/g) || []; // Matches consecutive non-whitespace chars
1581    length = tokens.length;
1582  } else {
1583    length = text.length;
1584  }
1585  return length
1586};
1587
1588// Generate count message and bind it to the input
1589// returns reference to the generated element
1590CharacterCount.prototype.createCountMessage = function () {
1591  var countElement = this.$textarea;
1592  var elementId = countElement.id;
1593  // Check for existing info count message
1594  var countMessage = document.getElementById(elementId + '-info');
1595  // If there is no existing info count message we add one right after the field
1596  if (elementId && !countMessage) {
1597    countElement.insertAdjacentHTML('afterend', '<span id="' + elementId + '-info" class="govuk-hint govuk-character-count__message" aria-live="polite"></span>');
1598    this.describedBy = countElement.getAttribute('aria-describedby');
1599    this.describedByInfo = this.describedBy + ' ' + elementId + '-info';
1600    countElement.setAttribute('aria-describedby', this.describedByInfo);
1601    countMessage = document.getElementById(elementId + '-info');
1602  } else {
1603  // If there is an existing info count message we move it right after the field
1604    countElement.insertAdjacentElement('afterend', countMessage);
1605  }
1606  return countMessage
1607};
1608
1609// Bind input propertychange to the elements and update based on the change
1610CharacterCount.prototype.bindChangeEvents = function () {
1611  var $textarea = this.$textarea;
1612  $textarea.addEventListener('keyup', this.checkIfValueChanged.bind(this));
1613
1614  // Bind focus/blur events to start/stop polling
1615  $textarea.addEventListener('focus', this.handleFocus.bind(this));
1616  $textarea.addEventListener('blur', this.handleBlur.bind(this));
1617};
1618
1619// Speech recognition software such as Dragon NaturallySpeaking will modify the
1620// fields by directly changing its `value`. These changes don't trigger events
1621// in JavaScript, so we need to poll to handle when and if they occur.
1622CharacterCount.prototype.checkIfValueChanged = function () {
1623  if (!this.$textarea.oldValue) this.$textarea.oldValue = '';
1624  if (this.$textarea.value !== this.$textarea.oldValue) {
1625    this.$textarea.oldValue = this.$textarea.value;
1626    var boundUpdateCountMessage = this.updateCountMessage.bind(this);
1627    boundUpdateCountMessage();
1628  }
1629};
1630
1631// Update message box
1632CharacterCount.prototype.updateCountMessage = function () {
1633  var countElement = this.$textarea;
1634  var options = this.options;
1635  var countMessage = this.countMessage;
1636
1637  // Determine the remaining number of characters/words
1638  var currentLength = this.count(countElement.value);
1639  var maxLength = this.maxLength;
1640  var remainingNumber = maxLength - currentLength;
1641
1642  // Set threshold if presented in options
1643  var thresholdPercent = options.threshold ? options.threshold : 0;
1644  var thresholdValue = maxLength * thresholdPercent / 100;
1645  if (thresholdValue > currentLength) {
1646    countMessage.classList.add('govuk-character-count__message--disabled');
1647    // Ensure threshold is hidden for users of assistive technologies
1648    countMessage.setAttribute('aria-hidden', true);
1649  } else {
1650    countMessage.classList.remove('govuk-character-count__message--disabled');
1651    // Ensure threshold is visible for users of assistive technologies
1652    countMessage.removeAttribute('aria-hidden');
1653  }
1654
1655  // Update styles
1656  if (remainingNumber < 0) {
1657    countElement.classList.add('govuk-textarea--error');
1658    countMessage.classList.remove('govuk-hint');
1659    countMessage.classList.add('govuk-error-message');
1660  } else {
1661    countElement.classList.remove('govuk-textarea--error');
1662    countMessage.classList.remove('govuk-error-message');
1663    countMessage.classList.add('govuk-hint');
1664  }
1665
1666  // Update message
1667  var charVerb = 'remaining';
1668  var charNoun = 'character';
1669  var displayNumber = remainingNumber;
1670  if (options.maxwords) {
1671    charNoun = 'word';
1672  }
1673  charNoun = charNoun + ((remainingNumber === -1 || remainingNumber === 1) ? '' : 's');
1674
1675  charVerb = (remainingNumber < 0) ? 'too many' : 'remaining';
1676  displayNumber = Math.abs(remainingNumber);
1677
1678  countMessage.innerHTML = 'You have ' + displayNumber + ' ' + charNoun + ' ' + charVerb;
1679};
1680
1681CharacterCount.prototype.handleFocus = function () {
1682  // Check if value changed on focus
1683  this.valueChecker = setInterval(this.checkIfValueChanged.bind(this), 1000);
1684};
1685
1686CharacterCount.prototype.handleBlur = function () {
1687  // Cancel value checking on blur
1688  clearInterval(this.valueChecker);
1689};
1690
1691function Checkboxes ($module) {
1692  this.$module = $module;
1693  this.$inputs = $module.querySelectorAll('input[type="checkbox"]');
1694}
1695
1696Checkboxes.prototype.init = function () {
1697  var $module = this.$module;
1698  var $inputs = this.$inputs;
1699
1700  /**
1701  * Loop over all items with [data-controls]
1702  * Check if they have a matching conditional reveal
1703  * If they do, assign attributes.
1704  **/
1705  nodeListForEach($inputs, function ($input) {
1706    var controls = $input.getAttribute('data-aria-controls');
1707
1708    // Check if input controls anything
1709    // Check if content exists, before setting attributes.
1710    if (!controls || !$module.querySelector('#' + controls)) {
1711      return
1712    }
1713
1714    // If we have content that is controlled, set attributes.
1715    $input.setAttribute('aria-controls', controls);
1716    $input.removeAttribute('data-aria-controls');
1717    this.setAttributes($input);
1718  }.bind(this));
1719
1720  // Handle events
1721  $module.addEventListener('click', this.handleClick.bind(this));
1722};
1723
1724Checkboxes.prototype.setAttributes = function ($input) {
1725  var inputIsChecked = $input.checked;
1726  $input.setAttribute('aria-expanded', inputIsChecked);
1727
1728  var $content = this.$module.querySelector('#' + $input.getAttribute('aria-controls'));
1729  if ($content) {
1730    $content.classList.toggle('govuk-checkboxes__conditional--hidden', !inputIsChecked);
1731  }
1732};
1733
1734Checkboxes.prototype.handleClick = function (event) {
1735  var $target = event.target;
1736
1737  // If a checkbox with aria-controls, handle click
1738  var isCheckbox = $target.getAttribute('type') === 'checkbox';
1739  var hasAriaControls = $target.getAttribute('aria-controls');
1740  if (isCheckbox && hasAriaControls) {
1741    this.setAttributes($target);
1742  }
1743};
1744
1745(function(undefined) {
1746
1747  // Detection from https://raw.githubusercontent.com/Financial-Times/polyfill-service/1f3c09b402f65bf6e393f933a15ba63f1b86ef1f/packages/polyfill-library/polyfills/Element/prototype/matches/detect.js
1748  var detect = (
1749    'document' in this && "matches" in document.documentElement
1750  );
1751
1752  if (detect) return
1753
1754  // Polyfill from https://raw.githubusercontent.com/Financial-Times/polyfill-service/1f3c09b402f65bf6e393f933a15ba63f1b86ef1f/packages/polyfill-library/polyfills/Element/prototype/matches/polyfill.js
1755  Element.prototype.matches = Element.prototype.webkitMatchesSelector || Element.prototype.oMatchesSelector || Element.prototype.msMatchesSelector || Element.prototype.mozMatchesSelector || function matches(selector) {
1756    var element = this;
1757    var elements = (element.document || element.ownerDocument).querySelectorAll(selector);
1758    var index = 0;
1759
1760    while (elements[index] && elements[index] !== element) {
1761      ++index;
1762    }
1763
1764    return !!elements[index];
1765  };
1766
1767}).call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
1768
1769(function(undefined) {
1770
1771  // Detection from https://raw.githubusercontent.com/Financial-Times/polyfill-service/1f3c09b402f65bf6e393f933a15ba63f1b86ef1f/packages/polyfill-library/polyfills/Element/prototype/closest/detect.js
1772  var detect = (
1773    'document' in this && "closest" in document.documentElement
1774  );
1775
1776  if (detect) return
1777
1778    // Polyfill from https://raw.githubusercontent.com/Financial-Times/polyfill-service/1f3c09b402f65bf6e393f933a15ba63f1b86ef1f/packages/polyfill-library/polyfills/Element/prototype/closest/polyfill.js
1779  Element.prototype.closest = function closest(selector) {
1780    var node = this;
1781
1782    while (node) {
1783      if (node.matches(selector)) return node;
1784      else node = 'SVGElement' in window && node instanceof SVGElement ? node.parentNode : node.parentElement;
1785    }
1786
1787    return null;
1788  };
1789
1790}).call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
1791
1792function ErrorSummary ($module) {
1793  this.$module = $module;
1794}
1795
1796ErrorSummary.prototype.init = function () {
1797  var $module = this.$module;
1798  if (!$module) {
1799    return
1800  }
1801  $module.focus();
1802
1803  $module.addEventListener('click', this.handleClick.bind(this));
1804};
1805
1806/**
1807* Click event handler
1808*
1809* @param {MouseEvent} event - Click event
1810*/
1811ErrorSummary.prototype.handleClick = function (event) {
1812  var target = event.target;
1813  if (this.focusTarget(target)) {
1814    event.preventDefault();
1815  }
1816};
1817
1818/**
1819 * Focus the target element
1820 *
1821 * By default, the browser will scroll the target into view. Because our labels
1822 * or legends appear above the input, this means the user will be presented with
1823 * an input without any context, as the label or legend will be off the top of
1824 * the screen.
1825 *
1826 * Manually handling the click event, scrolling the question into view and then
1827 * focussing the element solves this.
1828 *
1829 * This also results in the label and/or legend being announced correctly in
1830 * NVDA (as tested in 2018.3.2) - without this only the field type is announced
1831 * (e.g. "Edit, has autocomplete").
1832 *
1833 * @param {HTMLElement} $target - Event target
1834 * @returns {boolean} True if the target was able to be focussed
1835 */
1836ErrorSummary.prototype.focusTarget = function ($target) {
1837  // If the element that was clicked was not a link, return early
1838  if ($target.tagName !== 'A' || $target.href === false) {
1839    return false
1840  }
1841
1842  var inputId = this.getFragmentFromUrl($target.href);
1843  var $input = document.getElementById(inputId);
1844  if (!$input) {
1845    return false
1846  }
1847
1848  var $legendOrLabel = this.getAssociatedLegendOrLabel($input);
1849  if (!$legendOrLabel) {
1850    return false
1851  }
1852
1853  // Scroll the legend or label into view *before* calling focus on the input to
1854  // avoid extra scrolling in browsers that don't support `preventScroll` (which
1855  // at time of writing is most of them...)
1856  $legendOrLabel.scrollIntoView();
1857  $input.focus({ preventScroll: true });
1858
1859  return true
1860};
1861
1862/**
1863 * Get fragment from URL
1864 *
1865 * Extract the fragment (everything after the hash) from a URL, but not including
1866 * the hash.
1867 *
1868 * @param {string} url - URL
1869 * @returns {string} Fragment from URL, without the hash
1870 */
1871ErrorSummary.prototype.getFragmentFromUrl = function (url) {
1872  if (url.indexOf('#') === -1) {
1873    return false
1874  }
1875
1876  return url.split('#').pop()
1877};
1878
1879/**
1880 * Get associated legend or label
1881 *
1882 * Returns the first element that exists from this list:
1883 *
1884 * - The `<legend>` associated with the closest `<fieldset>` ancestor
1885 * - The first `<label>` that is associated with the input using for="inputId"
1886 * - The closest parent `<label>`
1887 *
1888 * @param {HTMLElement} $input - The input
1889 * @returns {HTMLElement} Associated legend or label, or null if no associated
1890 *                        legend or label can be found
1891 */
1892ErrorSummary.prototype.getAssociatedLegendOrLabel = function ($input) {
1893  var $fieldset = $input.closest('fieldset');
1894
1895  if ($fieldset) {
1896    var legends = $fieldset.getElementsByTagName('legend');
1897
1898    if (legends.length) {
1899      return legends[0]
1900    }
1901  }
1902
1903  return document.querySelector("label[for='" + $input.getAttribute('id') + "']") ||
1904    $input.closest('label')
1905};
1906
1907function Header ($module) {
1908  this.$module = $module;
1909}
1910
1911Header.prototype.init = function () {
1912  // Check for module
1913  var $module = this.$module;
1914  if (!$module) {
1915    return
1916  }
1917
1918  // Check for button
1919  var $toggleButton = $module.querySelector('.govuk-js-header-toggle');
1920  if (!$toggleButton) {
1921    return
1922  }
1923
1924  // Handle $toggleButton click events
1925  $toggleButton.addEventListener('click', this.handleClick.bind(this));
1926};
1927
1928/**
1929* Toggle class
1930* @param {object} node element
1931* @param {string} className to toggle
1932*/
1933Header.prototype.toggleClass = function (node, className) {
1934  if (node.className.indexOf(className) > 0) {
1935    node.className = node.className.replace(' ' + className, '');
1936  } else {
1937    node.className += ' ' + className;
1938  }
1939};
1940
1941/**
1942* An event handler for click event on $toggleButton
1943* @param {object} event event
1944*/
1945Header.prototype.handleClick = function (event) {
1946  var $module = this.$module;
1947  var $toggleButton = event.target || event.srcElement;
1948  var $target = $module.querySelector('#' + $toggleButton.getAttribute('aria-controls'));
1949
1950  // If a button with aria-controls, handle click
1951  if ($toggleButton && $target) {
1952    this.toggleClass($target, 'govuk-header__navigation--open');
1953    this.toggleClass($toggleButton, 'govuk-header__menu-button--open');
1954
1955    $toggleButton.setAttribute('aria-expanded', $toggleButton.getAttribute('aria-expanded') !== 'true');
1956    $target.setAttribute('aria-hidden', $target.getAttribute('aria-hidden') === 'false');
1957  }
1958};
1959
1960function Radios ($module) {
1961  this.$module = $module;
1962}
1963
1964Radios.prototype.init = function () {
1965  var $module = this.$module;
1966  var $inputs = $module.querySelectorAll('input[type="radio"]');
1967
1968  /**
1969  * Loop over all items with [data-controls]
1970  * Check if they have a matching conditional reveal
1971  * If they do, assign attributes.
1972  **/
1973  nodeListForEach($inputs, function ($input) {
1974    var controls = $input.getAttribute('data-aria-controls');
1975
1976    // Check if input controls anything
1977    // Check if content exists, before setting attributes.
1978    if (!controls || !$module.querySelector('#' + controls)) {
1979      return
1980    }
1981
1982    // If we have content that is controlled, set attributes.
1983    $input.setAttribute('aria-controls', controls);
1984    $input.removeAttribute('data-aria-controls');
1985    this.setAttributes($input);
1986  }.bind(this));
1987
1988  // Handle events
1989  $module.addEventListener('click', this.handleClick.bind(this));
1990};
1991
1992Radios.prototype.setAttributes = function ($input) {
1993  var $content = document.querySelector('#' + $input.getAttribute('aria-controls'));
1994
1995  if ($content && $content.classList.contains('govuk-radios__conditional')) {
1996    var inputIsChecked = $input.checked;
1997
1998    $input.setAttribute('aria-expanded', inputIsChecked);
1999
2000    $content.classList.toggle('govuk-radios__conditional--hidden', !inputIsChecked);
2001  }
2002};
2003
2004Radios.prototype.handleClick = function (event) {
2005  var $clickedInput = event.target;
2006  // We only want to handle clicks for radio inputs
2007  if ($clickedInput.type !== 'radio') {
2008    return
2009  }
2010  // Because checking one radio can uncheck a radio in another $module,
2011  // we need to call set attributes on all radios in the same form, or document if they're not in a form.
2012  //
2013  // We also only want radios which have aria-controls, as they support conditional reveals.
2014  var $allInputs = document.querySelectorAll('input[type="radio"][aria-controls]');
2015  nodeListForEach($allInputs, function ($input) {
2016    // Only inputs with the same form owner should change.
2017    var hasSameFormOwner = ($input.form === $clickedInput.form);
2018
2019    // In radios, only radios with the same name will affect each other.
2020    var hasSameName = ($input.name === $clickedInput.name);
2021    if (hasSameName && hasSameFormOwner) {
2022      this.setAttributes($input);
2023    }
2024  }.bind(this));
2025};
2026
2027(function(undefined) {
2028
2029    // Detection from https://github.com/Financial-Times/polyfill-service/pull/1062/files#diff-b09a5d2acf3314b46a6c8f8d0c31b85c
2030    var detect = (
2031      'Element' in this && "nextElementSibling" in document.documentElement
2032    );
2033
2034    if (detect) return
2035
2036
2037    (function (global) {
2038
2039      // Polyfill from https://github.com/Financial-Times/polyfill-service/pull/1062/files#diff-404b69b4750d18dea4174930a49170fd
2040      Object.defineProperty(Element.prototype, "nextElementSibling", {
2041        get: function(){
2042          var el = this.nextSibling;
2043          while (el && el.nodeType !== 1) { el = el.nextSibling; }
2044          return (el.nodeType === 1) ? el : null;
2045        }
2046      });
2047
2048    }(this));
2049
2050}).call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
2051
2052(function(undefined) {
2053
2054    // Detection from https://github.com/Financial-Times/polyfill-service/pull/1062/files#diff-a162235fbc9c0dd40d4032265f44942e
2055    var detect = (
2056      'Element' in this && 'previousElementSibling' in document.documentElement
2057    );
2058
2059    if (detect) return
2060
2061    (function (global) {
2062      // Polyfill from https://github.com/Financial-Times/polyfill-service/pull/1062/files#diff-b45a1197b842728cb76b624b6ba7d739
2063      Object.defineProperty(Element.prototype, 'previousElementSibling', {
2064        get: function(){
2065          var el = this.previousSibling;
2066          while (el && el.nodeType !== 1) { el = el.previousSibling; }
2067          return (el.nodeType === 1) ? el : null;
2068        }
2069      });
2070
2071    }(this));
2072
2073}).call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
2074
2075function Tabs ($module) {
2076  this.$module = $module;
2077  this.$tabs = $module.querySelectorAll('.govuk-tabs__tab');
2078
2079  this.keys = { left: 37, right: 39, up: 38, down: 40 };
2080  this.jsHiddenClass = 'govuk-tabs__panel--hidden';
2081}
2082
2083Tabs.prototype.init = function () {
2084  if (typeof window.matchMedia === 'function') {
2085    this.setupResponsiveChecks();
2086  } else {
2087    this.setup();
2088  }
2089};
2090
2091Tabs.prototype.setupResponsiveChecks = function () {
2092  this.mql = window.matchMedia('(min-width: 40.0625em)');
2093  this.mql.addListener(this.checkMode.bind(this));
2094  this.checkMode();
2095};
2096
2097Tabs.prototype.checkMode = function () {
2098  if (this.mql.matches) {
2099    this.setup();
2100  } else {
2101    this.teardown();
2102  }
2103};
2104
2105Tabs.prototype.setup = function () {
2106  var $module = this.$module;
2107  var $tabs = this.$tabs;
2108  var $tabList = $module.querySelector('.govuk-tabs__list');
2109  var $tabListItems = $module.querySelectorAll('.govuk-tabs__list-item');
2110
2111  if (!$tabs || !$tabList || !$tabListItems) {
2112    return
2113  }
2114
2115  $tabList.setAttribute('role', 'tablist');
2116
2117  nodeListForEach($tabListItems, function ($item) {
2118    $item.setAttribute('role', 'presentation');
2119  });
2120
2121  nodeListForEach($tabs, function ($tab) {
2122    // Set HTML attributes
2123    this.setAttributes($tab);
2124
2125    // Save bounded functions to use when removing event listeners during teardown
2126    $tab.boundTabClick = this.onTabClick.bind(this);
2127    $tab.boundTabKeydown = this.onTabKeydown.bind(this);
2128
2129    // Handle events
2130    $tab.addEventListener('click', $tab.boundTabClick, true);
2131    $tab.addEventListener('keydown', $tab.boundTabKeydown, true);
2132
2133    // Remove old active panels
2134    this.hideTab($tab);
2135  }.bind(this));
2136
2137  // Show either the active tab according to the URL's hash or the first tab
2138  var $activeTab = this.getTab(window.location.hash) || this.$tabs[0];
2139  this.showTab($activeTab);
2140
2141  // Handle hashchange events
2142  $module.boundOnHashChange = this.onHashChange.bind(this);
2143  window.addEventListener('hashchange', $module.boundOnHashChange, true);
2144};
2145
2146Tabs.prototype.teardown = function () {
2147  var $module = this.$module;
2148  var $tabs = this.$tabs;
2149  var $tabList = $module.querySelector('.govuk-tabs__list');
2150  var $tabListItems = $module.querySelectorAll('.govuk-tabs__list-item');
2151
2152  if (!$tabs || !$tabList || !$tabListItems) {
2153    return
2154  }
2155
2156  $tabList.removeAttribute('role');
2157
2158  nodeListForEach($tabListItems, function ($item) {
2159    $item.removeAttribute('role', 'presentation');
2160  });
2161
2162  nodeListForEach($tabs, function ($tab) {
2163    // Remove events
2164    $tab.removeEventListener('click', $tab.boundTabClick, true);
2165    $tab.removeEventListener('keydown', $tab.boundTabKeydown, true);
2166
2167    // Unset HTML attributes
2168    this.unsetAttributes($tab);
2169  }.bind(this));
2170
2171  // Remove hashchange event handler
2172  window.removeEventListener('hashchange', $module.boundOnHashChange, true);
2173};
2174
2175Tabs.prototype.onHashChange = function (e) {
2176  var hash = window.location.hash;
2177  var $tabWithHash = this.getTab(hash);
2178  if (!$tabWithHash) {
2179    return
2180  }
2181
2182  // Prevent changing the hash
2183  if (this.changingHash) {
2184    this.changingHash = false;
2185    return
2186  }
2187
2188  // Show either the active tab according to the URL's hash or the first tab
2189  var $previousTab = this.getCurrentTab();
2190
2191  this.hideTab($previousTab);
2192  this.showTab($tabWithHash);
2193  $tabWithHash.focus();
2194};
2195
2196Tabs.prototype.hideTab = function ($tab) {
2197  this.unhighlightTab($tab);
2198  this.hidePanel($tab);
2199};
2200
2201Tabs.prototype.showTab = function ($tab) {
2202  this.highlightTab($tab);
2203  this.showPanel($tab);
2204};
2205
2206Tabs.prototype.getTab = function (hash) {
2207  return this.$module.querySelector('.govuk-tabs__tab[href="' + hash + '"]')
2208};
2209
2210Tabs.prototype.setAttributes = function ($tab) {
2211  // set tab attributes
2212  var panelId = this.getHref($tab).slice(1);
2213  $tab.setAttribute('id', 'tab_' + panelId);
2214  $tab.setAttribute('role', 'tab');
2215  $tab.setAttribute('aria-controls', panelId);
2216  $tab.setAttribute('aria-selected', 'false');
2217  $tab.setAttribute('tabindex', '-1');
2218
2219  // set panel attributes
2220  var $panel = this.getPanel($tab);
2221  $panel.setAttribute('role', 'tabpanel');
2222  $panel.setAttribute('aria-labelledby', $tab.id);
2223  $panel.classList.add(this.jsHiddenClass);
2224};
2225
2226Tabs.prototype.unsetAttributes = function ($tab) {
2227  // unset tab attributes
2228  $tab.removeAttribute('id');
2229  $tab.removeAttribute('role');
2230  $tab.removeAttribute('aria-controls');
2231  $tab.removeAttribute('aria-selected');
2232  $tab.removeAttribute('tabindex');
2233
2234  // unset panel attributes
2235  var $panel = this.getPanel($tab);
2236  $panel.removeAttribute('role');
2237  $panel.removeAttribute('aria-labelledby');
2238  $panel.classList.remove(this.jsHiddenClass);
2239};
2240
2241Tabs.prototype.onTabClick = function (e) {
2242  if (!e.target.classList.contains('govuk-tabs__tab')) {
2243  // Allow events on child DOM elements to bubble up to tab parent
2244    return false
2245  }
2246  e.preventDefault();
2247  var $newTab = e.target;
2248  var $currentTab = this.getCurrentTab();
2249  this.hideTab($currentTab);
2250  this.showTab($newTab);
2251  this.createHistoryEntry($newTab);
2252};
2253
2254Tabs.prototype.createHistoryEntry = function ($tab) {
2255  var $panel = this.getPanel($tab);
2256
2257  // Save and restore the id
2258  // so the page doesn't jump when a user clicks a tab (which changes the hash)
2259  var id = $panel.id;
2260  $panel.id = '';
2261  this.changingHash = true;
2262  window.location.hash = this.getHref($tab).slice(1);
2263  $panel.id = id;
2264};
2265
2266Tabs.prototype.onTabKeydown = function (e) {
2267  switch (e.keyCode) {
2268    case this.keys.left:
2269    case this.keys.up:
2270      this.activatePreviousTab();
2271      e.preventDefault();
2272      break
2273    case this.keys.right:
2274    case this.keys.down:
2275      this.activateNextTab();
2276      e.preventDefault();
2277      break
2278  }
2279};
2280
2281Tabs.prototype.activateNextTab = function () {
2282  var currentTab = this.getCurrentTab();
2283  var nextTabListItem = currentTab.parentNode.nextElementSibling;
2284  if (nextTabListItem) {
2285    var nextTab = nextTabListItem.querySelector('.govuk-tabs__tab');
2286  }
2287  if (nextTab) {
2288    this.hideTab(currentTab);
2289    this.showTab(nextTab);
2290    nextTab.focus();
2291    this.createHistoryEntry(nextTab);
2292  }
2293};
2294
2295Tabs.prototype.activatePreviousTab = function () {
2296  var currentTab = this.getCurrentTab();
2297  var previousTabListItem = currentTab.parentNode.previousElementSibling;
2298  if (previousTabListItem) {
2299    var previousTab = previousTabListItem.querySelector('.govuk-tabs__tab');
2300  }
2301  if (previousTab) {
2302    this.hideTab(currentTab);
2303    this.showTab(previousTab);
2304    previousTab.focus();
2305    this.createHistoryEntry(previousTab);
2306  }
2307};
2308
2309Tabs.prototype.getPanel = function ($tab) {
2310  var $panel = this.$module.querySelector(this.getHref($tab));
2311  return $panel
2312};
2313
2314Tabs.prototype.showPanel = function ($tab) {
2315  var $panel = this.getPanel($tab);
2316  $panel.classList.remove(this.jsHiddenClass);
2317};
2318
2319Tabs.prototype.hidePanel = function (tab) {
2320  var $panel = this.getPanel(tab);
2321  $panel.classList.add(this.jsHiddenClass);
2322};
2323
2324Tabs.prototype.unhighlightTab = function ($tab) {
2325  $tab.setAttribute('aria-selected', 'false');
2326  $tab.parentNode.classList.remove('govuk-tabs__list-item--selected');
2327  $tab.setAttribute('tabindex', '-1');
2328};
2329
2330Tabs.prototype.highlightTab = function ($tab) {
2331  $tab.setAttribute('aria-selected', 'true');
2332  $tab.parentNode.classList.add('govuk-tabs__list-item--selected');
2333  $tab.setAttribute('tabindex', '0');
2334};
2335
2336Tabs.prototype.getCurrentTab = function () {
2337  return this.$module.querySelector('.govuk-tabs__list-item--selected .govuk-tabs__tab')
2338};
2339
2340// this is because IE doesn't always return the actual value but a relative full path
2341// should be a utility function most prob
2342// http://labs.thesedays.com/blog/2010/01/08/getting-the-href-value-with-jquery-in-ie/
2343Tabs.prototype.getHref = function ($tab) {
2344  var href = $tab.getAttribute('href');
2345  var hash = href.slice(href.indexOf('#'), href.length);
2346  return hash
2347};
2348
2349function initAll (options) {
2350  // Set the options to an empty object by default if no options are passed.
2351  options = typeof options !== 'undefined' ? options : {};
2352
2353  // Allow the user to initialise GOV.UK Frontend in only certain sections of the page
2354  // Defaults to the entire document if nothing is set.
2355  var scope = typeof options.scope !== 'undefined' ? options.scope : document;
2356
2357  var $buttons = scope.querySelectorAll('[data-module="govuk-button"]');
2358  nodeListForEach($buttons, function ($button) {
2359    new Button($button).init();
2360  });
2361
2362  var $accordions = scope.querySelectorAll('[data-module="govuk-accordion"]');
2363  nodeListForEach($accordions, function ($accordion) {
2364    new Accordion($accordion).init();
2365  });
2366
2367  var $details = scope.querySelectorAll('[data-module="govuk-details"]');
2368  nodeListForEach($details, function ($detail) {
2369    new Details($detail).init();
2370  });
2371
2372  var $characterCounts = scope.querySelectorAll('[data-module="govuk-character-count"]');
2373  nodeListForEach($characterCounts, function ($characterCount) {
2374    new CharacterCount($characterCount).init();
2375  });
2376
2377  var $checkboxes = scope.querySelectorAll('[data-module="govuk-checkboxes"]');
2378  nodeListForEach($checkboxes, function ($checkbox) {
2379    new Checkboxes($checkbox).init();
2380  });
2381
2382  // Find first error summary module to enhance.
2383  var $errorSummary = scope.querySelector('[data-module="govuk-error-summary"]');
2384  new ErrorSummary($errorSummary).init();
2385
2386  // Find first header module to enhance.
2387  var $toggleButton = scope.querySelector('[data-module="govuk-header"]');
2388  new Header($toggleButton).init();
2389
2390  var $radios = scope.querySelectorAll('[data-module="govuk-radios"]');
2391  nodeListForEach($radios, function ($radio) {
2392    new Radios($radio).init();
2393  });
2394
2395  var $tabs = scope.querySelectorAll('[data-module="govuk-tabs"]');
2396  nodeListForEach($tabs, function ($tabs) {
2397    new Tabs($tabs).init();
2398  });
2399}
2400
2401exports.initAll = initAll;
2402exports.Accordion = Accordion;
2403exports.Button = Button;
2404exports.Details = Details;
2405exports.CharacterCount = CharacterCount;
2406exports.Checkboxes = Checkboxes;
2407exports.ErrorSummary = ErrorSummary;
2408exports.Header = Header;
2409exports.Radios = Radios;
2410exports.Tabs = Tabs;
2411
2412})));

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.