govuk_tech_docs 3.2.0 → 3.3.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of govuk_tech_docs might be problematic. Click here for more details.

Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/publish.yaml +1 -1
  3. data/CHANGELOG.md +26 -7
  4. data/README.md +2 -2
  5. data/example/source/code.html.md +3 -26
  6. data/lib/govuk_tech_docs/contribution_banner.rb +1 -1
  7. data/lib/govuk_tech_docs/tech_docs_html_renderer.rb +3 -3
  8. data/lib/govuk_tech_docs/version.rb +1 -1
  9. data/lib/source/layouts/core.erb +1 -1
  10. data/node_modules/govuk-frontend/govuk/all.js +1548 -311
  11. data/node_modules/govuk-frontend/govuk/common/closest-attribute-value.js +70 -0
  12. data/node_modules/govuk-frontend/govuk/common/index.js +172 -0
  13. data/node_modules/govuk-frontend/govuk/common/normalise-dataset.js +373 -0
  14. data/node_modules/govuk-frontend/govuk/common.js +138 -3
  15. data/node_modules/govuk-frontend/govuk/components/_all.scss +1 -0
  16. data/node_modules/govuk-frontend/govuk/components/accordion/_index.scss +5 -6
  17. data/node_modules/govuk-frontend/govuk/components/accordion/accordion.js +754 -36
  18. data/node_modules/govuk-frontend/govuk/components/breadcrumbs/_index.scss +0 -2
  19. data/node_modules/govuk-frontend/govuk/components/button/_index.scss +29 -21
  20. data/node_modules/govuk-frontend/govuk/components/button/button.js +365 -107
  21. data/node_modules/govuk-frontend/govuk/components/character-count/_index.scss +9 -0
  22. data/node_modules/govuk-frontend/govuk/components/character-count/character-count.js +1092 -109
  23. data/node_modules/govuk-frontend/govuk/components/checkboxes/_index.scss +3 -2
  24. data/node_modules/govuk-frontend/govuk/components/checkboxes/checkboxes.js +30 -2
  25. data/node_modules/govuk-frontend/govuk/components/details/details.js +51 -33
  26. data/node_modules/govuk-frontend/govuk/components/error-summary/error-summary.js +289 -6
  27. data/node_modules/govuk-frontend/govuk/components/footer/_index.scss +13 -23
  28. data/node_modules/govuk-frontend/govuk/components/header/_index.scss +30 -24
  29. data/node_modules/govuk-frontend/govuk/components/header/header.js +59 -11
  30. data/node_modules/govuk-frontend/govuk/components/input/_index.scss +13 -23
  31. data/node_modules/govuk-frontend/govuk/components/notification-banner/notification-banner.js +252 -2
  32. data/node_modules/govuk-frontend/govuk/components/pagination/_index.scss +247 -0
  33. data/node_modules/govuk-frontend/govuk/components/pagination/_pagination.scss +2 -0
  34. data/node_modules/govuk-frontend/govuk/components/panel/_index.scss +1 -1
  35. data/node_modules/govuk-frontend/govuk/components/radios/_index.scss +5 -12
  36. data/node_modules/govuk-frontend/govuk/components/radios/radios.js +30 -2
  37. data/node_modules/govuk-frontend/govuk/components/select/_index.scss +11 -0
  38. data/node_modules/govuk-frontend/govuk/components/skip-link/_index.scss +1 -3
  39. data/node_modules/govuk-frontend/govuk/components/skip-link/skip-link.js +10 -4
  40. data/node_modules/govuk-frontend/govuk/components/summary-list/_index.scss +45 -13
  41. data/node_modules/govuk-frontend/govuk/components/table/_index.scss +1 -1
  42. data/node_modules/govuk-frontend/govuk/components/tabs/tabs.js +28 -0
  43. data/node_modules/govuk-frontend/govuk/core/_section-break.scss +1 -1
  44. data/node_modules/govuk-frontend/govuk/helpers/_colour.scss +5 -5
  45. data/node_modules/govuk-frontend/govuk/helpers/_focused.scss +5 -0
  46. data/node_modules/govuk-frontend/govuk/helpers/_links.scss +13 -11
  47. data/node_modules/govuk-frontend/govuk/helpers/_media-queries.scss +2 -2
  48. data/node_modules/govuk-frontend/govuk/helpers/_shape-arrow.scss +1 -1
  49. data/node_modules/govuk-frontend/govuk/helpers/_spacing.scss +3 -3
  50. data/node_modules/govuk-frontend/govuk/helpers/_typography.scss +16 -9
  51. data/node_modules/govuk-frontend/govuk/i18n.js +390 -0
  52. data/node_modules/govuk-frontend/govuk/objects/_button-group.scss +10 -26
  53. data/node_modules/govuk-frontend/govuk/objects/_template.scss +1 -1
  54. data/node_modules/govuk-frontend/govuk/objects/_width-container.scss +0 -4
  55. data/node_modules/govuk-frontend/govuk/overrides/_spacing.scss +56 -12
  56. data/node_modules/govuk-frontend/govuk/settings/_all.scss +1 -0
  57. data/node_modules/govuk-frontend/govuk/settings/_colours-palette.scss +12 -0
  58. data/node_modules/govuk-frontend/govuk/settings/_compatibility.scss +26 -0
  59. data/node_modules/govuk-frontend/govuk/settings/_spacing.scss +4 -8
  60. data/node_modules/govuk-frontend/govuk/settings/_typography-font.scss +23 -0
  61. data/node_modules/govuk-frontend/govuk/settings/_typography-responsive.scss +12 -0
  62. data/node_modules/govuk-frontend/govuk/settings/_warnings.scss +53 -0
  63. data/node_modules/govuk-frontend/govuk/tools/_compatibility.scss +20 -6
  64. data/node_modules/govuk-frontend/govuk/tools/_exports.scss +1 -1
  65. data/node_modules/govuk-frontend/govuk/tools/_font-url.scss +1 -1
  66. data/node_modules/govuk-frontend/govuk/tools/_image-url.scss +1 -1
  67. data/node_modules/govuk-frontend/govuk/tools/_px-to-em.scss +2 -2
  68. data/node_modules/govuk-frontend/govuk/tools/_px-to-rem.scss +1 -1
  69. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Date/now.js +21 -0
  70. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Element/prototype/dataset.js +300 -0
  71. data/node_modules/govuk-frontend/govuk/vendor/polyfills/String/prototype/trim.js +21 -0
  72. data/node_modules/govuk-frontend/govuk-prototype-kit/init.js +7 -0
  73. data/node_modules/govuk-frontend/govuk-prototype-kit/init.scss +12 -0
  74. data/package-lock.json +12 -12
  75. data/package.json +1 -1
  76. metadata +17 -5
