govuk_publishing_components 13.6.0 → 13.6.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (28) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/govuk_publishing_components/dependencies.js +3 -3
  3. data/app/assets/javascripts/govuk_publishing_components/lib/toggle.js +52 -28
  4. data/app/views/govuk_publishing_components/components/docs/related_navigation.yml +216 -201
  5. data/lib/govuk_publishing_components/presenters/meta_tags.rb +0 -7
  6. data/lib/govuk_publishing_components/presenters/related_navigation_helper.rb +3 -3
  7. data/lib/govuk_publishing_components/version.rb +1 -1
  8. data/node_modules/govuk-frontend/all.js +1197 -943
  9. data/node_modules/govuk-frontend/components/_all.scss +2 -0
  10. data/node_modules/govuk-frontend/components/accordion/README.md +17 -0
  11. data/node_modules/govuk-frontend/components/accordion/_accordion.scss +173 -0
  12. data/node_modules/govuk-frontend/components/accordion/accordion.js +1011 -0
  13. data/node_modules/govuk-frontend/components/accordion/macro-options.json +70 -0
  14. data/node_modules/govuk-frontend/components/accordion/macro.njk +3 -0
  15. data/node_modules/govuk-frontend/components/accordion/template.njk +27 -0
  16. data/node_modules/govuk-frontend/components/input/macro-options.json +7 -0
  17. data/node_modules/govuk-frontend/components/summary-list/README.md +15 -0
  18. data/node_modules/govuk-frontend/components/summary-list/_summary-list.scss +112 -0
  19. data/node_modules/govuk-frontend/components/summary-list/macro-options.json +95 -0
  20. data/node_modules/govuk-frontend/components/summary-list/macro.njk +3 -0
  21. data/node_modules/govuk-frontend/components/summary-list/template.njk +35 -0
  22. data/node_modules/govuk-frontend/helpers/_grid.scss +43 -35
  23. data/node_modules/govuk-frontend/helpers/_visually-hidden.scss +4 -2
  24. data/node_modules/govuk-frontend/objects/_grid.scss +21 -8
  25. data/node_modules/govuk-frontend/package.json +11 -11
  26. data/node_modules/govuk-frontend/settings/_measurements.scss +14 -0
  27. data/node_modules/govuk-frontend/template.njk +2 -0
  28. metadata +13 -2
@@ -29,13 +29,6 @@ module GovukPublishingComponents
29
29
  meta_tags["govuk:format"] = content_item[:document_type] if content_item[:document_type]
30
30
  meta_tags["govuk:publishing-application"] = content_item[:publishing_app] if content_item[:publishing_app]
31
31
  meta_tags["govuk:schema-name"] = content_item[:schema_name] if content_item[:schema_name]
32
-
33
- user_journey_stage = content_item[:user_journey_document_supertype]
34
- meta_tags["govuk:user-journey-stage"] = user_journey_stage if user_journey_stage
35
-
36
- navigation_document_type = content_item[:navigation_document_supertype]
37
- meta_tags["govuk:navigation-document-type"] = navigation_document_type if navigation_document_type
38
-
39
32
  meta_tags["govuk:content-id"] = content_item[:content_id] if content_item[:content_id]
40
33
  meta_tags["govuk:withdrawn"] = "withdrawn" if content_item[:withdrawn_notice].present?
41
34
  meta_tags["govuk:content-has-history"] = "true" if has_content_history?
@@ -13,9 +13,9 @@ module GovukPublishingComponents
13
13
  statistical_data_sets
14
14
  ).freeze
15
15
 
16
- def initialize(content_item:, context: nil)
17
- @content_item = content_item
18
- @context = context
16
+ def initialize(options = {})
17
+ @content_item = options.fetch(:content_item) { raise ArgumentError, 'missing argument: content_item' }
18
+ @context = options.fetch(:context, nil)
19
19
  end
20
20
 
21
21
  def related_navigation
@@ -1,3 +1,3 @@
1
1
  module GovukPublishingComponents
2
- VERSION = '13.6.0'.freeze
2
+ VERSION = '13.6.1'.freeze
3
3
  end
