govuk_publishing_components 32.1.0 → 33.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (145) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/component_guide/accessibility-test.js +0 -1
  3. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-core.js +175 -0
  4. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-ecommerce-tracker.js +4 -4
  5. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-event-tracker.js +5 -13
  6. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-link-tracker.js +80 -309
  7. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-page-views.js +2 -2
  8. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-specialist-link-tracker.js +140 -0
  9. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/init-ga4.js +3 -0
  10. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4.js +1 -0
  11. data/app/assets/javascripts/govuk_publishing_components/components/accordion.js +12 -1
  12. data/app/assets/javascripts/govuk_publishing_components/components/single-page-notification-button.js +24 -8
  13. data/app/assets/javascripts/govuk_publishing_components/components/step-by-step-nav.js +22 -1
  14. data/app/assets/javascripts/govuk_publishing_components/vendor/lux/lux-reporter.js +140 -191
  15. data/app/assets/stylesheets/govuk_publishing_components/components/_big-number.scss +2 -5
  16. data/app/assets/stylesheets/govuk_publishing_components/components/_image-card.scss +1 -5
  17. data/app/assets/stylesheets/govuk_publishing_components/components/_input.scss +3 -5
  18. data/app/assets/stylesheets/govuk_publishing_components/components/_layout-super-navigation-header.scss +10 -30
  19. data/app/assets/stylesheets/govuk_publishing_components/components/_search.scss +0 -7
  20. data/app/views/govuk_publishing_components/components/_accordion.html.erb +14 -1
  21. data/app/views/govuk_publishing_components/components/_error_summary.html.erb +27 -26
  22. data/app/views/govuk_publishing_components/components/_layout_super_navigation_header.html.erb +2 -2
  23. data/app/views/govuk_publishing_components/components/_phase_banner.html.erb +1 -1
  24. data/app/views/govuk_publishing_components/components/_share_links.html.erb +11 -13
  25. data/app/views/govuk_publishing_components/components/_single_page_notification_button.html.erb +1 -1
  26. data/app/views/govuk_publishing_components/components/_step_by_step_nav.html.erb +4 -1
  27. data/app/views/govuk_publishing_components/components/docs/accordion.yml +15 -3
  28. data/app/views/govuk_publishing_components/components/docs/button.yml +10 -0
  29. data/app/views/govuk_publishing_components/components/docs/share_links.yml +59 -30
  30. data/app/views/govuk_publishing_components/components/docs/single_page_notification_button.yml +10 -1
  31. data/app/views/govuk_publishing_components/components/docs/step_by_step_nav.yml +34 -0
  32. data/app/views/govuk_publishing_components/components/feedback/_problem_form.html.erb +1 -1
  33. data/app/views/govuk_publishing_components/components/feedback/_survey_signup_form.html.erb +1 -1
  34. data/app/views/govuk_publishing_components/components/feedback/_yes_no_banner.html.erb +3 -3
  35. data/lib/govuk_publishing_components/presenters/button_helper.rb +9 -2
  36. data/lib/govuk_publishing_components/presenters/single_page_notification_button_helper.rb +25 -1
  37. data/lib/govuk_publishing_components/version.rb +1 -1
  38. data/node_modules/axe-core/axe.js +4559 -4673
  39. data/node_modules/axe-core/axe.min.js +2 -2
  40. data/node_modules/axe-core/package.json +2 -2
  41. data/node_modules/axe-core/sri-history.json +4 -0
  42. data/node_modules/govuk-frontend/README.md +1 -2
  43. data/node_modules/govuk-frontend/govuk/all.js +1398 -273
  44. data/node_modules/govuk-frontend/govuk/common/closest-attribute-value.js +70 -0
  45. data/node_modules/govuk-frontend/govuk/common/index.js +172 -0
  46. data/node_modules/govuk-frontend/govuk/common/normalise-dataset.js +373 -0
  47. data/node_modules/govuk-frontend/govuk/common.js +138 -3
  48. data/node_modules/govuk-frontend/govuk/components/accordion/accordion.js +753 -25
  49. data/node_modules/govuk-frontend/govuk/components/accordion/fixtures.json +54 -22
  50. data/node_modules/govuk-frontend/govuk/components/accordion/macro-options.json +36 -0
  51. data/node_modules/govuk-frontend/govuk/components/accordion/template.njk +7 -1
  52. data/node_modules/govuk-frontend/govuk/components/back-link/fixtures.json +12 -12
  53. data/node_modules/govuk-frontend/govuk/components/breadcrumbs/fixtures.json +22 -22
  54. data/node_modules/govuk-frontend/govuk/components/button/_index.scss +23 -5
  55. data/node_modules/govuk-frontend/govuk/components/button/button.js +365 -107
  56. data/node_modules/govuk-frontend/govuk/components/button/fixtures.json +85 -66
  57. data/node_modules/govuk-frontend/govuk/components/button/template.njk +1 -1
  58. data/node_modules/govuk-frontend/govuk/components/character-count/_index.scss +9 -0
  59. data/node_modules/govuk-frontend/govuk/components/character-count/character-count.js +1033 -121
  60. data/node_modules/govuk-frontend/govuk/components/character-count/fixtures.json +112 -36
  61. data/node_modules/govuk-frontend/govuk/components/character-count/macro-options.json +42 -0
  62. data/node_modules/govuk-frontend/govuk/components/character-count/template.njk +27 -3
  63. data/node_modules/govuk-frontend/govuk/components/checkboxes/checkboxes.js +30 -2
  64. data/node_modules/govuk-frontend/govuk/components/checkboxes/fixtures.json +96 -93
  65. data/node_modules/govuk-frontend/govuk/components/cookie-banner/fixtures.json +46 -46
  66. data/node_modules/govuk-frontend/govuk/components/date-input/fixtures.json +50 -50
  67. data/node_modules/govuk-frontend/govuk/components/details/details.js +43 -13
  68. data/node_modules/govuk-frontend/govuk/components/details/fixtures.json +20 -20
  69. data/node_modules/govuk-frontend/govuk/components/error-message/fixtures.json +20 -20
  70. data/node_modules/govuk-frontend/govuk/components/error-summary/error-summary.js +268 -6
  71. data/node_modules/govuk-frontend/govuk/components/error-summary/fixtures.json +44 -35
  72. data/node_modules/govuk-frontend/govuk/components/error-summary/template.njk +25 -21
  73. data/node_modules/govuk-frontend/govuk/components/fieldset/fixtures.json +51 -39
  74. data/node_modules/govuk-frontend/govuk/components/file-upload/fixtures.json +26 -26
  75. data/node_modules/govuk-frontend/govuk/components/footer/_index.scss +1 -1
  76. data/node_modules/govuk-frontend/govuk/components/footer/fixtures.json +46 -46
  77. data/node_modules/govuk-frontend/govuk/components/footer/macro-options.json +2 -2
  78. data/node_modules/govuk-frontend/govuk/components/header/fixtures.json +93 -38
  79. data/node_modules/govuk-frontend/govuk/components/header/header.js +6 -0
  80. data/node_modules/govuk-frontend/govuk/components/header/macro-options.json +8 -2
  81. data/node_modules/govuk-frontend/govuk/components/header/template.njk +4 -2
  82. data/node_modules/govuk-frontend/govuk/components/hint/fixtures.json +12 -12
  83. data/node_modules/govuk-frontend/govuk/components/input/fixtures.json +80 -80
  84. data/node_modules/govuk-frontend/govuk/components/inset-text/fixtures.json +12 -12
  85. data/node_modules/govuk-frontend/govuk/components/label/fixtures.json +34 -34
  86. data/node_modules/govuk-frontend/govuk/components/notification-banner/fixtures.json +56 -46
  87. data/node_modules/govuk-frontend/govuk/components/notification-banner/notification-banner.js +252 -2
  88. data/node_modules/govuk-frontend/govuk/components/notification-banner/template.njk +1 -1
  89. data/node_modules/govuk-frontend/govuk/components/pagination/_index.scss +10 -7
  90. data/node_modules/govuk-frontend/govuk/components/pagination/fixtures.json +33 -26
  91. data/node_modules/govuk-frontend/govuk/components/panel/fixtures.json +18 -18
  92. data/node_modules/govuk-frontend/govuk/components/phase-banner/fixtures.json +14 -14
  93. data/node_modules/govuk-frontend/govuk/components/radios/fixtures.json +94 -91
  94. data/node_modules/govuk-frontend/govuk/components/radios/radios.js +30 -2
  95. data/node_modules/govuk-frontend/govuk/components/select/fixtures.json +32 -32
  96. data/node_modules/govuk-frontend/govuk/components/skip-link/fixtures.json +22 -20
  97. data/node_modules/govuk-frontend/govuk/components/skip-link/skip-link.js +10 -4
  98. data/node_modules/govuk-frontend/govuk/components/summary-list/fixtures.json +50 -50
  99. data/node_modules/govuk-frontend/govuk/components/table/_index.scss +1 -1
  100. data/node_modules/govuk-frontend/govuk/components/table/fixtures.json +40 -40
  101. data/node_modules/govuk-frontend/govuk/components/tabs/fixtures.json +29 -29
  102. data/node_modules/govuk-frontend/govuk/components/tabs/tabs.js +28 -0
  103. data/node_modules/govuk-frontend/govuk/components/tag/fixtures.json +28 -28
  104. data/node_modules/govuk-frontend/govuk/components/textarea/fixtures.json +34 -34
  105. data/node_modules/govuk-frontend/govuk/components/warning-text/fixtures.json +14 -14
  106. data/node_modules/govuk-frontend/govuk/core/_section-break.scss +1 -1
  107. data/node_modules/govuk-frontend/govuk/helpers/_colour.scss +2 -2
  108. data/node_modules/govuk-frontend/govuk/helpers/_links.scss +6 -6
  109. data/node_modules/govuk-frontend/govuk/i18n.js +390 -0
  110. data/node_modules/govuk-frontend/govuk/macros/i18n.njk +15 -0
  111. data/node_modules/govuk-frontend/govuk/settings/_all.scss +1 -0
  112. data/node_modules/govuk-frontend/govuk/settings/_colours-palette.scss +12 -0
  113. data/node_modules/govuk-frontend/govuk/settings/_compatibility.scss +26 -0
  114. data/node_modules/govuk-frontend/govuk/settings/_typography-font.scss +23 -0
  115. data/node_modules/govuk-frontend/govuk/settings/_typography-responsive.scss +12 -0
  116. data/node_modules/govuk-frontend/govuk/settings/_warnings.scss +53 -0
  117. data/node_modules/govuk-frontend/govuk/tools/_compatibility.scss +20 -6
  118. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Date/now.js +21 -0
  119. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Element/prototype/dataset.js +300 -0
  120. data/node_modules/govuk-frontend/govuk/vendor/polyfills/String/prototype/trim.js +21 -0
  121. data/node_modules/govuk-frontend/govuk-esm/all.mjs +50 -27
  122. data/node_modules/govuk-frontend/govuk-esm/common/closest-attribute-value.mjs +15 -0
  123. data/node_modules/govuk-frontend/govuk-esm/common/index.mjs +159 -0
  124. data/node_modules/govuk-frontend/govuk-esm/common/normalise-dataset.mjs +58 -0
  125. data/node_modules/govuk-frontend/govuk-esm/common.mjs +6 -28
  126. data/node_modules/govuk-frontend/govuk-esm/components/accordion/accordion.mjs +113 -43
  127. data/node_modules/govuk-frontend/govuk-esm/components/button/button.mjs +67 -30
  128. data/node_modules/govuk-frontend/govuk-esm/components/character-count/character-count.mjs +325 -123
  129. data/node_modules/govuk-frontend/govuk-esm/components/checkboxes/checkboxes.mjs +9 -3
  130. data/node_modules/govuk-frontend/govuk-esm/components/details/details.mjs +22 -8
  131. data/node_modules/govuk-frontend/govuk-esm/components/error-summary/error-summary.mjs +48 -6
  132. data/node_modules/govuk-frontend/govuk-esm/components/header/header.mjs +6 -0
  133. data/node_modules/govuk-frontend/govuk-esm/components/notification-banner/notification-banner.mjs +32 -2
  134. data/node_modules/govuk-frontend/govuk-esm/components/radios/radios.mjs +9 -3
  135. data/node_modules/govuk-frontend/govuk-esm/components/skip-link/skip-link.mjs +10 -4
  136. data/node_modules/govuk-frontend/govuk-esm/components/tabs/tabs.mjs +8 -2
  137. data/node_modules/govuk-frontend/govuk-esm/i18n.mjs +380 -0
  138. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Date/now.mjs +13 -0
  139. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Element/prototype/dataset.mjs +68 -0
  140. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/String/prototype/trim.mjs +13 -0
  141. data/node_modules/govuk-frontend/govuk-prototype-kit/init.js +7 -0
  142. data/node_modules/govuk-frontend/govuk-prototype-kit/init.scss +12 -0
  143. data/node_modules/govuk-frontend/govuk-prototype-kit.config.json +138 -7
  144. data/node_modules/govuk-frontend/package.json +1 -1
  145. metadata +22 -3