@@ -128,8 +128,6 @@
128
128
  }
129
129
 
130
130
  .govuk-breadcrumbs__list {
131
- display: -webkit-box;
132
- display: -webkit-flex;
133
131
  display: -ms-flexbox;
134
132
  display: flex;
135
133
  }
@@ -1,8 +1,26 @@
1
+ ////
2
+ /// @group components/button
3
+ ////
4
+
5
+ /// Button component background colour
6
+ ///
7
+ /// @type Colour
8
+ /// @access public
9
+
10
+ $govuk-button-background-colour: govuk-colour("green", $legacy: #00823b) !default;
11
+
12
+ /// Button component text colour
13
+ ///
14
+ /// @type Colour
15
+ /// @access public
16
+
17
+ $govuk-button-text-colour: govuk-colour("white") !default;
18
+
1
19
  @include govuk-exports("govuk/component/button") {
2
- $govuk-button-colour: govuk-colour("green", $legacy: #00823b);
20
+ $govuk-button-colour: $govuk-button-background-colour;
3
21
  $govuk-button-hover-colour: govuk-shade($govuk-button-colour, 20%);
4
22
  $govuk-button-shadow-colour: govuk-shade($govuk-button-colour, 60%);
5
- $govuk-button-text-colour: govuk-colour("white");
23
+ $govuk-button-text-colour: $govuk-button-text-colour;
6
24
 
7
25
  // Secondary button variables
8
26
  $govuk-secondary-button-colour: govuk-colour("light-grey", $legacy: "grey-3");
@@ -101,7 +119,7 @@
101
119
  // we need to override the text colour for that combination of selectors so
102
120
  // so that unvisited links styled as buttons do not end up with dark blue
103
121
  // text when focussed.
104
- @include govuk-compatibility(govuk_template) {
122
+ @include _govuk-compatibility(govuk_template) {
105
123
  &:link:focus {
106
124
  color: $govuk-button-text-colour;
107
125
  }
@@ -184,7 +202,7 @@
184
202
  // we need to override the text colour for that combination of selectors so
185
203
  // so that unvisited links styled as buttons do not end up with dark blue
186
204
  // text when focussed.
187
- @include govuk-compatibility(govuk_template) {
205
+ @include _govuk-compatibility(govuk_template) {
188
206
  &:link:focus {
189
207
  color: $govuk-secondary-button-text-colour;
190
208
  }
@@ -216,7 +234,7 @@
216
234
  // we need to override the text colour for that combination of selectors so
217
235
  // so that unvisited links styled as buttons do not end up with dark blue
218
236
  // text when focussed.
219
- @include govuk-compatibility(govuk_template) {
237
+ @include _govuk-compatibility(govuk_template) {
220
238
  &:link:focus {
221
239
  color: $govuk-warning-button-text-colour;
222
240
  }
@@ -235,22 +253,14 @@
235
253
  @include govuk-typography-weight-bold;
236
254
  @include govuk-typography-responsive($size: 24, $override-line-height: 1);
237
255
 
238
- display: -webkit-inline-box;
239
-
240
- display: -webkit-inline-flex;
241
-
242
256
  display: -ms-inline-flexbox;
243
257
 
244
258
  display: inline-flex;
245
259
  min-height: auto;
246
260
 
247
- -webkit-box-pack: center;
248
-
249
- -webkit-justify-content: center;
250
-
251
- -ms-flex-pack: center;
261
+ -ms-flex-pack: center;
252
262
 
253
- justify-content: center;
263
+ justify-content: center;
254
264
  }
255
265
 
256
266
  .govuk-button__start-icon {
@@ -260,12 +270,10 @@
260
270
  margin-left: govuk-spacing(2);
261
271
  }
262
272
  vertical-align: middle;
263
- -webkit-flex-shrink: 0;
264
- -ms-flex-negative: 0;
265
- flex-shrink: 0;
266
- -webkit-align-self: center;
267
- -ms-flex-item-align: center;
268
- align-self: center;
273
+ -ms-flex-negative: 0;
274
+ flex-shrink: 0;
275
+ -ms-flex-item-align: center;
276
+ align-self: center;
269
277
  // Work around SVGs not inheriting color from parent in forced color mode
270
278
  // (https://github.com/w3c/csswg-drafts/issues/6310)
271
279
  forced-color-adjust: auto;
@@ -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
 
@@ -17,6 +17,15 @@
17
17
  @include govuk-font($size: false, $tabular: true);
18
18
  margin-top: 0;
19
19
  margin-bottom: 0;
20
+
21
+ &:after {
22
+ // Zero-width space that will reserve vertical space when no hint is provided
23
+ // as:
24
+ // - setting a min-height is not possible without a magic number
25
+ // because the line-height is set by the `govuk-font` call above
26
+ // - using `:empty` is not possible as the hint macro outputs line breaks
27
+ content: "\200B";
28
+ }
20
29
  }
21
30
 
22
31
  .govuk-character-count__message--disabled {