@@ -35,167 +35,6 @@ function generateUniqueID () {
35
35
 
36
36
  (function(undefined) {
37
37
 
38
- // Detection from https://github.com/Financial-Times/polyfill-service/blob/master/packages/polyfill-library/polyfills/Window/detect.js
39
- var detect = ('Window' in this);
40
-
41
- if (detect) return
42
-
43
- // Polyfill from https://cdn.polyfill.io/v2/polyfill.js?features=Window&flags=always
44
- if ((typeof WorkerGlobalScope === "undefined") && (typeof importScripts !== "function")) {
45
- (function (global) {
46
- if (global.constructor) {
47
- global.Window = global.constructor;
48
- } else {
49
- (global.Window = global.constructor = new Function('return function Window() {}')()).prototype = this;
50
- }
51
- }(this));
52
- }
53
-
54
- })
55
- .call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
56
-
57
- (function(undefined) {
58
-
59
- // Detection from https://github.com/Financial-Times/polyfill-service/blob/master/packages/polyfill-library/polyfills/Document/detect.js
60
- var detect = ("Document" in this);
61
-
62
- if (detect) return
63
-
64
- // Polyfill from https://cdn.polyfill.io/v2/polyfill.js?features=Document&flags=always
65
- if ((typeof WorkerGlobalScope === "undefined") && (typeof importScripts !== "function")) {
66
-
67
- if (this.HTMLDocument) { // IE8
68
-
69
- // HTMLDocument is an extension of Document. If the browser has HTMLDocument but not Document, the former will suffice as an alias for the latter.
70
- this.Document = this.HTMLDocument;
71
-
72
- } else {
73
-
74
- // 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.
75
- this.Document = this.HTMLDocument = document.constructor = (new Function('return function Document() {}')());
76
- this.Document.prototype = document;
77
- }
78
- }
79
-
80
-
81
- })
82
- .call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
83
-
84
- (function(undefined) {
85
-
86
- // Detection from https://github.com/Financial-Times/polyfill-service/blob/master/packages/polyfill-library/polyfills/Element/detect.js
87
- var detect = ('Element' in this && 'HTMLElement' in this);
88
-
89
- if (detect) return
90
-
91
- // Polyfill from https://cdn.polyfill.io/v2/polyfill.js?features=Element&flags=always
92
- (function () {
93
-
94
- // IE8
95
- if (window.Element && !window.HTMLElement) {
96
- window.HTMLElement = window.Element;
97
- return;
98
- }
99
-
100
- // create Element constructor
101
- window.Element = window.HTMLElement = new Function('return function Element() {}')();
102
-
103
- // generate sandboxed iframe
104
- var vbody = document.appendChild(document.createElement('body'));
105
- var frame = vbody.appendChild(document.createElement('iframe'));
106
-
107
- // use sandboxed iframe to replicate Element functionality
108
- var frameDocument = frame.contentWindow.document;
109
- var prototype = Element.prototype = frameDocument.appendChild(frameDocument.createElement('*'));
110
- var cache = {};
111
-
112
- // polyfill Element.prototype on an element
113
- var shiv = function (element, deep) {
114
- var
115
- childNodes = element.childNodes || [],
116
- index = -1,
117
- key, value, childNode;
118
-
119
- if (element.nodeType === 1 && element.constructor !== Element) {
120
- element.constructor = Element;
121
-
122
- for (key in cache) {
123
- value = cache[key];
124
- element[key] = value;
125
- }
126
- }
127
-
128
- while (childNode = deep && childNodes[++index]) {
129
- shiv(childNode, deep);
130
- }
131
-
132
- return element;
133
- };
134
-
135
- var elements = document.getElementsByTagName('*');
136
- var nativeCreateElement = document.createElement;
137
- var interval;
138
- var loopLimit = 100;
139
-
140
- prototype.attachEvent('onpropertychange', function (event) {
141
- var
142
- propertyName = event.propertyName,
143
- nonValue = !cache.hasOwnProperty(propertyName),
144
- newValue = prototype[propertyName],
145
- oldValue = cache[propertyName],
146
- index = -1,
147
- element;
148
-
149
- while (element = elements[++index]) {
150
- if (element.nodeType === 1) {
151
- if (nonValue || element[propertyName] === oldValue) {
152
- element[propertyName] = newValue;
153
- }
154
- }
155
- }
156
-
157
- cache[propertyName] = newValue;
158
- });
159
-
160
- prototype.constructor = Element;
161
-
162
- if (!prototype.hasAttribute) {
163
- // <Element>.hasAttribute
164
- prototype.hasAttribute = function hasAttribute(name) {
165
- return this.getAttribute(name) !== null;
166
- };
167
- }
168
-
169
- // Apply Element prototype to the pre-existing DOM as soon as the body element appears.
170
- function bodyCheck() {
171
- if (!(loopLimit--)) clearTimeout(interval);
172
- if (document.body && !document.body.prototype && /(complete|interactive)/.test(document.readyState)) {
173
- shiv(document, true);
174
- if (interval && document.body.prototype) clearTimeout(interval);
175
- return (!!document.body.prototype);
176
- }
177
- return false;
178
- }
179
- if (!bodyCheck()) {
180
- document.onreadystatechange = bodyCheck;
181
- interval = setInterval(bodyCheck, 25);
182
- }
183
-
184
- // Apply to any new elements created after load
185
- document.createElement = function createElement(nodeName) {
186
- var element = nativeCreateElement(String(nodeName).toLowerCase());
187
- return shiv(element);
188
- };
189
-
190
- // remove sandboxed iframe
191
- document.removeChild(vbody);
192
- }());
193
-
194
- })
195
- .call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
196
-
197
- (function(undefined) {
198
-
199
38
  // Detection from https://github.com/Financial-Times/polyfill-service/blob/master/packages/polyfill-library/polyfills/Object/defineProperty/detect.js
200
39
  var detect = (
201
40
  // In IE8, defineProperty could only act on DOM elements, so full support
@@ -282,959 +121,1368 @@ if (detect) return
282
121
  .call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
283
122
 
284
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;
285
126
 
286
- // Detection from https://github.com/Financial-Times/polyfill-service/blob/master/packages/polyfill-library/polyfills/Event/detect.js
287
- var detect = (
288
- (function(global) {
127
+ if (detect) return
289
128
 
290
- if (!('Event' in global)) return false;
291
- if (typeof global.Event === 'function') return true;
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
292
146
 
293
- try {
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 in
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 () {
294
168
 
295
- // In IE 9-11, the Event object exists but cannot be instantiated
296
- new Event('click');
297
- return true;
298
- } catch(e) {
299
- return false;
300
- }
301
- }(this))
302
- );
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.
303
185
 
304
- if (detect) return
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;
305
194
 
306
- // Polyfill from https://cdn.polyfill.io/v2/polyfill.js?features=Event&flags=always
307
- (function () {
308
- var unlistenableWindowEvents = {
309
- click: 1,
310
- dblclick: 1,
311
- keyup: 1,
312
- keypress: 1,
313
- keydown: 1,
314
- mousedown: 1,
315
- mouseup: 1,
316
- mousemove: 1,
317
- mouseover: 1,
318
- mouseenter: 1,
319
- mouseleave: 1,
320
- mouseout: 1,
321
- storage: 1,
322
- storagecommit: 1,
323
- textinput: 1
324
- };
325
-
326
- // This polyfill depends on availability of `document` so will not run in a worker
327
- // However, we asssume there are no browsers with worker support that lack proper
328
- // support for `Event` within the worker
329
- if (typeof document === 'undefined' || typeof window === 'undefined') return;
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.
330
213
 
331
- function indexOf(array, element) {
332
- var
333
- index = -1,
334
- length = array.length;
214
+ // equiv: target.call(this, ...boundArgs, ...args)
215
+ return target.apply(
216
+ that,
217
+ array_concat.call(args, array_slice.call(arguments))
218
+ );
335
219
 
336
- while (++index < length) {
337
- if (index in array && array[index] === element) {
338
- return index;
339
- }
340
- }
220
+ }
341
221
 
342
- return -1;
343
- }
222
+ };
344
223
 
345
- var existingProto = (window.Event && window.Event.prototype) || null;
346
- window.Event = Window.prototype.Event = function Event(type, eventInitDict) {
347
- if (!type) {
348
- throw new Error('Not enough arguments');
349
- }
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.
350
229
 
351
- var event;
352
- // Shortcut if browser supports createEvent
353
- if ('createEvent' in document) {
354
- event = document.createEvent('Event');
355
- var bubbles = eventInitDict && eventInitDict.bubbles !== undefined ? eventInitDict.bubbles : false;
356
- var cancelable = eventInitDict && eventInitDict.cancelable !== undefined ? eventInitDict.cancelable : false;
230
+ var boundLength = max(0, target.length - args.length);
357
231
 
358
- event.initEvent(type, bubbles, cancelable);
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
+ }
359
238
 
360
- return event;
361
- }
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);
362
246
 
363
- event = document.createEventObject();
247
+ if (target.prototype) {
248
+ Empty.prototype = target.prototype;
249
+ bound.prototype = new Empty();
250
+ // Clean up dangling references.
251
+ Empty.prototype = null;
252
+ }
364
253
 
365
- event.type = type;
366
- event.bubbles = eventInitDict && eventInitDict.bubbles !== undefined ? eventInitDict.bubbles : false;
367
- event.cancelable = eventInitDict && eventInitDict.cancelable !== undefined ? eventInitDict.cancelable : false;
254
+ // TODO
255
+ // 18. Set the [[Extensible]] internal property of F to true.
368
256
 
369
- return event;
370
- };
371
- if (existingProto) {
372
- Object.defineProperty(window.Event, 'prototype', {
373
- configurable: false,
374
- enumerable: false,
375
- writable: true,
376
- value: existingProto
377
- });
378
- }
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.
379
267
 
380
- if (!('createEvent' in document)) {
381
- window.addEventListener = Window.prototype.addEventListener = Document.prototype.addEventListener = Element.prototype.addEventListener = function addEventListener() {
382
- var
383
- element = this,
384
- type = arguments[0],
385
- listener = arguments[1];
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.
386
273
 
387
- if (element === window && type in unlistenableWindowEvents) {
388
- 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.');
389
- }
274
+ // 22. Return F.
275
+ return bound;
276
+ }
277
+ });
278
+ })
279
+ .call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
390
280
 
391
- if (!element._events) {
392
- element._events = {};
393
- }
281
+ (function(undefined) {
394
282
 
395
- if (!element._events[type]) {
396
- element._events[type] = function (event) {
397
- var
398
- list = element._events[event.type].list,
399
- events = list.slice(),
400
- index = -1,
401
- length = events.length,
402
- eventElement;
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
+ );
403
289
 
404
- event.preventDefault = function preventDefault() {
405
- if (event.cancelable !== false) {
406
- event.returnValue = false;
407
- }
408
- };
290
+ if (detect) return
409
291
 
410
- event.stopPropagation = function stopPropagation() {
411
- event.cancelBubble = true;
412
- };
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;
413
295
 
414
- event.stopImmediatePropagation = function stopImmediatePropagation() {
415
- event.cancelBubble = true;
416
- event.cancelImmediate = true;
417
- };
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
+ });
418
312
 
419
- event.currentTarget = element;
420
- event.relatedTarget = event.fromElement || null;
421
- event.target = event.target || event.srcElement || element;
422
- event.timeStamp = new Date().getTime();
313
+ else object.__defineGetter__(name, fn);
314
+ };
423
315
 
424
- if (event.clientX) {
425
- event.pageX = event.clientX + document.documentElement.scrollLeft;
426
- event.pageY = event.clientY + document.documentElement.scrollTop;
427
- }
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
+ }
428
323
 