@@ -4,24 +4,177 @@
4
4
  (global.GOVUKFrontend = global.GOVUKFrontend || {}, global.GOVUKFrontend.Button = factory());
5
5
  }(this, (function () { 'use strict';
6
6
 
7
+ /**
8
+ * Common helpers which do not require polyfill.
9
+ *
10
+ * IMPORTANT: If a helper require a polyfill, please isolate it in its own module
11
+ * so that the polyfill can be properly tree-shaken and does not burden
12
+ * the components that do not need that helper
13
+ *
14
+ * @module common/index
15
+ */
16
+
17
+ /**
18
+ * Config flattening function
19
+ *
20
+ * Takes any number of objects, flattens them into namespaced key-value pairs,
21
+ * (e.g. {'i18n.showSection': 'Show section'}) and combines them together, with
22
+ * greatest priority on the LAST item passed in.
23
+ *
24
+ * @returns {object} A flattened object of key-value pairs.
25
+ */
26
+ function mergeConfigs (/* configObject1, configObject2, ...configObjects */) {
27
+ /**
28
+ * Function to take nested objects and flatten them to a dot-separated keyed
29
+ * object. Doing this means we don't need to do any deep/recursive merging of
30
+ * each of our objects, nor transform our dataset from a flat list into a
31
+ * nested object.
32
+ *
33
+ * @param {object} configObject - Deeply nested object
34
+ * @returns {object} Flattened object with dot-separated keys
35
+ */
36
+ var flattenObject = function (configObject) {
37
+ // Prepare an empty return object
38
+ var flattenedObject = {};
39
+
40
+ // Our flattening function, this is called recursively for each level of
41
+ // depth in the object. At each level we prepend the previous level names to
42
+ // the key using `prefix`.
43
+ var flattenLoop = function (obj, prefix) {
44
+ // Loop through keys...
45
+ for (var key in obj) {
46
+ // Check to see if this is a prototypical key/value,
47
+ // if it is, skip it.
48
+ if (!Object.prototype.hasOwnProperty.call(obj, key)) {
49
+ continue
50
+ }
51
+ var value = obj[key];
52
+ var prefixedKey = prefix ? prefix + '.' + key : key;
53
+ if (typeof value === 'object') {
54
+ // If the value is a nested object, recurse over that too
55
+ flattenLoop(value, prefixedKey);
56
+ } else {
57
+ // Otherwise, add this value to our return object
58
+ flattenedObject[prefixedKey] = value;
59
+ }
60
+ }
61
+ };
62
+
63
+ // Kick off the recursive loop
64
+ flattenLoop(configObject);
65
+ return flattenedObject
66
+ };
67
+
68
+ // Start with an empty object as our base
69
+ var formattedConfigObject = {};
70
+
71
+ // Loop through each of the remaining passed objects and push their keys
72
+ // one-by-one into configObject. Any duplicate keys will override the existing
73
+ // key with the new value.
74
+ for (var i = 0; i < arguments.length; i++) {
75
+ var obj = flattenObject(arguments[i]);
76
+ for (var key in obj) {
77
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
78
+ formattedConfigObject[key] = obj[key];
79
+ }
80
+ }
81
+ }
82
+
83
+ return formattedConfigObject
84
+ }
85
+
86
+ /**
87
+ * @callback nodeListIterator
88
+ * @param {Element} value - The current node being iterated on
89
+ * @param {number} index - The current index in the iteration
90
+ * @param {NodeListOf<Element>} nodes - NodeList from querySelectorAll()
91
+ * @returns {undefined}
92
+ */
93
+
7
94
  (function(undefined) {
8
95
 
9
- // Detection from https://github.com/Financial-Times/polyfill-service/blob/master/packages/polyfill-library/polyfills/Window/detect.js
10
- var detect = ('Window' in this);
96
+ // Detection from https://github.com/Financial-Times/polyfill-service/blob/master/packages/polyfill-library/polyfills/Object/defineProperty/detect.js
97
+ var detect = (
98
+ // In IE8, defineProperty could only act on DOM elements, so full support
99
+ // for the feature requires the ability to set a property on an arbitrary object
100
+ 'defineProperty' in Object && (function() {
101
+ try {
102
+ var a = {};
103
+ Object.defineProperty(a, 'test', {value:42});
104
+ return true;
105
+ } catch(e) {
106
+ return false
107
+ }
108
+ }())
109
+ );
11
110
 
12
111
  if (detect) return
13
112
 
14
- // Polyfill from https://cdn.polyfill.io/v2/polyfill.js?features=Window&flags=always
15
- if ((typeof WorkerGlobalScope === "undefined") && (typeof importScripts !== "function")) {
16
- (function (global) {
17
- if (global.constructor) {
18
- global.Window = global.constructor;
113
+ // Polyfill from https://cdn.polyfill.io/v2/polyfill.js?features=Object.defineProperty&flags=always
114
+ (function (nativeDefineProperty) {
115
+
116
+ var supportsAccessors = Object.prototype.hasOwnProperty('__defineGetter__');
117
+ var ERR_ACCESSORS_NOT_SUPPORTED = 'Getters & setters cannot be defined on this javascript engine';
118
+ var ERR_VALUE_ACCESSORS = 'A property cannot both have accessors and be writable or have a value';
119
+
120
+ Object.defineProperty = function defineProperty(object, property, descriptor) {
121
+
122
+ // Where native support exists, assume it
123
+ if (nativeDefineProperty && (object === window || object === document || object === Element.prototype || object instanceof Element)) {
124
+ return nativeDefineProperty(object, property, descriptor);
125
+ }
126
+
127
+ if (object === null || !(object instanceof Object || typeof object === 'object')) {
128
+ throw new TypeError('Object.defineProperty called on non-object');
129
+ }
130
+
131
+ if (!(descriptor instanceof Object)) {
132
+ throw new TypeError('Property description must be an object');
133
+ }
134
+
135
+ var propertyString = String(property);
136
+ var hasValueOrWritable = 'value' in descriptor || 'writable' in descriptor;
137
+ var getterType = 'get' in descriptor && typeof descriptor.get;
138
+ var setterType = 'set' in descriptor && typeof descriptor.set;
139
+
140
+ // handle descriptor.get
141
+ if (getterType) {
142
+ if (getterType !== 'function') {
143
+ throw new TypeError('Getter must be a function');
144
+ }
145
+ if (!supportsAccessors) {
146
+ throw new TypeError(ERR_ACCESSORS_NOT_SUPPORTED);
147
+ }
148
+ if (hasValueOrWritable) {
149
+ throw new TypeError(ERR_VALUE_ACCESSORS);
150
+ }
151
+ Object.__defineGetter__.call(object, propertyString, descriptor.get);
19
152
  } else {
20
- (global.Window = global.constructor = new Function('return function Window() {}')()).prototype = this;
153
+ object[propertyString] = descriptor.value;
21
154
  }
22
- }(this));
23
- }
24
155
 
156
+ // handle descriptor.set
157
+ if (setterType) {
158
+ if (setterType !== 'function') {
159
+ throw new TypeError('Setter must be a function');
160
+ }
161
+ if (!supportsAccessors) {
162
+ throw new TypeError(ERR_ACCESSORS_NOT_SUPPORTED);
163
+ }
164
+ if (hasValueOrWritable) {
165
+ throw new TypeError(ERR_VALUE_ACCESSORS);
166
+ }
167
+ Object.__defineSetter__.call(object, propertyString, descriptor.set);
168
+ }
169
+
170
+ // OK to define value unconditionally - if a getter has been specified as well, an error would be thrown above
171
+ if ('value' in descriptor) {
172
+ object[propertyString] = descriptor.value;
173
+ }
174
+
175
+ return object;
176
+ };
177
+ }(Object.defineProperty));
25
178
  })
26
179
  .call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
27
180
 
@@ -167,88 +320,158 @@ if (detect) return
167
320
 
168
321
  (function(undefined) {
169
322
 
170
- // Detection from https://github.com/Financial-Times/polyfill-service/blob/master/packages/polyfill-library/polyfills/Object/defineProperty/detect.js
171
- var detect = (
172
- // In IE8, defineProperty could only act on DOM elements, so full support
173
- // for the feature requires the ability to set a property on an arbitrary object
174
- 'defineProperty' in Object && (function() {
175
- try {
176
- var a = {};
177
- Object.defineProperty(a, 'test', {value:42});
178
- return true;
179
- } catch(e) {
180
- return false
181
- }
182
- }())
183
- );
323
+ // Detection from https://raw.githubusercontent.com/Financial-Times/polyfill-library/13cf7c340974d128d557580b5e2dafcd1b1192d1/polyfills/Element/prototype/dataset/detect.js
324
+ var detect = (function(){
325
+ if (!document.documentElement.dataset) {
326
+ return false;
327
+ }
328
+ var el = document.createElement('div');
329
+ el.setAttribute("data-a-b", "c");
330
+ return el.dataset && el.dataset.aB == "c";
331
+ }());
184
332
 
185
- if (detect) return
333
+ if (detect) return
186
334
 
187
- // Polyfill from https://cdn.polyfill.io/v2/polyfill.js?features=Object.defineProperty&flags=always
188
- (function (nativeDefineProperty) {
335
+ // Polyfill derived from https://raw.githubusercontent.com/Financial-Times/polyfill-library/13cf7c340974d128d557580b5e2dafcd1b1192d1/polyfills/Element/prototype/dataset/polyfill.js
336
+ Object.defineProperty(Element.prototype, 'dataset', {
337
+ get: function() {
338
+ var element = this;
339
+ var attributes = this.attributes;
340
+ var map = {};
341
+
342
+ for (var i = 0; i < attributes.length; i++) {
343
+ var attribute = attributes[i];
344
+
345
+ // This regex has been edited from the original polyfill, to add
346
+ // support for period (.) separators in data-* attribute names. These
347
+ // are allowed in the HTML spec, but were not covered by the original
348
+ // polyfill's regex. We use periods in our i18n implementation.
349
+ if (attribute && attribute.name && (/^data-\w[.\w-]*$/).test(attribute.name)) {
350
+ var name = attribute.name;
351
+ var value = attribute.value;
352
+
353
+ var propName = name.substr(5).replace(/-./g, function (prop) {
354
+ return prop.charAt(1).toUpperCase();
355
+ });
356
+
357
+ // If this browser supports __defineGetter__ and __defineSetter__,
358
+ // continue using defineProperty. If not (like IE 8 and below), we use
359
+ // a hacky fallback which at least gives an object in the right format
360
+ if ('__defineGetter__' in Object.prototype && '__defineSetter__' in Object.prototype) {
361
+ Object.defineProperty(map, propName, {
362
+ enumerable: true,
363
+ get: function() {
364
+ return this.value;
365
+ }.bind({value: value || ''}),
366
+ set: function setter(name, value) {
367
+ if (typeof value !== 'undefined') {
368
+ this.setAttribute(name, value);
369
+ } else {
370
+ this.removeAttribute(name);
371
+ }
372
+ }.bind(element, name)
373
+ });
374
+ } else {
375
+ map[propName] = value;
376
+ }
189
377
 
190
- var supportsAccessors = Object.prototype.hasOwnProperty('__defineGetter__');
191
- var ERR_ACCESSORS_NOT_SUPPORTED = 'Getters & setters cannot be defined on this javascript engine';
192
- var ERR_VALUE_ACCESSORS = 'A property cannot both have accessors and be writable or have a value';
378
+ }
379
+ }
380
+
381
+ return map;
382
+ }
383
+ });
193
384
 
194
- Object.defineProperty = function defineProperty(object, property, descriptor) {
385
+ }).call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
195
386
 
196
- // Where native support exists, assume it
197
- if (nativeDefineProperty && (object === window || object === document || object === Element.prototype || object instanceof Element)) {
198
- return nativeDefineProperty(object, property, descriptor);
199
- }
387
+ (function(undefined) {
200
388
 
201
- if (object === null || !(object instanceof Object || typeof object === 'object')) {
202
- throw new TypeError('Object.defineProperty called on non-object');
203
- }
389
+ // Detection from https://github.com/mdn/content/blob/cf607d68522cd35ee7670782d3ee3a361eaef2e4/files/en-us/web/javascript/reference/global_objects/string/trim/index.md#polyfill
390
+ var detect = ('trim' in String.prototype);
391
+
392
+ if (detect) return
204
393
 
205
- if (!(descriptor instanceof Object)) {
206
- throw new TypeError('Property description must be an object');
207
- }
394
+ // Polyfill from https://github.com/mdn/content/blob/cf607d68522cd35ee7670782d3ee3a361eaef2e4/files/en-us/web/javascript/reference/global_objects/string/trim/index.md#polyfill
395
+ String.prototype.trim = function () {
396
+ return this.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
397
+ };
208
398
 
209
- var propertyString = String(property);
210
- var hasValueOrWritable = 'value' in descriptor || 'writable' in descriptor;
211
- var getterType = 'get' in descriptor && typeof descriptor.get;
212
- var setterType = 'set' in descriptor && typeof descriptor.set;
399
+ }).call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
213
400
 
214
- // handle descriptor.get
215
- if (getterType) {
216
- if (getterType !== 'function') {
217
- throw new TypeError('Getter must be a function');
218
- }
219
- if (!supportsAccessors) {
220
- throw new TypeError(ERR_ACCESSORS_NOT_SUPPORTED);
221
- }
222
- if (hasValueOrWritable) {
223
- throw new TypeError(ERR_VALUE_ACCESSORS);
224
- }
225
- Object.__defineGetter__.call(object, propertyString, descriptor.get);
226
- } else {
227
- object[propertyString] = descriptor.value;
228
- }
401
+ /**
402
+ * Normalise string
403
+ *
404
+ * 'If it looks like a duck, and it quacks like a duck…' 🦆
405
+ *
406
+ * If the passed value looks like a boolean or a number, convert it to a boolean
407
+ * or number.
408
+ *
409
+ * Designed to be used to convert config passed via data attributes (which are
410
+ * always strings) into something sensible.
411
+ *
412
+ * @param {string} value - The value to normalise
413
+ * @returns {string | boolean | number | undefined} Normalised data
414
+ */
415
+ function normaliseString (value) {
416
+ if (typeof value !== 'string') {
417
+ return value
418
+ }
229
419
 
230
- // handle descriptor.set
231
- if (setterType) {
232
- if (setterType !== 'function') {
233
- throw new TypeError('Setter must be a function');
234
- }
235
- if (!supportsAccessors) {
236
- throw new TypeError(ERR_ACCESSORS_NOT_SUPPORTED);
237
- }
238
- if (hasValueOrWritable) {
239
- throw new TypeError(ERR_VALUE_ACCESSORS);
240
- }
241
- Object.__defineSetter__.call(object, propertyString, descriptor.set);
242
- }
420
+ var trimmedValue = value.trim();
243
421
 
244
- // OK to define value unconditionally - if a getter has been specified as well, an error would be thrown above
245
- if ('value' in descriptor) {
246
- object[propertyString] = descriptor.value;
422
+ if (trimmedValue === 'true') {
423
+ return true
424
+ }
425
+
426
+ if (trimmedValue === 'false') {
427
+ return false
428
+ }
429
+
430
+ // Empty / whitespace-only strings are considered finite so we need to check
431
+ // the length of the trimmed string as well
432
+ if (trimmedValue.length > 0 && isFinite(trimmedValue)) {
433
+ return Number(trimmedValue)
434
+ }
435
+
436
+ return value
437
+ }
438
+
439
+ /**
440
+ * Normalise dataset
441
+ *
442
+ * Loop over an object and normalise each value using normaliseData function
443
+ *
444
+ * @param {DOMStringMap} dataset - HTML element dataset
445
+ * @returns {Object<string, string | boolean | number | undefined>} Normalised dataset
446
+ */
447
+ function normaliseDataset (dataset) {
448
+ var out = {};
449
+
450
+ for (var key in dataset) {
451
+ out[key] = normaliseString(dataset[key]);
452
+ }
453
+
454
+ return out
455
+ }
456
+
457
+ (function(undefined) {
458
+
459
+ // Detection from https://github.com/Financial-Times/polyfill-service/blob/master/packages/polyfill-library/polyfills/Window/detect.js
460
+ var detect = ('Window' in this);
461
+
462
+ if (detect) return
463
+
464
+ // Polyfill from https://cdn.polyfill.io/v2/polyfill.js?features=Window&flags=always
465
+ if ((typeof WorkerGlobalScope === "undefined") && (typeof importScripts !== "function")) {
466
+ (function (global) {
467
+ if (global.constructor) {
468
+ global.Window = global.constructor;
469
+ } else {
470
+ (global.Window = global.constructor = new Function('return function Window() {}')()).prototype = this;
247
471
  }
472
+ }(this));
473
+ }
248
474
 
249
- return object;
250
- };
251
- }(Object.defineProperty));
252
475
  })
253
476
  .call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
254
477
 
@@ -662,44 +885,79 @@ if (detect) return
662
885
  var KEY_SPACE = 32;
663
886
  var DEBOUNCE_TIMEOUT_IN_SECONDS = 1;
664
887
 
665
- function Button ($module) {
888
+ /**
889
+ * JavaScript enhancements for the Button component
890
+ *
891
+ * @class
892
+ * @param {HTMLElement} $module - The element this component controls
893
+ * @param {ButtonConfig} config - Button config
894
+ */
895
+ function Button ($module, config) {
896
+ if (!$module) {
897
+ return this
898
+ }
899
+
666
900
  this.$module = $module;
667
901
  this.debounceFormSubmitTimer = null;
902
+
903
+ var defaultConfig = {
904
+ preventDoubleClick: false
905
+ };
906
+ this.config = mergeConfigs(
907
+ defaultConfig,
908
+ config || {},
909
+ normaliseDataset($module.dataset)
910
+ );
668
911
  }
669
912
 
670
913
  /**
671
- * JavaScript 'shim' to trigger the click event of element(s) when the space key is pressed.
672
- *
673
- * Created since some Assistive Technologies (for example some Screenreaders)
674
- * will tell a user to press space on a 'button', so this functionality needs to be shimmed
675
- * See https://github.com/alphagov/govuk_elements/pull/272#issuecomment-233028270
676
- *
677
- * @param {object} event event
678
- */
914
+ * Initialise component
915
+ */
916
+ Button.prototype.init = function () {
917
+ if (!this.$module) {
918
+ return
919
+ }
920
+
921
+ this.$module.addEventListener('keydown', this.handleKeyDown);
922
+ this.$module.addEventListener('click', this.debounce.bind(this));
923
+ };
924
+
925
+ /**
926
+ * Trigger a click event when the space key is pressed
927
+ *
928
+ * Some screen readers tell users they can activate things with the 'button'
929
+ * role, so we need to match the functionality of native HTML buttons
930
+ *
931
+ * See https://github.com/alphagov/govuk_elements/pull/272#issuecomment-233028270
932
+ *
933
+ * @param {KeyboardEvent} event
934
+ */
679
935
  Button.prototype.handleKeyDown = function (event) {
680
- // get the target element
681
936
  var target = event.target;
682
- // if the element has a role='button' and the pressed key is a space, we'll simulate a click
937
+
683
938
  if (target.getAttribute('role') === 'button' && event.keyCode === KEY_SPACE) {
684
- event.preventDefault();
685
- // trigger the target's click event
939
+ event.preventDefault(); // prevent the page from scrolling
686
940
  target.click();
687
941
  }
688
942
  };
689
943
 
690
944
  /**
691
- * If the click quickly succeeds a previous click then nothing will happen.
692
- * This stops people accidentally causing multiple form submissions by
693
- * double clicking buttons.
694
- */
945
+ * Debounce double-clicks
946
+ *
947
+ * If the click quickly succeeds a previous click then nothing will happen. This
948
+ * stops people accidentally causing multiple form submissions by double
949
+ * clicking buttons.
950
+ *
951
+ * @param {MouseEvent} event
952
+ * @returns {undefined | false} - Returns undefined, or false when debounced
953
+ */
695
954
  Button.prototype.debounce = function (event) {
696
- var target = event.target;
697
- // Check the button that is clicked on has the preventDoubleClick feature enabled
698
- if (target.getAttribute('data-prevent-double-click') !== 'true') {
955
+ // Check the button that was clicked has preventDoubleClick enabled
956
+ if (!this.config.preventDoubleClick) {
699
957
  return
700
958
  }
701
959
 
702
- // If the timer is still running then we want to prevent the click from submitting the form
960
+ // If the timer is still running, prevent the click from submitting the form
703
961
  if (this.debounceFormSubmitTimer) {
704
962
  event.preventDefault();
705
963
  return false
@@ -711,13 +969,13 @@ Button.prototype.debounce = function (event) {
711
969
  };
712
970
 
713
971
  /**
714
- * Initialise an event listener for keydown at document level
715
- * this will help listening for later inserted elements with a role="button"
716
- */
717
- Button.prototype.init = function () {
718
- this.$module.addEventListener('keydown', this.handleKeyDown);
719
- this.$module.addEventListener('click', this.debounce);
720
- };
972
+ * Button config
973
+ *
974
+ * @typedef {object} ButtonConfig
975
+ * @property {boolean} [preventDoubleClick = false] -
976
+ * Prevent accidental double clicks on submit buttons from submitting forms
977
+ * multiple times.
978
+ */
721
979
 
722
980
  return Button;
723
981