429
- while (++index < length && !event.cancelImmediate) {
430
- if (index in events) {
431
- eventElement = events[index];
432
324
 
433
- if (indexOf(list, eventElement) !== -1 && typeof eventElement === 'function') {
434
- eventElement.call(element, event);
435
- }
436
- }
437
- }
438
- };
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);
439
336
 
440
- element._events[type].list = [];
337
+ };
338
+ var reindex = function () {
441
339
 
442
- if (element.attachEvent) {
443
- element.attachEvent('on' + type, element._events[type]);
444
- }
445
- }
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
+ };
446
346
 
447
- element._events[type].list.push(listener);
448
- };
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+/;
449
353
 
450
- window.removeEventListener = Window.prototype.removeEventListener = Document.prototype.removeEventListener = Element.prototype.removeEventListener = function removeEventListener() {
451
- var
452
- element = this,
453
- type = arguments[0],
454
- listener = arguments[1],
455
- index;
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 invalid character');
359
+ error.code = 5;
360
+ error.name = "InvalidCharacterError";
361
+ throw error;
362
+ }
456
363
 
457
- if (element._events && element._events[type] && element._events[type].list) {
458
- index = indexOf(element._events[type].list, listener);
459
364
 
460
- if (index !== -1) {
461
- element._events[type].list.splice(index, 1);
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
+ }
462
371
 
463
- if (!element._events[type].list.length) {
464
- if (element.detachEvent) {
465
- element.detachEvent('on' + type, element._events[type]);
466
- }
467
- delete element._events[type];
468
- }
469
- }
470
- }
471
- };
372
+ /** Avoid treating blank strings as single-item token lists */
373
+ if ("" === tokens[0]) tokens = [];
472
374
 
473
- window.dispatchEvent = Window.prototype.dispatchEvent = Document.prototype.dispatchEvent = Element.prototype.dispatchEvent = function dispatchEvent(event) {
474
- if (!arguments.length) {
475
- throw new Error('Not enough arguments');
476
- }
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
+ };
477
382
 
478
- if (!event || typeof event.type !== 'string') {
479
- throw new Error('DOM Events Exception 0');
480
- }
383
+ /** Populate our internal token list if the targeted attribute of the subject element isn't empty. */
384
+ preop();
481
385
 
482
- var element = this, type = event.type;
386
+ /** Return the number of tokens in the underlying string. Read-only. */
387
+ defineGetter(that, "length", function () {
388
+ preop();
389
+ return length;
390
+ });
483
391
 
484
- try {
485
- if (!event.bubbles) {
486
- event.cancelBubble = true;
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
+ };
487
398
 
488
- var cancelBubbleEvent = function (event) {
489
- event.cancelBubble = true;
399
+ that.item = function (idx) {
400
+ preop();
401
+ return tokens[idx];
402
+ };
490
403
 
491
- (element || window).detachEvent('on' + type, cancelBubbleEvent);
492
- };
404
+ that.contains = function (token) {
405
+ preop();
406
+ return !!tokenMap[token];
407
+ };
493
408
 
494
- this.attachEvent('on' + type, cancelBubbleEvent);
495
- }
409
+ that.add = function () {
410
+ preop.apply(that, args = arguments);
496
411
 
497
- this.fireEvent('on' + type, event);
498
- } catch (error) {
499
- event.target = element;
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
+ }
500
419
 
501
- do {
502
- event.currentTarget = element;
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
+ };
503
431
 
504
- if ('_events' in element && typeof element._events[type] === 'function') {
505
- element._events[type].call(element, event);
506
- }
432
+ that.remove = function () {
433
+ preop.apply(that, args = arguments);
507
434
 
508
- if (typeof element['on' + type] === 'function') {
509
- element['on' + type].call(element, event);
510
- }
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
+ }
511
440
 
512
- element = element.nodeType === 9 ? element.parentWindow : element.parentNode;
513
- } while (element && !event.cancelBubble);
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
549
+ var detect = ("Document" in this);
550
+
551
+ if (detect) return
552
+
553
+ // Polyfill from https://cdn.polyfill.io/v2/polyfill.js?features=Document&flags=always
554
+ if ((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
576
+ var detect = ('Element' in this && 'HTMLElement' in this);
577
+
578
+ if (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;
514
614
  }
615
+ }
515
616
 
516
- return true;
517
- };
617
+ while (childNode = deep && childNodes[++index]) {
618
+ shiv(childNode, deep);
619
+ }
518
620
 
519
- // Add the DOMContentLoaded Event
520
- document.attachEvent('onreadystatechange', function() {
521
- if (document.readyState === 'complete') {
522
- document.dispatchEvent(new Event('DOMContentLoaded', {
523
- bubbles: true
524
- }));
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
+ }
525
643
  }
526
- });
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);
527
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);
528
681
  }());
529
682
 
530
683
  })
531
684
  .call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
532
685
 
533
- /**
534
- * JavaScript 'shim' to trigger the click event of element(s) when the space key is pressed.
535
- *
536
- * Created since some Assistive Technologies (for example some Screenreaders)
537
- * will tell a user to press space on a 'button', so this functionality needs to be shimmed
538
- * See https://github.com/alphagov/govuk_elements/pull/272#issuecomment-233028270
539
- *
540
- * Usage instructions:
541
- * the 'shim' will be automatically initialised
542
- */
686
+ (function(undefined) {
543
687
 
544
- var KEY_SPACE = 32;
688
+ // Detection from https://raw.githubusercontent.com/Financial-Times/polyfill-service/8717a9e04ac7aff99b4980fbedead98036b0929a/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
+ );
545
696
 
546
- function Button ($module) {
547
- this.$module = $module;
548
- }
697
+ if (detect) return
549
698
 
550
- /**
551
- * Add event handler for KeyDown
552
- * if the event target element has a role='button' and the event is key space pressed
553
- * then it prevents the default event and triggers a click event
554
- * @param {object} event event
555
- */
556
- Button.prototype.handleKeyDown = function (event) {
557
- // get the target element
558
- var target = event.target;
559
- // if the element has a role='button' and the pressed key is a space, we'll simulate a click
560
- if (target.getAttribute('role') === 'button' && event.keyCode === KEY_SPACE) {
561
- event.preventDefault();
562
- // trigger the target's click event
563
- target.click();
564
- }
565
- };
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;
566
745
 
567
- /**
568
- * Initialise an event listener for keydown at document level
569
- * this will help listening for later inserted elements with a role="button"
570
- */
571
- Button.prototype.init = function () {
572
- this.$module.addEventListener('keydown', this.handleKeyDown);
573
- };
746
+ for (var i = 0; i < l; ++i)
747
+ if (reflections[i]._R === THIS) {
748
+ visage = reflections[i];
749
+ break;
750
+ }
574
751
 
575
- (function(undefined) {
576
- // Detection from https://github.com/Financial-Times/polyfill-service/blob/master/packages/polyfill-library/polyfills/Function/prototype/bind/detect.js
577
- var detect = 'bind' in Function.prototype;
752
+ /** Couldn't find an element's reflection inside the mirror. Materialise one. */
753
+ visage || (visage = mirror.appendChild(document.createElement("div")));
578
754
 
579
- if (detect) return
755
+ tokenList = DOMTokenList.call(visage, THIS, attr);
756
+ } else tokenList = new DOMTokenList(THIS, attr);
580
757
 
581
- // Polyfill from https://cdn.polyfill.io/v2/polyfill.js?features=Function.prototype.bind&flags=always
582
- Object.defineProperty(Function.prototype, 'bind', {
583
- value: function bind(that) { // .length is 1
584
- // add necessary es5-shim utilities
585
- var $Array = Array;
586
- var $Object = Object;
587
- var ObjectPrototype = $Object.prototype;
588
- var ArrayPrototype = $Array.prototype;
589
- var Empty = function Empty() {};
590
- var to_string = ObjectPrototype.toString;
591
- var hasToStringTag = typeof Symbol === 'function' && typeof Symbol.toStringTag === 'symbol';
592
- 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; };
593
- var array_slice = ArrayPrototype.slice;
594
- var array_concat = ArrayPrototype.concat;
595
- var array_push = ArrayPrototype.push;
596
- var max = Math.max;
597
- // /add necessary es5-shim utilities
758
+ defineGetter(THIS, name, function () {
759
+ return tokenList;
760
+ });
761
+ delete THIS[gibberishProperty];
598
762
 
599
- // 1. Let Target be the this value.
600
- var target = this;
601
- // 2. If IsCallable(Target) is false, throw a TypeError exception.
602
- if (!isCallable(target)) {
603
- throw new TypeError('Function.prototype.bind called on incompatible ' + target);
604
- }
605
- // 3. Let A be a new (possibly empty) internal list of all of the
606
- // argument values provided after thisArg (arg1, arg2 etc), in order.
607
- // XXX slicedArgs will stand in for "A" if used
608
- var args = array_slice.call(arguments, 1); // for normal call
609
- // 4. Let F be a new native ECMAScript object.
610
- // 11. Set the [[Prototype]] internal property of F to the standard
611
- // built-in Function prototype object as specified in 15.3.3.1.
612
- // 12. Set the [[Call]] internal property of F as described in
613
- // 15.3.4.5.1.
614
- // 13. Set the [[Construct]] internal property of F as described in
615
- // 15.3.4.5.2.
616
- // 14. Set the [[HasInstance]] internal property of F as described in
617
- // 15.3.4.5.3.
618
- var bound;
619
- var binder = function () {
763
+ return tokenList;
764
+ }, true);
765
+ };
620
766
 
621
- if (this instanceof bound) {
622
- // 15.3.4.5.2 [[Construct]]
623
- // When the [[Construct]] internal method of a function object,
624
- // F that was created using the bind function is called with a
625
- // list of arguments ExtraArgs, the following steps are taken:
626
- // 1. Let target be the value of F's [[TargetFunction]]
627
- // internal property.
628
- // 2. If target has no [[Construct]] internal method, a
629
- // TypeError exception is thrown.
630
- // 3. Let boundArgs be the value of F's [[BoundArgs]] internal
631
- // property.
632
- // 4. Let args be a new list containing the same values as the
633
- // list boundArgs in the same order followed by the same
634
- // values as the list ExtraArgs in the same order.
635
- // 5. Return the result of calling the [[Construct]] internal
636
- // method of target providing args as the arguments.
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));
637
773
 
638
- var result = target.apply(
639
- this,
640
- array_concat.call(args, array_slice.call(arguments))
641
- );
642
- if ($Object(result) === result) {
643
- return result;
644
- }
645
- return this;
774
+ }).call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
646
775
 
647
- } else {
648
- // 15.3.4.5.1 [[Call]]
649
- // When the [[Call]] internal method of a function object, F,
650
- // which was created using the bind function is called with a
651
- // this value and a list of arguments ExtraArgs, the following
652
- // steps are taken:
653
- // 1. Let boundArgs be the value of F's [[BoundArgs]] internal
654
- // property.
655
- // 2. Let boundThis be the value of F's [[BoundThis]] internal
656
- // property.
657
- // 3. Let target be the value of F's [[TargetFunction]] internal
658
- // property.
659
- // 4. Let args be a new list containing the same values as the
660
- // list boundArgs in the same order followed by the same
661
- // values as the list ExtraArgs in the same order.
662
- // 5. Return the result of calling the [[Call]] internal method
663
- // of target providing boundThis as the this value and
664
- // providing args as the arguments.
776
+ function 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
+ }
665
794
 
666
- // equiv: target.call(this, ...boundArgs, ...args)
667
- return target.apply(
668
- that,
669
- array_concat.call(args, array_slice.call(arguments))
670
- );
795
+ // Initialize component
796
+ Accordion.prototype.init = function () {
797
+ // Check for module
798
+ if (!this.$module) {
799
+ return
800
+ }
671
801
 
672
- }
802
+ this.initControls();
673
803
 
674
- };
804
+ this.initSectionHeaders();
675
805
 
676
- // 15. If the [[Class]] internal property of Target is "Function", then
677
- // a. Let L be the length property of Target minus the length of A.
678
- // b. Set the length own property of F to either 0 or L, whichever is
679
- // larger.
680
- // 16. Else set the length own property of F to 0.
806
+ // See if "Open all" button text should be updated
807
+ var areAllSectionsOpen = this.checkIfAllSectionsOpen();
808
+ this.updateOpenAllButton(areAllSectionsOpen);
809
+ };
681
810
 
682
- var boundLength = max(0, target.length - args.length);
811
+ // Initialise controls and set attributes
812
+ Accordion.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
+ };
683
830
 
684
- // 17. Set the attributes of the length own property of F to the values
685
- // specified in 15.3.5.1.
686
- var boundArgs = [];
687
- for (var i = 0; i < boundLength; i++) {
688
- array_push.call(boundArgs, '$' + i);
689
- }
831
+ // Initialise section headers
832
+ Accordion.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);
690
838
 
691
- // XXX Build a dynamic function with desired amount of arguments is the only
692
- // way to set the length property of a function.
693
- // In environments where Content Security Policies enabled (Chrome extensions,
694
- // for ex.) all use of eval or Function costructor throws an exception.
695
- // However in all of these environments Function.prototype.bind exists
696
- // and so this code will never be executed.
697
- bound = Function('binder', 'return function (' + boundArgs.join(',') + '){ return binder.apply(this, arguments); }')(binder);
839
+ this.setExpanded(this.isExpanded($section), $section);
698
840
 
699
- if (target.prototype) {
700
- Empty.prototype = target.prototype;
701
- bound.prototype = new Empty();
702
- // Clean up dangling references.
703
- Empty.prototype = null;
704
- }
841
+ // Handle events
842
+ header.addEventListener('click', this.onSectionToggle.bind(this, $section));
705
843
 
706
- // TODO
707
- // 18. Set the [[Extensible]] internal property of F to true.
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
+ };
708
849
 
709
- // TODO
710
- // 19. Let thrower be the [[ThrowTypeError]] function Object (13.2.3).
711
- // 20. Call the [[DefineOwnProperty]] internal method of F with
712
- // arguments "caller", PropertyDescriptor {[[Get]]: thrower, [[Set]]:
713
- // thrower, [[Enumerable]]: false, [[Configurable]]: false}, and
714
- // false.
715
- // 21. Call the [[DefineOwnProperty]] internal method of F with
716
- // arguments "arguments", PropertyDescriptor {[[Get]]: thrower,
717
- // [[Set]]: thrower, [[Enumerable]]: false, [[Configurable]]: false},
718
- // and false.
850
+ // Set individual header attributes
851
+ Accordion.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
+ }
719
868
 
720
- // TODO
721
- // NOTE Function objects created using Function.prototype.bind do not
722
- // have a prototype property or the [[Code]], [[FormalParameters]], and
723
- // [[Scope]] internal properties.
724
- // XXX can't delete prototype in pure-js.
869
+ $button.addEventListener('focusin', function (e) {
870
+ if (!$headerWrapper.classList.contains($module.sectionHeaderFocusedClass)) {
871
+ $headerWrapper.className += ' ' + $module.sectionHeaderFocusedClass;
872
+ }
873
+ });
725
874
 
726
- // 22. Return F.
727
- return bound;
728
- }
875
+ $button.addEventListener('blur', function (e) {
876
+ $headerWrapper.classList.remove($module.sectionHeaderFocusedClass);
729
877
  });
730
- })
731
- .call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
732
878
 
733
- /**
734
- * JavaScript 'polyfill' for HTML5's <details> and <summary> elements
735
- * and 'shim' to add accessiblity enhancements for all browsers
736
- *
737
- * http://caniuse.com/#feat=details
738
- *
739
- * Usage instructions:
740
- * the 'polyfill' will be automatically initialised
741
- */
879
+ if (typeof ($summary) !== 'undefined' && $summary !== null) {
880
+ $button.setAttribute('aria-describedby', this.moduleId + '-summary-' + (index + 1));
881
+ }
742
882
 
743
- var KEY_ENTER = 13;
744
- var KEY_SPACE$1 = 32;
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;
745
885
 
746
- // Create a flag to know if the browser supports navtive details
747
- var NATIVE_DETAILS = typeof document.createElement('details').open === 'boolean';
886
+ $heading.removeChild($span);
887
+ $heading.appendChild($button);
748
888
 
749
- function Details ($module) {
750
- this.$module = $module;
751
- }
889
+ // Add "+/-" icon
890
+ var icon = document.createElement('span');
891
+ icon.className = this.iconClass;
892
+ icon.setAttribute('aria-hidden', 'true');
752
893
 
753
- /**
754
- * Handle cross-modal click events
755
- * @param {object} node element
756
- * @param {function} callback function
757
- */
758
- Details.prototype.handleInputs = function (node, callback) {
759
- node.addEventListener('keypress', function (event) {
760
- var target = event.target;
761
- // When the key gets pressed - check if it is enter or space
762
- if (event.keyCode === KEY_ENTER || event.keyCode === KEY_SPACE$1) {
763
- if (target.nodeName.toLowerCase() === 'summary') {
764
- // Prevent space from scrolling the page
765
- // and enter from submitting a form
766
- event.preventDefault();
767
- // Click to let the click event do all the necessary action
768
- if (target.click) {
769
- target.click();
770
- } else {
771
- // except Safari 5.1 and under don't support .click() here
772
- callback(event);
773
- }
774
- }
775
- }
776
- });
894
+ $heading.appendChild(icon);
895
+ };
777
896
 
778
- // Prevent keyup to prevent clicking twice in Firefox when using space key
779
- node.addEventListener('keyup', function (event) {
780
- var target = event.target;
781
- if (event.keyCode === KEY_SPACE$1) {
782
- if (target.nodeName.toLowerCase() === 'summary') {
783
- event.preventDefault();
784
- }
785
- }
786
- });
897
+ // When section toggled, set and store state
898
+ Accordion.prototype.onSectionToggle = function ($section) {
899
+ var expanded = this.isExpanded($section);
900
+ this.setExpanded(!expanded, $section);
787
901
 
788
- node.addEventListener('click', callback);
902
+ // Store the state in sessionStorage when a change is triggered
903
+ this.storeState($section);
789
904
  };
790
905
 
791
- Details.prototype.init = function () {
792
- var $module = this.$module;
906
+ // When Open/Close All toggled, set and store state
907
+ Accordion.prototype.onOpenOrCloseAllToggle = function () {
908
+ var $module = this;
909
+ var $sections = this.$sections;
793
910
 
794
- if (!$module) {
795
- return
796
- }
911
+ var nowExpanded = !this.checkIfAllSectionsOpen();
797
912
 
798
- // Save shortcuts to the inner summary and content elements
799
- var $summary = this.$summary = $module.getElementsByTagName('summary').item(0);
800
- var $content = this.$content = $module.getElementsByTagName('div').item(0);
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
+ });
801
918
 
802
- // If <details> doesn't have a <summary> and a <div> representing the content
803
- // it means the required HTML structure is not met so the script will stop
804
- if (!$summary || !$content) {
805
- return
806
- }
919
+ $module.updateOpenAllButton(nowExpanded);
920
+ };
807
921
 
808
- // If the content doesn't have an ID, assign it one now
809
- // which we'll need for the summary's aria-controls assignment
810
- if (!$content.id) {
811
- $content.id = 'details-content-' + generateUniqueID();
922
+ // Set section attributes when opened/closed
923
+ Accordion.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);
812
931
  }
813
932
 
814
- // Add ARIA role="group" to details
815
- $module.setAttribute('role', 'group');
933
+ // See if "Open all" button text should be updated
934
+ var areAllSectionsOpen = this.checkIfAllSectionsOpen();
935
+ this.updateOpenAllButton(areAllSectionsOpen);
936
+ };
816
937
 
817
- // Add role=button to summary
818
- $summary.setAttribute('role', 'button');
938
+ // Get state of section
939
+ Accordion.prototype.isExpanded = function ($section) {
940
+ return $section.classList.contains(this.sectionExpandedClass)
941
+ };
819
942
 
820
- // Add aria-controls
821
- $summary.setAttribute('aria-controls', $content.id);
943
+ // Check if all sections are open
944
+ Accordion.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;
822
950
 
823
- // Set tabIndex so the summary is keyboard accessible for non-native elements
824
- // http://www.saliences.com/browserBugs/tabIndex.html
825
- if (!NATIVE_DETAILS) {
826
- $summary.tabIndex = 0;
827
- }
951
+ return areAllSectionsOpen
952
+ };
828
953
 
829
- // Detect initial open state
830
- var openAttr = $module.getAttribute('open') !== null;
831
- if (openAttr === true) {
832
- $summary.setAttribute('aria-expanded', 'true');
833
- $content.setAttribute('aria-hidden', 'false');
834
- } else {
835
- $summary.setAttribute('aria-expanded', 'false');
836
- $content.setAttribute('aria-hidden', 'true');
837
- if (!NATIVE_DETAILS) {
838
- $content.style.display = 'none';
954
+ // Update "Open all" button
955
+ Accordion.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.
963
+ var 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
+ }
839
976
  }
840
977
  }
841
-
842
- // Bind an event to handle summary elements
843
- this.handleInputs($summary, this.setAttributes.bind(this));
844
978
  };
845
979
 
846
- /**
847
- * Define a statechange function that updates aria-expanded and style.display
848
- * @param {object} summary element
849
- */
850
- Details.prototype.setAttributes = function () {
851
- var $module = this.$module;
852
- var $summary = this.$summary;
853
- var $content = this.$content;
980
+ // Set the state of the accordions in sessionStorage
981
+ Accordion.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);
854
987
 
855
- var expanded = $summary.getAttribute('aria-expanded') === 'true';
856
- var hidden = $content.getAttribute('aria-hidden') === 'true';
988
+ if ($button) {
989
+ var contentId = $button.getAttribute('aria-controls');
990
+ var contentState = $button.getAttribute('aria-expanded');
857
991
 
858
- $summary.setAttribute('aria-expanded', (expanded ? 'false' : 'true'));
859
- $content.setAttribute('aria-hidden', (hidden ? 'false' : 'true'));
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
+ }
860
995
 
861
- if (!NATIVE_DETAILS) {
862
- $content.style.display = (expanded ? 'none' : '');
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
+ }
863
999
 
864
- var hasOpenAttr = $module.getAttribute('open') !== null;
865
- if (!hasOpenAttr) {
866
- $module.setAttribute('open', 'open');
867
- } else {
868
- $module.removeAttribute('open');
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
+ }
869
1004
  }
870
1005
  }
871
- return true
872
1006
  };
873
1007
 
874
- /**
875
- * Remove the click event from the node element
876
- * @param {object} node element
877
- */
878
- Details.prototype.destroy = function (node) {
879
- node.removeEventListener('keypress');
880
- node.removeEventListener('keyup');
881
- node.removeEventListener('click');
1008
+ // Read the state of the accordions from sessionStorage
1009
+ Accordion.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
+ }
882
1022
  };
883
1023
 
884
1024
  (function(undefined) {
885
1025
 
886
- // Detection from https://raw.githubusercontent.com/Financial-Times/polyfill-service/master/packages/polyfill-library/polyfills/DOMTokenList/detect.js
887
- var detect = (
888
- 'DOMTokenList' in this && (function (x) {
889
- return 'classList' in x ? !x.classList.toggle('x', false) && !x.className : true;
890
- })(document.createElement('x'))
891
- );
1026
+ // Detection from https://github.com/Financial-Times/polyfill-service/blob/master/packages/polyfill-library/polyfills/Window/detect.js
1027
+ var detect = ('Window' in this);
892
1028
 
893
- if (detect) return
1029
+ if (detect) return
894
1030
 
895
- // Polyfill from https://raw.githubusercontent.com/Financial-Times/polyfill-service/master/packages/polyfill-library/polyfills/DOMTokenList/polyfill.js
896
- (function (global) {
897
- var nativeImpl = "DOMTokenList" in global && global.DOMTokenList;
1031
+ // Polyfill from https://cdn.polyfill.io/v2/polyfill.js?features=Window&flags=always
1032
+ if ((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
+ }
898
1041
 
899
- if (
900
- !nativeImpl ||
901
- (
902
- !!document.createElementNS &&
903
- !!document.createElementNS('http://www.w3.org/2000/svg', 'svg') &&
904
- !(document.createElementNS("http://www.w3.org/2000/svg", "svg").classList instanceof DOMTokenList)
905
- )
906
- ) {
907
- global.DOMTokenList = (function() { // eslint-disable-line no-unused-vars
908
- var dpSupport = true;
909
- var defineGetter = function (object, name, fn, configurable) {
910
- if (Object.defineProperty)
911
- Object.defineProperty(object, name, {
912
- configurable: false === dpSupport ? true : !!configurable,
913
- get: fn
914
- });
1042
+ })
1043
+ .call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
915
1044
 
916
- else object.__defineGetter__(name, fn);
917
- };
1045
+ (function(undefined) {
1046
+
1047
+ // Detection from https://github.com/Financial-Times/polyfill-service/blob/master/packages/polyfill-library/polyfills/Event/detect.js
1048
+ var 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
+
1065
+ if (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];
918
1193
 
919
- /** Ensure the browser allows Object.defineProperty to be used on native JavaScript objects. */
920
- try {
921
- defineGetter({}, "support");
922
- }
923
- catch (e) {
924
- dpSupport = false;
925
- }
1194
+ if (indexOf(list, eventElement) !== -1 && typeof eventElement === 'function') {
1195
+ eventElement.call(element, event);
1196
+ }
1197
+ }
1198
+ }
1199
+ };
926
1200
 
1201
+ element._events[type].list = [];
927
1202
 
928
- var _DOMTokenList = function (el, prop) {
929
- var that = this;
930
- var tokens = [];
931
- var tokenMap = {};
932
- var length = 0;
933
- var maxLength = 0;
934
- var addIndexGetter = function (i) {
935
- defineGetter(that, i, function () {
936
- preop();
937
- return tokens[i];
938
- }, false);
1203
+ if (element.attachEvent) {
1204
+ element.attachEvent('on' + type, element._events[type]);
1205
+ }
1206
+ }
939
1207
 
940
- };
941
- var reindex = function () {
1208
+ element._events[type].list.push(listener);
1209
+ };
942
1210
 
943
- /** Define getter functions for array-like access to the tokenList's contents. */
944
- if (length >= maxLength)
945
- for (; maxLength < length; ++maxLength) {
946
- addIndexGetter(maxLength);
947
- }
948
- };
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;
949
1217
 
950
- /** Helper function called at the start of each class method. Internal use only. */
951
- var preop = function () {
952
- var error;
953
- var i;
954
- var args = arguments;
955
- var rSpace = /\s+/;
1218
+ if (element._events && element._events[type] && element._events[type].list) {
1219
+ index = indexOf(element._events[type].list, listener);
956
1220
 
957
- /** Validate the token/s passed to an instance method, if any. */
958
- if (args.length)
959
- for (i = 0; i < args.length; ++i)
960
- if (rSpace.test(args[i])) {
961
- error = new SyntaxError('String "' + args[i] + '" ' + "contains" + ' an invalid character');
962
- error.code = 5;
963
- error.name = "InvalidCharacterError";
964
- throw error;
965
- }
1221
+ if (index !== -1) {
1222
+ element._events[type].list.splice(index, 1);
966
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
+ };
967
1233
 
968
- /** Split the new value apart by whitespace*/
969
- if (typeof el[prop] === "object") {
970
- tokens = ("" + el[prop].baseVal).replace(/^\s+|\s+$/g, "").split(rSpace);
971
- } else {
972
- tokens = ("" + el[prop]).replace(/^\s+|\s+$/g, "").split(rSpace);
973
- }
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
+ }
974
1238
 
975
- /** Avoid treating blank strings as single-item token lists */
976
- if ("" === tokens[0]) tokens = [];
1239
+ if (!event || typeof event.type !== 'string') {
1240
+ throw new Error('DOM Events Exception 0');
1241
+ }
977
1242
 
978
- /** Repopulate the internal token lists */
979
- tokenMap = {};
980
- for (i = 0; i < tokens.length; ++i)
981
- tokenMap[tokens[i]] = true;
982
- length = tokens.length;
983
- reindex();
984
- };
1243
+ var element = this, type = event.type;
985
1244
 
986
- /** Populate our internal token list if the targeted attribute of the subject element isn't empty. */
987
- preop();
1245
+ try {
1246
+ if (!event.bubbles) {
1247
+ event.cancelBubble = true;
988
1248
 
989
- /** Return the number of tokens in the underlying string. Read-only. */
990
- defineGetter(that, "length", function () {
991
- preop();
992
- return length;
993
- });
1249
+ var cancelBubbleEvent = function (event) {
1250
+ event.cancelBubble = true;
994
1251
 
995
- /** Override the default toString/toLocaleString methods to return a space-delimited list of tokens when typecast. */
996
- that.toLocaleString =
997
- that.toString = function () {
998
- preop();
999
- return tokens.join(" ");
1000
- };
1252
+ (element || window).detachEvent('on' + type, cancelBubbleEvent);
1253
+ };
1001
1254
 
1002
- that.item = function (idx) {
1003
- preop();
1004
- return tokens[idx];
1005
- };
1255
+ this.attachEvent('on' + type, cancelBubbleEvent);
1256
+ }
1006
1257
 
1007
- that.contains = function (token) {
1008
- preop();
1009
- return !!tokenMap[token];
1010
- };
1258
+ this.fireEvent('on' + type, event);
1259
+ } catch (error) {
1260
+ event.target = element;
1011
1261
 
1012
- that.add = function () {
1013
- preop.apply(that, args = arguments);
1262
+ do {
1263
+ event.currentTarget = element;
1014
1264
 
1015
- for (var args, token, i = 0, l = args.length; i < l; ++i) {
1016
- token = args[i];
1017
- if (!tokenMap[token]) {
1018
- tokens.push(token);
1019
- tokenMap[token] = true;
1020
- }
1021
- }
1265
+ if ('_events' in element && typeof element._events[type] === 'function') {
1266
+ element._events[type].call(element, event);
1267
+ }
1022
1268
 
1023
- /** Update the targeted attribute of the attached element if the token list's changed. */
1024
- if (length !== tokens.length) {
1025
- length = tokens.length >>> 0;
1026
- if (typeof el[prop] === "object") {
1027
- el[prop].baseVal = tokens.join(" ");
1028
- } else {
1029
- el[prop] = tokens.join(" ");
1030
- }
1031
- reindex();
1032
- }
1033
- };
1269
+ if (typeof element['on' + type] === 'function') {
1270
+ element['on' + type].call(element, event);
1271
+ }
1034
1272
 
1035
- that.remove = function () {
1036
- preop.apply(that, args = arguments);
1273
+ element = element.nodeType === 9 ? element.parentWindow : element.parentNode;
1274
+ } while (element && !event.cancelBubble);
1275
+ }
1037
1276
 
1038
- /** Build a hash of token names to compare against when recollecting our token list. */
1039
- for (var args, ignore = {}, i = 0, t = []; i < args.length; ++i) {
1040
- ignore[args[i]] = true;
1041
- delete tokenMap[args[i]];
1042
- }
1277
+ return true;
1278
+ };
1043
1279
 
1044
- /** Run through our tokens list and reassign only those that aren't defined in the hash declared above. */
1045
- for (i = 0; i < tokens.length; ++i)
1046
- if (!ignore[tokens[i]]) t.push(tokens[i]);
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
+ }());
1047
1290
 
1048
- tokens = t;
1049
- length = t.length >>> 0;
1291
+ })
1292
+ .call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
1050
1293
 
1051
- /** Update the targeted attribute of the attached element. */
1052
- if (typeof el[prop] === "object") {
1053
- el[prop].baseVal = tokens.join(" ");
1054
- } else {
1055
- el[prop] = tokens.join(" ");
1056
- }
1057
- reindex();
1058
- };
1294
+ /**
1295
+ * JavaScript 'shim' to trigger the click event of element(s) when the space key is pressed.
1296
+ *
1297
+ * Created since some Assistive Technologies (for example some Screenreaders)
1298
+ * will tell a user to press space on a 'button', so this functionality needs to be shimmed
1299
+ * See https://github.com/alphagov/govuk_elements/pull/272#issuecomment-233028270
1300
+ *
1301
+ * Usage instructions:
1302
+ * the 'shim' will be automatically initialised
1303
+ */
1059
1304
 
1060
- that.toggle = function (token, force) {
1061
- preop.apply(that, [token]);
1305
+ var KEY_SPACE = 32;
1062
1306
 
1063
- /** Token state's being forced. */
1064
- if (undefined !== force) {
1065
- if (force) {
1066
- that.add(token);
1067
- return true;
1068
- } else {
1069
- that.remove(token);
1070
- return false;
1071
- }
1072
- }
1307
+ function Button ($module) {
1308
+ this.$module = $module;
1309
+ }
1073
1310
 
1074
- /** Token already exists in tokenList. Remove it, and return FALSE. */
1075
- if (tokenMap[token]) {
1076
- that.remove(token);
1077
- return false;
1078
- }
1311
+ /**
1312
+ * Add event handler for KeyDown
1313
+ * if the event target element has a role='button' and the event is key space pressed
1314
+ * then it prevents the default event and triggers a click event
1315
+ * @param {object} event event
1316
+ */
1317
+ Button.prototype.handleKeyDown = function (event) {
1318
+ // get the target element
1319
+ var target = event.target;
1320
+ // if the element has a role='button' and the pressed key is a space, we'll simulate a click
1321
+ if (target.getAttribute('role') === 'button' && event.keyCode === KEY_SPACE) {
1322
+ event.preventDefault();
1323
+ // trigger the target's click event
1324
+ target.click();
1325
+ }
1326
+ };
1079
1327
 
1080
- /** Otherwise, add the token and return TRUE. */
1081
- that.add(token);
1082
- return true;
1083
- };
1328
+ /**
1329
+ * Initialise an event listener for keydown at document level
1330
+ * this will help listening for later inserted elements with a role="button"
1331
+ */
1332
+ Button.prototype.init = function () {
1333
+ this.$module.addEventListener('keydown', this.handleKeyDown);
1334
+ };
1084
1335
 
1085
- return that;
1086
- };
1336
+ /**
1337
+ * JavaScript 'polyfill' for HTML5's <details> and <summary> elements
1338
+ * and 'shim' to add accessiblity enhancements for all browsers
1339
+ *
1340
+ * http://caniuse.com/#feat=details
1341
+ *
1342
+ * Usage instructions:
1343
+ * the 'polyfill' will be automatically initialised
1344
+ */
1087
1345
 
1088
- return _DOMTokenList;
1089
- }());
1090
- }
1346
+ var KEY_ENTER = 13;
1347
+ var KEY_SPACE$1 = 32;
1091
1348
 
1092
- // Add second argument to native DOMTokenList.toggle() if necessary
1093
- (function () {
1094
- var e = document.createElement('span');
1095
- if (!('classList' in e)) return;
1096
- e.classList.toggle('x', false);
1097
- if (!e.classList.contains('x')) return;
1098
- e.classList.constructor.prototype.toggle = function toggle(token /*, force*/) {
1099
- var force = arguments[1];
1100
- if (force === undefined) {
1101
- var add = !this.contains(token);
1102
- this[add ? 'add' : 'remove'](token);
1103
- return add;
1104
- }
1105
- force = !!force;
1106
- this[force ? 'add' : 'remove'](token);
1107
- return force;
1108
- };
1109
- }());
1349
+ // Create a flag to know if the browser supports navtive details
1350
+ var NATIVE_DETAILS = typeof document.createElement('details').open === 'boolean';
1110
1351
 
1111
- // Add multiple arguments to native DOMTokenList.add() if necessary
1112
- (function () {
1113
- var e = document.createElement('span');
1114
- if (!('classList' in e)) return;
1115
- e.classList.add('a', 'b');
1116
- if (e.classList.contains('b')) return;
1117
- var native = e.classList.constructor.prototype.add;
1118
- e.classList.constructor.prototype.add = function () {
1119
- var args = arguments;
1120
- var l = arguments.length;
1121
- for (var i = 0; i < l; i++) {
1122
- native.call(this, args[i]);
1123
- }
1124
- };
1125
- }());
1352
+ function Details ($module) {
1353
+ this.$module = $module;
1354
+ }
1126
1355
 
1127
- // Add multiple arguments to native DOMTokenList.remove() if necessary
1128
- (function () {
1129
- var e = document.createElement('span');
1130
- if (!('classList' in e)) return;
1131
- e.classList.add('a');
1132
- e.classList.add('b');
1133
- e.classList.remove('a', 'b');
1134
- if (!e.classList.contains('b')) return;
1135
- var native = e.classList.constructor.prototype.remove;
1136
- e.classList.constructor.prototype.remove = function () {
1137
- var args = arguments;
1138
- var l = arguments.length;
1139
- for (var i = 0; i < l; i++) {
1140
- native.call(this, args[i]);
1141
- }
1142
- };
1143
- }());
1356
+ /**
1357
+ * Handle cross-modal click events
1358
+ * @param {object} node element
1359
+ * @param {function} callback function
1360
+ */
1361
+ Details.prototype.handleInputs = function (node, callback) {
1362
+ node.addEventListener('keypress', function (event) {
1363
+ var target = event.target;
1364
+ // When the key gets pressed - check if it is enter or space
1365
+ if (event.keyCode === KEY_ENTER || event.keyCode === KEY_SPACE$1) {
1366
+ if (target.nodeName.toLowerCase() === 'summary') {
1367
+ // Prevent space from scrolling the page
1368
+ // and enter from submitting a form
1369
+ event.preventDefault();
1370
+ // Click to let the click event do all the necessary action
1371
+ if (target.click) {
1372
+ target.click();
1373
+ } else {
1374
+ // except Safari 5.1 and under don't support .click() here
1375
+ callback(event);
1376
+ }
1377
+ }
1378
+ }
1379
+ });
1144
1380
 
1145
- }(this));
1381
+ // Prevent keyup to prevent clicking twice in Firefox when using space key
1382
+ node.addEventListener('keyup', function (event) {
1383
+ var target = event.target;
1384
+ if (event.keyCode === KEY_SPACE$1) {
1385
+ if (target.nodeName.toLowerCase() === 'summary') {
1386
+ event.preventDefault();
1387
+ }
1388
+ }
1389
+ });
1146
1390
 
1147
- }).call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
1391
+ node.addEventListener('click', callback);
1392
+ };
1148
1393
 
1149
- (function(undefined) {
1394
+ Details.prototype.init = function () {
1395
+ var $module = this.$module;
1150
1396
 
1151
- // Detection from https://raw.githubusercontent.com/Financial-Times/polyfill-service/8717a9e04ac7aff99b4980fbedead98036b0929a/packages/polyfill-library/polyfills/Element/prototype/classList/detect.js
1152
- var detect = (
1153
- 'document' in this && "classList" in document.documentElement && 'Element' in this && 'classList' in Element.prototype && (function () {
1154
- var e = document.createElement('span');
1155
- e.classList.add('a', 'b');
1156
- return e.classList.contains('b');
1157
- }())
1158
- );
1397
+ if (!$module) {
1398
+ return
1399
+ }
1159
1400
 
1160
- if (detect) return
1401
+ // Save shortcuts to the inner summary and content elements
1402
+ var $summary = this.$summary = $module.getElementsByTagName('summary').item(0);
1403
+ var $content = this.$content = $module.getElementsByTagName('div').item(0);
1161
1404
 
1162
- // Polyfill from https://cdn.polyfill.io/v2/polyfill.js?features=Element.prototype.classList&flags=always
1163
- (function (global) {
1164
- var dpSupport = true;
1165
- var defineGetter = function (object, name, fn, configurable) {
1166
- if (Object.defineProperty)
1167
- Object.defineProperty(object, name, {
1168
- configurable: false === dpSupport ? true : !!configurable,
1169
- get: fn
1170
- });
1405
+ // If <details> doesn't have a <summary> and a <div> representing the content
1406
+ // it means the required HTML structure is not met so the script will stop
1407
+ if (!$summary || !$content) {
1408
+ return
1409
+ }
1171
1410
 
1172
- else object.__defineGetter__(name, fn);
1173
- };
1174
- /** Ensure the browser allows Object.defineProperty to be used on native JavaScript objects. */
1175
- try {
1176
- defineGetter({}, "support");
1177
- }
1178
- catch (e) {
1179
- dpSupport = false;
1180
- }
1181
- /** Polyfills a property with a DOMTokenList */
1182
- var addProp = function (o, name, attr) {
1411
+ // If the content doesn't have an ID, assign it one now
1412
+ // which we'll need for the summary's aria-controls assignment
1413
+ if (!$content.id) {
1414
+ $content.id = 'details-content-' + generateUniqueID();
1415
+ }
1183
1416
 
1184
- defineGetter(o.prototype, name, function () {
1185
- var tokenList;
1417
+ // Add ARIA role="group" to details
1418
+ $module.setAttribute('role', 'group');
1186
1419
 
1187
- var THIS = this,
1420
+ // Add role=button to summary
1421
+ $summary.setAttribute('role', 'button');
1188
1422
 
1189
- /** Prevent this from firing twice for some reason. What the hell, IE. */
1190
- gibberishProperty = "__defineGetter__" + "DEFINE_PROPERTY" + name;
1191
- if(THIS[gibberishProperty]) return tokenList;
1192
- THIS[gibberishProperty] = true;
1423
+ // Add aria-controls
1424
+ $summary.setAttribute('aria-controls', $content.id);
1193
1425
 
1194
- /**
1195
- * IE8 can't define properties on native JavaScript objects, so we'll use a dumb hack instead.
1196
- *
1197
- * What this is doing is creating a dummy element ("reflection") inside a detached phantom node ("mirror")
1198
- * that serves as the target of Object.defineProperty instead. While we could simply use the subject HTML
1199
- * element instead, this would conflict with element types which use indexed properties (such as forms and
1200
- * select lists).
1201
- */
1202
- if (false === dpSupport) {
1426
+ // Set tabIndex so the summary is keyboard accessible for non-native elements
1427
+ // http://www.saliences.com/browserBugs/tabIndex.html
1428
+ if (!NATIVE_DETAILS) {
1429
+ $summary.tabIndex = 0;
1430
+ }
1203
1431
 
1204
- var visage;
1205
- var mirror = addProp.mirror || document.createElement("div");
1206
- var reflections = mirror.childNodes;
1207
- var l = reflections.length;
1432
+ // Detect initial open state
1433
+ var openAttr = $module.getAttribute('open') !== null;
1434
+ if (openAttr === true) {
1435
+ $summary.setAttribute('aria-expanded', 'true');
1436
+ $content.setAttribute('aria-hidden', 'false');
1437
+ } else {
1438
+ $summary.setAttribute('aria-expanded', 'false');
1439
+ $content.setAttribute('aria-hidden', 'true');
1440
+ if (!NATIVE_DETAILS) {
1441
+ $content.style.display = 'none';
1442
+ }
1443
+ }
1208
1444
 
1209
- for (var i = 0; i < l; ++i)
1210
- if (reflections[i]._R === THIS) {
1211
- visage = reflections[i];
1212
- break;
1213
- }
1445
+ // Bind an event to handle summary elements
1446
+ this.handleInputs($summary, this.setAttributes.bind(this));
1447
+ };
1214
1448
 
1215
- /** Couldn't find an element's reflection inside the mirror. Materialise one. */
1216
- visage || (visage = mirror.appendChild(document.createElement("div")));
1449
+ /**
1450
+ * Define a statechange function that updates aria-expanded and style.display
1451
+ * @param {object} summary element
1452
+ */
1453
+ Details.prototype.setAttributes = function () {
1454
+ var $module = this.$module;
1455
+ var $summary = this.$summary;
1456
+ var $content = this.$content;
1217
1457
 
1218
- tokenList = DOMTokenList.call(visage, THIS, attr);
1219
- } else tokenList = new DOMTokenList(THIS, attr);
1458
+ var expanded = $summary.getAttribute('aria-expanded') === 'true';
1459
+ var hidden = $content.getAttribute('aria-hidden') === 'true';
1220
1460
 
1221
- defineGetter(THIS, name, function () {
1222
- return tokenList;
1223
- });
1224
- delete THIS[gibberishProperty];
1461
+ $summary.setAttribute('aria-expanded', (expanded ? 'false' : 'true'));
1462
+ $content.setAttribute('aria-hidden', (hidden ? 'false' : 'true'));
1225
1463
 
1226
- return tokenList;
1227
- }, true);
1228
- };
1464
+ if (!NATIVE_DETAILS) {
1465
+ $content.style.display = (expanded ? 'none' : '');
1229
1466
 
1230
- addProp(global.Element, "classList", "className");
1231
- addProp(global.HTMLElement, "classList", "className");
1232
- addProp(global.HTMLLinkElement, "relList", "rel");
1233
- addProp(global.HTMLAnchorElement, "relList", "rel");
1234
- addProp(global.HTMLAreaElement, "relList", "rel");
1235
- }(this));
1467
+ var hasOpenAttr = $module.getAttribute('open') !== null;
1468
+ if (!hasOpenAttr) {
1469
+ $module.setAttribute('open', 'open');
1470
+ } else {
1471
+ $module.removeAttribute('open');
1472
+ }
1473
+ }
1474
+ return true
1475
+ };
1236
1476
 
1237
- }).call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
1477
+ /**
1478
+ * Remove the click event from the node element
1479
+ * @param {object} node element
1480
+ */
1481
+ Details.prototype.destroy = function (node) {
1482
+ node.removeEventListener('keypress');
1483
+ node.removeEventListener('keyup');
1484
+ node.removeEventListener('click');
1485
+ };
1238
1486
 
1239
1487
  function CharacterCount ($module) {
1240
1488
  this.$module = $module;
@@ -2021,6 +2269,12 @@ function initAll () {
2021
2269
  // Find all buttons with [role=button] on the document to enhance.
2022
2270
  new Button(document).init();
2023
2271
 
2272
+ // Find all global accordion components to enhance.
2273
+ var $accordions = document.querySelectorAll('[data-module="accordion"]');
2274
+ nodeListForEach($accordions, function ($accordion) {
2275
+ new Accordion($accordion).init();
2276
+ });
2277
+
2024
2278
  // Find all global details elements to enhance.
2025
2279
  var $details = document.querySelectorAll('details');
2026
2280
  nodeListForEach($details, function ($detail) {