govuk_publishing_components 34.7.0 → 34.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (101) hide show
  1. checksums.yaml +4 -4
  2. data/lib/govuk_publishing_components/presenters/related_navigation_helper.rb +0 -7
  3. data/lib/govuk_publishing_components/version.rb +1 -1
  4. data/node_modules/govuk-frontend/govuk/all.js +4029 -3792
  5. data/node_modules/govuk-frontend/govuk/all.js.map +1 -0
  6. data/node_modules/govuk-frontend/govuk/common/closest-attribute-value.js +52 -51
  7. data/node_modules/govuk-frontend/govuk/common/closest-attribute-value.js.map +1 -0
  8. data/node_modules/govuk-frontend/govuk/common/index.js +153 -145
  9. data/node_modules/govuk-frontend/govuk/common/index.js.map +1 -0
  10. data/node_modules/govuk-frontend/govuk/common/normalise-dataset.js +324 -321
  11. data/node_modules/govuk-frontend/govuk/common/normalise-dataset.js.map +1 -0
  12. data/node_modules/govuk-frontend/govuk/common.js +154 -146
  13. data/node_modules/govuk-frontend/govuk/common.js.map +1 -0
  14. data/node_modules/govuk-frontend/govuk/components/_all.scss +1 -1
  15. data/node_modules/govuk-frontend/govuk/components/accordion/_index.scss +23 -4
  16. data/node_modules/govuk-frontend/govuk/components/accordion/accordion.js +2059 -1654
  17. data/node_modules/govuk-frontend/govuk/components/accordion/accordion.js.map +1 -0
  18. data/node_modules/govuk-frontend/govuk/components/accordion/fixtures.json +11 -11
  19. data/node_modules/govuk-frontend/govuk/components/accordion/template.njk +1 -1
  20. data/node_modules/govuk-frontend/govuk/components/back-link/_index.scss +19 -19
  21. data/node_modules/govuk-frontend/govuk/components/breadcrumbs/_index.scss +21 -10
  22. data/node_modules/govuk-frontend/govuk/components/button/button.js +927 -917
  23. data/node_modules/govuk-frontend/govuk/components/button/button.js.map +1 -0
  24. data/node_modules/govuk-frontend/govuk/components/character-count/character-count.js +2050 -2040
  25. data/node_modules/govuk-frontend/govuk/components/character-count/character-count.js.map +1 -0
  26. data/node_modules/govuk-frontend/govuk/components/checkboxes/checkboxes.js +1155 -1147
  27. data/node_modules/govuk-frontend/govuk/components/checkboxes/checkboxes.js.map +1 -0
  28. data/node_modules/govuk-frontend/govuk/components/cookie-banner/fixtures.json +23 -23
  29. data/node_modules/govuk-frontend/govuk/components/cookie-banner/template.njk +1 -1
  30. data/node_modules/govuk-frontend/govuk/components/details/details.js +800 -799
  31. data/node_modules/govuk-frontend/govuk/components/details/details.js.map +1 -0
  32. data/node_modules/govuk-frontend/govuk/components/error-summary/error-summary.js +1058 -1045
  33. data/node_modules/govuk-frontend/govuk/components/error-summary/error-summary.js.map +1 -0
  34. data/node_modules/govuk-frontend/govuk/components/header/_index.scss +6 -0
  35. data/node_modules/govuk-frontend/govuk/components/header/header.js +646 -998
  36. data/node_modules/govuk-frontend/govuk/components/header/header.js.map +1 -0
  37. data/node_modules/govuk-frontend/govuk/components/notification-banner/notification-banner.js +760 -752
  38. data/node_modules/govuk-frontend/govuk/components/notification-banner/notification-banner.js.map +1 -0
  39. data/node_modules/govuk-frontend/govuk/components/pagination/fixtures.json +61 -0
  40. data/node_modules/govuk-frontend/govuk/components/pagination/template.njk +1 -1
  41. data/node_modules/govuk-frontend/govuk/components/phase-banner/macro-options.json +1 -1
  42. data/node_modules/govuk-frontend/govuk/components/radios/radios.js +1110 -1107
  43. data/node_modules/govuk-frontend/govuk/components/radios/radios.js.map +1 -0
  44. data/node_modules/govuk-frontend/govuk/components/skip-link/skip-link.js +1017 -1014
  45. data/node_modules/govuk-frontend/govuk/components/skip-link/skip-link.js.map +1 -0
  46. data/node_modules/govuk-frontend/govuk/components/summary-list/_index.scss +107 -0
  47. data/node_modules/govuk-frontend/govuk/components/summary-list/fixtures.json +318 -23
  48. data/node_modules/govuk-frontend/govuk/components/summary-list/macro-options.json +110 -0
  49. data/node_modules/govuk-frontend/govuk/components/summary-list/template.njk +72 -28
  50. data/node_modules/govuk-frontend/govuk/components/tabs/tabs.js +1392 -1264
  51. data/node_modules/govuk-frontend/govuk/components/tabs/tabs.js.map +1 -0
  52. data/node_modules/govuk-frontend/govuk/i18n.js +363 -364
  53. data/node_modules/govuk-frontend/govuk/i18n.js.map +1 -0
  54. data/node_modules/govuk-frontend/govuk/settings/_measurements.scss +5 -5
  55. data/node_modules/govuk-frontend/govuk/vendor/polyfills/DOMTokenList.js +242 -241
  56. data/node_modules/govuk-frontend/govuk/vendor/polyfills/DOMTokenList.js.map +1 -0
  57. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Date/now.js +13 -12
  58. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Date/now.js.map +1 -0
  59. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Document.js +17 -16
  60. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Document.js.map +1 -0
  61. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Element/prototype/classList.js +547 -546
  62. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Element/prototype/classList.js.map +1 -0
  63. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Element/prototype/closest.js +37 -36
  64. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Element/prototype/closest.js.map +1 -0
  65. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Element/prototype/dataset.js +251 -250
  66. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Element/prototype/dataset.js.map +1 -0
  67. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Element/prototype/matches.js +21 -20
  68. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Element/prototype/matches.js.map +1 -0
  69. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Element/prototype/nextElementSibling.js +198 -197
  70. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Element/prototype/nextElementSibling.js.map +1 -0
  71. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Element/prototype/previousElementSibling.js +198 -197
  72. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Element/prototype/previousElementSibling.js.map +1 -0
  73. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Element.js +106 -105
  74. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Element.js.map +1 -0
  75. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Event.js +400 -399
  76. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Event.js.map +1 -0
  77. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Function/prototype/bind.js +239 -238
  78. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Function/prototype/bind.js.map +1 -0
  79. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Object/defineProperty.js +72 -71
  80. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Object/defineProperty.js.map +1 -0
  81. data/node_modules/govuk-frontend/govuk/vendor/polyfills/String/prototype/trim.js +14 -13
  82. data/node_modules/govuk-frontend/govuk/vendor/polyfills/String/prototype/trim.js.map +1 -0
  83. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Window.js +17 -16
  84. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Window.js.map +1 -0
  85. data/node_modules/govuk-frontend/govuk-esm/all.mjs +2 -2
  86. data/node_modules/govuk-frontend/govuk-esm/common/index.mjs +17 -10
  87. data/node_modules/govuk-frontend/govuk-esm/common/normalise-dataset.mjs +3 -1
  88. data/node_modules/govuk-frontend/govuk-esm/components/accordion/accordion.mjs +135 -52
  89. data/node_modules/govuk-frontend/govuk-esm/components/button/button.mjs +11 -9
  90. data/node_modules/govuk-frontend/govuk-esm/components/character-count/character-count.mjs +10 -7
  91. data/node_modules/govuk-frontend/govuk-esm/components/checkboxes/checkboxes.mjs +24 -18
  92. data/node_modules/govuk-frontend/govuk-esm/components/details/details.mjs +23 -16
  93. data/node_modules/govuk-frontend/govuk-esm/components/error-summary/error-summary.mjs +15 -11
  94. data/node_modules/govuk-frontend/govuk-esm/components/header/header.mjs +3 -2
  95. data/node_modules/govuk-frontend/govuk-esm/components/notification-banner/notification-banner.mjs +3 -4
  96. data/node_modules/govuk-frontend/govuk-esm/components/radios/radios.mjs +10 -9
  97. data/node_modules/govuk-frontend/govuk-esm/components/skip-link/skip-link.mjs +5 -3
  98. data/node_modules/govuk-frontend/govuk-esm/components/tabs/tabs.mjs +165 -38
  99. data/node_modules/govuk-frontend/govuk-esm/i18n.mjs +9 -11
  100. data/node_modules/govuk-frontend/package.json +1 -1
  101. metadata +34 -2
@@ -1,2177 +1,2187 @@
1
1
  (function (global, factory) {
2
- typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
3
- typeof define === 'function' && define.amd ? define('GOVUKFrontend.CharacterCount', factory) :
4
- (global.GOVUKFrontend = global.GOVUKFrontend || {}, global.GOVUKFrontend.CharacterCount = factory());
2
+ typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
3
+ typeof define === 'function' && define.amd ? define('GOVUKFrontend.CharacterCount', factory) :
4
+ (global.GOVUKFrontend = global.GOVUKFrontend || {}, global.GOVUKFrontend.CharacterCount = factory());
5
5
  }(this, (function () { 'use strict';
6
6
 
7
- (function(undefined) {
7
+ (function(undefined) {
8
8
 
9
- // Detection from https://github.com/Financial-Times/polyfill-library/blob/v3.111.0/polyfills/Date/now/detect.js
10
- var detect = ('Date' in self && 'now' in self.Date && 'getTime' in self.Date.prototype);
9
+ // Detection from https://raw.githubusercontent.com/Financial-Times/polyfill-service/1f3c09b402f65bf6e393f933a15ba63f1b86ef1f/packages/polyfill-library/polyfills/Element/prototype/matches/detect.js
10
+ var detect = (
11
+ 'document' in this && "matches" in document.documentElement
12
+ );
11
13
 
12
14
  if (detect) return
13
15
 
14
- // Polyfill from https://polyfill.io/v3/polyfill.js?version=3.111.0&features=Date.now&flags=always
15
- Date.now = function () {
16
- return new Date().getTime();
17
- };
18
-
19
- }).call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
20
-
21
- (function(undefined) {
22
-
23
- // Detection from https://github.com/Financial-Times/polyfill-service/blob/master/packages/polyfill-library/polyfills/Object/defineProperty/detect.js
24
- var detect = (
25
- // In IE8, defineProperty could only act on DOM elements, so full support
26
- // for the feature requires the ability to set a property on an arbitrary object
27
- 'defineProperty' in Object && (function() {
28
- try {
29
- var a = {};
30
- Object.defineProperty(a, 'test', {value:42});
31
- return true;
32
- } catch(e) {
33
- return false
34
- }
35
- }())
36
- );
37
-
38
- if (detect) return
39
-
40
- // Polyfill from https://cdn.polyfill.io/v2/polyfill.js?features=Object.defineProperty&flags=always
41
- (function (nativeDefineProperty) {
42
-
43
- var supportsAccessors = Object.prototype.hasOwnProperty('__defineGetter__');
44
- var ERR_ACCESSORS_NOT_SUPPORTED = 'Getters & setters cannot be defined on this javascript engine';
45
- var ERR_VALUE_ACCESSORS = 'A property cannot both have accessors and be writable or have a value';
46
-
47
- Object.defineProperty = function defineProperty(object, property, descriptor) {
48
-
49
- // Where native support exists, assume it
50
- if (nativeDefineProperty && (object === window || object === document || object === Element.prototype || object instanceof Element)) {
51
- return nativeDefineProperty(object, property, descriptor);
52
- }
53
-
54
- if (object === null || !(object instanceof Object || typeof object === 'object')) {
55
- throw new TypeError('Object.defineProperty called on non-object');
56
- }
57
-
58
- if (!(descriptor instanceof Object)) {
59
- throw new TypeError('Property description must be an object');
60
- }
61
-
62
- var propertyString = String(property);
63
- var hasValueOrWritable = 'value' in descriptor || 'writable' in descriptor;
64
- var getterType = 'get' in descriptor && typeof descriptor.get;
65
- var setterType = 'set' in descriptor && typeof descriptor.set;
66
-
67
- // handle descriptor.get
68
- if (getterType) {
69
- if (getterType !== 'function') {
70
- throw new TypeError('Getter must be a function');
71
- }
72
- if (!supportsAccessors) {
73
- throw new TypeError(ERR_ACCESSORS_NOT_SUPPORTED);
74
- }
75
- if (hasValueOrWritable) {
76
- throw new TypeError(ERR_VALUE_ACCESSORS);
77
- }
78
- Object.__defineGetter__.call(object, propertyString, descriptor.get);
79
- } else {
80
- object[propertyString] = descriptor.value;
81
- }
82
-
83
- // handle descriptor.set
84
- if (setterType) {
85
- if (setterType !== 'function') {
86
- throw new TypeError('Setter must be a function');
87
- }
88
- if (!supportsAccessors) {
89
- throw new TypeError(ERR_ACCESSORS_NOT_SUPPORTED);
90
- }
91
- if (hasValueOrWritable) {
92
- throw new TypeError(ERR_VALUE_ACCESSORS);
93
- }
94
- Object.__defineSetter__.call(object, propertyString, descriptor.set);
95
- }
96
-
97
- // OK to define value unconditionally - if a getter has been specified as well, an error would be thrown above
98
- if ('value' in descriptor) {
99
- object[propertyString] = descriptor.value;
100
- }
101
-
102
- return object;
103
- };
104
- }(Object.defineProperty));
105
- })
106
- .call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
107
-
108
- (function(undefined) {
109
- // Detection from https://github.com/Financial-Times/polyfill-service/blob/master/packages/polyfill-library/polyfills/Function/prototype/bind/detect.js
110
- var detect = 'bind' in Function.prototype;
16
+ // Polyfill from https://raw.githubusercontent.com/Financial-Times/polyfill-service/1f3c09b402f65bf6e393f933a15ba63f1b86ef1f/packages/polyfill-library/polyfills/Element/prototype/matches/polyfill.js
17
+ Element.prototype.matches = Element.prototype.webkitMatchesSelector || Element.prototype.oMatchesSelector || Element.prototype.msMatchesSelector || Element.prototype.mozMatchesSelector || function matches(selector) {
18
+ var element = this;
19
+ var elements = (element.document || element.ownerDocument).querySelectorAll(selector);
20
+ var index = 0;
111
21
 
112
- if (detect) return
22
+ while (elements[index] && elements[index] !== element) {
23
+ ++index;
24
+ }
113
25
 
114
- // Polyfill from https://cdn.polyfill.io/v2/polyfill.js?features=Function.prototype.bind&flags=always
115
- Object.defineProperty(Function.prototype, 'bind', {
116
- value: function bind(that) { // .length is 1
117
- // add necessary es5-shim utilities
118
- var $Array = Array;
119
- var $Object = Object;
120
- var ObjectPrototype = $Object.prototype;
121
- var ArrayPrototype = $Array.prototype;
122
- var Empty = function Empty() {};
123
- var to_string = ObjectPrototype.toString;
124
- var hasToStringTag = typeof Symbol === 'function' && typeof Symbol.toStringTag === 'symbol';
125
- 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; };
126
- var array_slice = ArrayPrototype.slice;
127
- var array_concat = ArrayPrototype.concat;
128
- var array_push = ArrayPrototype.push;
129
- var max = Math.max;
130
- // /add necessary es5-shim utilities
131
-
132
- // 1. Let Target be the this value.
133
- var target = this;
134
- // 2. If IsCallable(Target) is false, throw a TypeError exception.
135
- if (!isCallable(target)) {
136
- throw new TypeError('Function.prototype.bind called on incompatible ' + target);
137
- }
138
- // 3. Let A be a new (possibly empty) internal list of all of the
139
- // argument values provided after thisArg (arg1, arg2 etc), in order.
140
- // XXX slicedArgs will stand in for "A" if used
141
- var args = array_slice.call(arguments, 1); // for normal call
142
- // 4. Let F be a new native ECMAScript object.
143
- // 11. Set the [[Prototype]] internal property of F to the standard
144
- // built-in Function prototype object as specified in 15.3.3.1.
145
- // 12. Set the [[Call]] internal property of F as described in
146
- // 15.3.4.5.1.
147
- // 13. Set the [[Construct]] internal property of F as described in
148
- // 15.3.4.5.2.
149
- // 14. Set the [[HasInstance]] internal property of F as described in
150
- // 15.3.4.5.3.
151
- var bound;
152
- var binder = function () {
153
-
154
- if (this instanceof bound) {
155
- // 15.3.4.5.2 [[Construct]]
156
- // When the [[Construct]] internal method of a function object,
157
- // F that was created using the bind function is called with a
158
- // list of arguments ExtraArgs, the following steps are taken:
159
- // 1. Let target be the value of F's [[TargetFunction]]
160
- // internal property.
161
- // 2. If target has no [[Construct]] internal method, a
162
- // TypeError exception is thrown.
163
- // 3. Let boundArgs be the value of F's [[BoundArgs]] internal
164
- // property.
165
- // 4. Let args be a new list containing the same values as the
166
- // list boundArgs in the same order followed by the same
167
- // values as the list ExtraArgs in the same order.
168
- // 5. Return the result of calling the [[Construct]] internal
169
- // method of target providing args as the arguments.
170
-
171
- var result = target.apply(
172
- this,
173
- array_concat.call(args, array_slice.call(arguments))
174
- );
175
- if ($Object(result) === result) {
176
- return result;
177
- }
178
- return this;
179
-
180
- } else {
181
- // 15.3.4.5.1 [[Call]]
182
- // When the [[Call]] internal method of a function object, F,
183
- // which was created using the bind function is called with a
184
- // this value and a list of arguments ExtraArgs, the following
185
- // steps are taken:
186
- // 1. Let boundArgs be the value of F's [[BoundArgs]] internal
187
- // property.
188
- // 2. Let boundThis be the value of F's [[BoundThis]] internal
189
- // property.
190
- // 3. Let target be the value of F's [[TargetFunction]] internal
191
- // property.
192
- // 4. Let args be a new list containing the same values as the
193
- // list boundArgs in the same order followed by the same
194
- // values as the list ExtraArgs in the same order.
195
- // 5. Return the result of calling the [[Call]] internal method
196
- // of target providing boundThis as the this value and
197
- // providing args as the arguments.
198
-
199
- // equiv: target.call(this, ...boundArgs, ...args)
200
- return target.apply(
201
- that,
202
- array_concat.call(args, array_slice.call(arguments))
203
- );
204
-
205
- }
26
+ return !!elements[index];
27
+ };
206
28
 
207
- };
29
+ }).call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
208
30
 
209
- // 15. If the [[Class]] internal property of Target is "Function", then
210
- // a. Let L be the length property of Target minus the length of A.
211
- // b. Set the length own property of F to either 0 or L, whichever is
212
- // larger.
213
- // 16. Else set the length own property of F to 0.
31
+ (function(undefined) {
214
32
 
215
- var boundLength = max(0, target.length - args.length);
33
+ // Detection from https://raw.githubusercontent.com/Financial-Times/polyfill-service/1f3c09b402f65bf6e393f933a15ba63f1b86ef1f/packages/polyfill-library/polyfills/Element/prototype/closest/detect.js
34
+ var detect = (
35
+ 'document' in this && "closest" in document.documentElement
36
+ );
216
37
 
217
- // 17. Set the attributes of the length own property of F to the values
218
- // specified in 15.3.5.1.
219
- var boundArgs = [];
220
- for (var i = 0; i < boundLength; i++) {
221
- array_push.call(boundArgs, '$' + i);
222
- }
38
+ if (detect) return
223
39
 
224
- // XXX Build a dynamic function with desired amount of arguments is the only
225
- // way to set the length property of a function.
226
- // In environments where Content Security Policies enabled (Chrome extensions,
227
- // for ex.) all use of eval or Function costructor throws an exception.
228
- // However in all of these environments Function.prototype.bind exists
229
- // and so this code will never be executed.
230
- bound = Function('binder', 'return function (' + boundArgs.join(',') + '){ return binder.apply(this, arguments); }')(binder);
231
-
232
- if (target.prototype) {
233
- Empty.prototype = target.prototype;
234
- bound.prototype = new Empty();
235
- // Clean up dangling references.
236
- Empty.prototype = null;
237
- }
40
+ // Polyfill from https://raw.githubusercontent.com/Financial-Times/polyfill-service/1f3c09b402f65bf6e393f933a15ba63f1b86ef1f/packages/polyfill-library/polyfills/Element/prototype/closest/polyfill.js
41
+ Element.prototype.closest = function closest(selector) {
42
+ var node = this;
238
43
 
239
- // TODO
240
- // 18. Set the [[Extensible]] internal property of F to true.
241
-
242
- // TODO
243
- // 19. Let thrower be the [[ThrowTypeError]] function Object (13.2.3).
244
- // 20. Call the [[DefineOwnProperty]] internal method of F with
245
- // arguments "caller", PropertyDescriptor {[[Get]]: thrower, [[Set]]:
246
- // thrower, [[Enumerable]]: false, [[Configurable]]: false}, and
247
- // false.
248
- // 21. Call the [[DefineOwnProperty]] internal method of F with
249
- // arguments "arguments", PropertyDescriptor {[[Get]]: thrower,
250
- // [[Set]]: thrower, [[Enumerable]]: false, [[Configurable]]: false},
251
- // and false.
252
-
253
- // TODO
254
- // NOTE Function objects created using Function.prototype.bind do not
255
- // have a prototype property or the [[Code]], [[FormalParameters]], and
256
- // [[Scope]] internal properties.
257
- // XXX can't delete prototype in pure-js.
258
-
259
- // 22. Return F.
260
- return bound;
44
+ while (node) {
45
+ if (node.matches(selector)) return node;
46
+ else node = 'SVGElement' in window && node instanceof SVGElement ? node.parentNode : node.parentElement;
261
47
  }
262
- });
263
- })
264
- .call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
265
-
266
- (function(undefined) {
267
-
268
- // Detection from https://github.com/Financial-Times/polyfill-service/blob/master/packages/polyfill-library/polyfills/Window/detect.js
269
- var detect = ('Window' in this);
270
-
271
- if (detect) return
272
48
 
273
- // Polyfill from https://cdn.polyfill.io/v2/polyfill.js?features=Window&flags=always
274
- if ((typeof WorkerGlobalScope === "undefined") && (typeof importScripts !== "function")) {
275
- (function (global) {
276
- if (global.constructor) {
277
- global.Window = global.constructor;
278
- } else {
279
- (global.Window = global.constructor = new Function('return function Window() {}')()).prototype = this;
280
- }
281
- }(this));
282
- }
283
-
284
- })
285
- .call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
49
+ return null;
50
+ };
286
51
 
287
- (function(undefined) {
52
+ }).call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
288
53
 
289
- // Detection from https://github.com/Financial-Times/polyfill-service/blob/master/packages/polyfill-library/polyfills/Document/detect.js
290
- var detect = ("Document" in this);
54
+ /**
55
+ * Returns the value of the given attribute closest to the given element (including itself)
56
+ *
57
+ * @param {HTMLElement} $element - The element to start walking the DOM tree up
58
+ * @param {string} attributeName - The name of the attribute
59
+ * @returns {string | undefined} Attribute value
60
+ */
61
+ function closestAttributeValue ($element, attributeName) {
62
+ var closestElementWithAttribute = $element.closest('[' + attributeName + ']');
63
+ if (closestElementWithAttribute) {
64
+ return closestElementWithAttribute.getAttribute(attributeName)
65
+ }
66
+ }
291
67
 
292
- if (detect) return
68
+ /**
69
+ * Common helpers which do not require polyfill.
70
+ *
71
+ * IMPORTANT: If a helper require a polyfill, please isolate it in its own module
72
+ * so that the polyfill can be properly tree-shaken and does not burden
73
+ * the components that do not need that helper
74
+ *
75
+ * @module common/index
76
+ */
293
77
 
294
- // Polyfill from https://cdn.polyfill.io/v2/polyfill.js?features=Document&flags=always
295
- if ((typeof WorkerGlobalScope === "undefined") && (typeof importScripts !== "function")) {
78
+ /**
79
+ * Config flattening function
80
+ *
81
+ * Takes any number of objects, flattens them into namespaced key-value pairs,
82
+ * (e.g. {'i18n.showSection': 'Show section'}) and combines them together, with
83
+ * greatest priority on the LAST item passed in.
84
+ *
85
+ * @returns {Object<string, unknown>} A flattened object of key-value pairs.
86
+ */
87
+ function mergeConfigs (/* configObject1, configObject2, ...configObjects */) {
88
+ /**
89
+ * Function to take nested objects and flatten them to a dot-separated keyed
90
+ * object. Doing this means we don't need to do any deep/recursive merging of
91
+ * each of our objects, nor transform our dataset from a flat list into a
92
+ * nested object.
93
+ *
94
+ * @param {Object<string, unknown>} configObject - Deeply nested object
95
+ * @returns {Object<string, unknown>} Flattened object with dot-separated keys
96
+ */
97
+ var flattenObject = function (configObject) {
98
+ // Prepare an empty return object
99
+ var flattenedObject = {};
100
+
101
+ /**
102
+ * Our flattening function, this is called recursively for each level of
103
+ * depth in the object. At each level we prepend the previous level names to
104
+ * the key using `prefix`.
105
+ *
106
+ * @param {Partial<Object<string, unknown>>} obj - Object to flatten
107
+ * @param {string} [prefix] - Optional dot-separated prefix
108
+ */
109
+ var flattenLoop = function (obj, prefix) {
110
+ // Loop through keys...
111
+ for (var key in obj) {
112
+ // Check to see if this is a prototypical key/value,
113
+ // if it is, skip it.
114
+ if (!Object.prototype.hasOwnProperty.call(obj, key)) {
115
+ continue
116
+ }
117
+ var value = obj[key];
118
+ var prefixedKey = prefix ? prefix + '.' + key : key;
119
+ if (typeof value === 'object') {
120
+ // If the value is a nested object, recurse over that too
121
+ flattenLoop(value, prefixedKey);
122
+ } else {
123
+ // Otherwise, add this value to our return object
124
+ flattenedObject[prefixedKey] = value;
125
+ }
126
+ }
127
+ };
296
128
 
297
- if (this.HTMLDocument) { // IE8
129
+ // Kick off the recursive loop
130
+ flattenLoop(configObject);
131
+ return flattenedObject
132
+ };
298
133
 
299
- // HTMLDocument is an extension of Document. If the browser has HTMLDocument but not Document, the former will suffice as an alias for the latter.
300
- this.Document = this.HTMLDocument;
134
+ // Start with an empty object as our base
135
+ var formattedConfigObject = {};
301
136
 
302
- } else {
137
+ // Loop through each of the remaining passed objects and push their keys
138
+ // one-by-one into configObject. Any duplicate keys will override the existing
139
+ // key with the new value.
140
+ for (var i = 0; i < arguments.length; i++) {
141
+ var obj = flattenObject(arguments[i]);
142
+ for (var key in obj) {
143
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
144
+ formattedConfigObject[key] = obj[key];
145
+ }
146
+ }
147
+ }
303
148
 
304
- // 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.
305
- this.Document = this.HTMLDocument = document.constructor = (new Function('return function Document() {}')());
306
- this.Document.prototype = document;
307
- }
308
- }
309
-
310
-
311
- })
312
- .call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
313
-
314
- (function(undefined) {
315
-
316
- // Detection from https://github.com/Financial-Times/polyfill-service/blob/master/packages/polyfill-library/polyfills/Element/detect.js
317
- var detect = ('Element' in this && 'HTMLElement' in this);
318
-
319
- if (detect) return
320
-
321
- // Polyfill from https://cdn.polyfill.io/v2/polyfill.js?features=Element&flags=always
322
- (function () {
323
-
324
- // IE8
325
- if (window.Element && !window.HTMLElement) {
326
- window.HTMLElement = window.Element;
327
- return;
328
- }
329
-
330
- // create Element constructor
331
- window.Element = window.HTMLElement = new Function('return function Element() {}')();
332
-
333
- // generate sandboxed iframe
334
- var vbody = document.appendChild(document.createElement('body'));
335
- var frame = vbody.appendChild(document.createElement('iframe'));
336
-
337
- // use sandboxed iframe to replicate Element functionality
338
- var frameDocument = frame.contentWindow.document;
339
- var prototype = Element.prototype = frameDocument.appendChild(frameDocument.createElement('*'));
340
- var cache = {};
341
-
342
- // polyfill Element.prototype on an element
343
- var shiv = function (element, deep) {
344
- var
345
- childNodes = element.childNodes || [],
346
- index = -1,
347
- key, value, childNode;
348
-
349
- if (element.nodeType === 1 && element.constructor !== Element) {
350
- element.constructor = Element;
351
-
352
- for (key in cache) {
353
- value = cache[key];
354
- element[key] = value;
355
- }
356
- }
357
-
358
- while (childNode = deep && childNodes[++index]) {
359
- shiv(childNode, deep);
360
- }
361
-
362
- return element;
363
- };
364
-
365
- var elements = document.getElementsByTagName('*');
366
- var nativeCreateElement = document.createElement;
367
- var interval;
368
- var loopLimit = 100;
369
-
370
- prototype.attachEvent('onpropertychange', function (event) {
371
- var
372
- propertyName = event.propertyName,
373
- nonValue = !cache.hasOwnProperty(propertyName),
374
- newValue = prototype[propertyName],
375
- oldValue = cache[propertyName],
376
- index = -1,
377
- element;
378
-
379
- while (element = elements[++index]) {
380
- if (element.nodeType === 1) {
381
- if (nonValue || element[propertyName] === oldValue) {
382
- element[propertyName] = newValue;
383
- }
384
- }
385
- }
386
-
387
- cache[propertyName] = newValue;
388
- });
389
-
390
- prototype.constructor = Element;
391
-
392
- if (!prototype.hasAttribute) {
393
- // <Element>.hasAttribute
394
- prototype.hasAttribute = function hasAttribute(name) {
395
- return this.getAttribute(name) !== null;
396
- };
397
- }
398
-
399
- // Apply Element prototype to the pre-existing DOM as soon as the body element appears.
400
- function bodyCheck() {
401
- if (!(loopLimit--)) clearTimeout(interval);
402
- if (document.body && !document.body.prototype && /(complete|interactive)/.test(document.readyState)) {
403
- shiv(document, true);
404
- if (interval && document.body.prototype) clearTimeout(interval);
405
- return (!!document.body.prototype);
406
- }
407
- return false;
408
- }
409
- if (!bodyCheck()) {
410
- document.onreadystatechange = bodyCheck;
411
- interval = setInterval(bodyCheck, 25);
412
- }
413
-
414
- // Apply to any new elements created after load
415
- document.createElement = function createElement(nodeName) {
416
- var element = nativeCreateElement(String(nodeName).toLowerCase());
417
- return shiv(element);
418
- };
419
-
420
- // remove sandboxed iframe
421
- document.removeChild(vbody);
422
- }());
423
-
424
- })
425
- .call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
426
-
427
- (function(undefined) {
428
-
429
- // Detection from https://github.com/Financial-Times/polyfill-service/blob/master/packages/polyfill-library/polyfills/Event/detect.js
430
- var detect = (
431
- (function(global) {
432
-
433
- if (!('Event' in global)) return false;
434
- if (typeof global.Event === 'function') return true;
435
-
436
- try {
437
-
438
- // In IE 9-11, the Event object exists but cannot be instantiated
439
- new Event('click');
440
- return true;
441
- } catch(e) {
442
- return false;
443
- }
444
- }(this))
445
- );
446
-
447
- if (detect) return
448
-
449
- // Polyfill from https://cdn.polyfill.io/v2/polyfill.js?features=Event&flags=always
450
- (function () {
451
- var unlistenableWindowEvents = {
452
- click: 1,
453
- dblclick: 1,
454
- keyup: 1,
455
- keypress: 1,
456
- keydown: 1,
457
- mousedown: 1,
458
- mouseup: 1,
459
- mousemove: 1,
460
- mouseover: 1,
461
- mouseenter: 1,
462
- mouseleave: 1,
463
- mouseout: 1,
464
- storage: 1,
465
- storagecommit: 1,
466
- textinput: 1
467
- };
468
-
469
- // This polyfill depends on availability of `document` so will not run in a worker
470
- // However, we asssume there are no browsers with worker support that lack proper
471
- // support for `Event` within the worker
472
- if (typeof document === 'undefined' || typeof window === 'undefined') return;
473
-
474
- function indexOf(array, element) {
475
- var
476
- index = -1,
477
- length = array.length;
478
-
479
- while (++index < length) {
480
- if (index in array && array[index] === element) {
481
- return index;
482
- }
483
- }
484
-
485
- return -1;
486
- }
487
-
488
- var existingProto = (window.Event && window.Event.prototype) || null;
489
- window.Event = Window.prototype.Event = function Event(type, eventInitDict) {
490
- if (!type) {
491
- throw new Error('Not enough arguments');
492
- }
493
-
494
- var event;
495
- // Shortcut if browser supports createEvent
496
- if ('createEvent' in document) {
497
- event = document.createEvent('Event');
498
- var bubbles = eventInitDict && eventInitDict.bubbles !== undefined ? eventInitDict.bubbles : false;
499
- var cancelable = eventInitDict && eventInitDict.cancelable !== undefined ? eventInitDict.cancelable : false;
500
-
501
- event.initEvent(type, bubbles, cancelable);
502
-
503
- return event;
504
- }
505
-
506
- event = document.createEventObject();
507
-
508
- event.type = type;
509
- event.bubbles = eventInitDict && eventInitDict.bubbles !== undefined ? eventInitDict.bubbles : false;
510
- event.cancelable = eventInitDict && eventInitDict.cancelable !== undefined ? eventInitDict.cancelable : false;
511
-
512
- return event;
513
- };
514
- if (existingProto) {
515
- Object.defineProperty(window.Event, 'prototype', {
516
- configurable: false,
517
- enumerable: false,
518
- writable: true,
519
- value: existingProto
520
- });
521
- }
522
-
523
- if (!('createEvent' in document)) {
524
- window.addEventListener = Window.prototype.addEventListener = Document.prototype.addEventListener = Element.prototype.addEventListener = function addEventListener() {
525
- var
526
- element = this,
527
- type = arguments[0],
528
- listener = arguments[1];
529
-
530
- if (element === window && type in unlistenableWindowEvents) {
531
- 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.');
532
- }
533
-
534
- if (!element._events) {
535
- element._events = {};
536
- }
537
-
538
- if (!element._events[type]) {
539
- element._events[type] = function (event) {
540
- var
541
- list = element._events[event.type].list,
542
- events = list.slice(),
543
- index = -1,
544
- length = events.length,
545
- eventElement;
546
-
547
- event.preventDefault = function preventDefault() {
548
- if (event.cancelable !== false) {
549
- event.returnValue = false;
550
- }
551
- };
552
-
553
- event.stopPropagation = function stopPropagation() {
554
- event.cancelBubble = true;
555
- };
556
-
557
- event.stopImmediatePropagation = function stopImmediatePropagation() {
558
- event.cancelBubble = true;
559
- event.cancelImmediate = true;
560
- };
561
-
562
- event.currentTarget = element;
563
- event.relatedTarget = event.fromElement || null;
564
- event.target = event.target || event.srcElement || element;
565
- event.timeStamp = new Date().getTime();
566
-
567
- if (event.clientX) {
568
- event.pageX = event.clientX + document.documentElement.scrollLeft;
569
- event.pageY = event.clientY + document.documentElement.scrollTop;
570
- }
571
-
572
- while (++index < length && !event.cancelImmediate) {
573
- if (index in events) {
574
- eventElement = events[index];
575
-
576
- if (indexOf(list, eventElement) !== -1 && typeof eventElement === 'function') {
577
- eventElement.call(element, event);
578
- }
579
- }
580
- }
581
- };
582
-
583
- element._events[type].list = [];
584
-
585
- if (element.attachEvent) {
586
- element.attachEvent('on' + type, element._events[type]);
587
- }
588
- }
589
-
590
- element._events[type].list.push(listener);
591
- };
592
-
593
- window.removeEventListener = Window.prototype.removeEventListener = Document.prototype.removeEventListener = Element.prototype.removeEventListener = function removeEventListener() {
594
- var
595
- element = this,
596
- type = arguments[0],
597
- listener = arguments[1],
598
- index;
599
-
600
- if (element._events && element._events[type] && element._events[type].list) {
601
- index = indexOf(element._events[type].list, listener);
602
-
603
- if (index !== -1) {
604
- element._events[type].list.splice(index, 1);
605
-
606
- if (!element._events[type].list.length) {
607
- if (element.detachEvent) {
608
- element.detachEvent('on' + type, element._events[type]);
609
- }
610
- delete element._events[type];
611
- }
612
- }
613
- }
614
- };
615
-
616
- window.dispatchEvent = Window.prototype.dispatchEvent = Document.prototype.dispatchEvent = Element.prototype.dispatchEvent = function dispatchEvent(event) {
617
- if (!arguments.length) {
618
- throw new Error('Not enough arguments');
619
- }
620
-
621
- if (!event || typeof event.type !== 'string') {
622
- throw new Error('DOM Events Exception 0');
623
- }
624
-
625
- var element = this, type = event.type;
626
-
627
- try {
628
- if (!event.bubbles) {
629
- event.cancelBubble = true;
630
-
631
- var cancelBubbleEvent = function (event) {
632
- event.cancelBubble = true;
633
-
634
- (element || window).detachEvent('on' + type, cancelBubbleEvent);
635
- };
636
-
637
- this.attachEvent('on' + type, cancelBubbleEvent);
638
- }
639
-
640
- this.fireEvent('on' + type, event);
641
- } catch (error) {
642
- event.target = element;
643
-
644
- do {
645
- event.currentTarget = element;
646
-
647
- if ('_events' in element && typeof element._events[type] === 'function') {
648
- element._events[type].call(element, event);
649
- }
650
-
651
- if (typeof element['on' + type] === 'function') {
652
- element['on' + type].call(element, event);
653
- }
654
-
655
- element = element.nodeType === 9 ? element.parentWindow : element.parentNode;
656
- } while (element && !event.cancelBubble);
657
- }
658
-
659
- return true;
660
- };
661
-
662
- // Add the DOMContentLoaded Event
663
- document.attachEvent('onreadystatechange', function() {
664
- if (document.readyState === 'complete') {
665
- document.dispatchEvent(new Event('DOMContentLoaded', {
666
- bubbles: true
667
- }));
668
- }
669
- });
670
- }
671
- }());
672
-
673
- })
674
- .call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
675
-
676
- (function(undefined) {
677
-
678
- // Detection from https://raw.githubusercontent.com/Financial-Times/polyfill-service/master/packages/polyfill-library/polyfills/DOMTokenList/detect.js
679
- var detect = (
680
- 'DOMTokenList' in this && (function (x) {
681
- return 'classList' in x ? !x.classList.toggle('x', false) && !x.className : true;
682
- })(document.createElement('x'))
683
- );
149
+ return formattedConfigObject
150
+ }
684
151
 
685
- if (detect) return
152
+ /**
153
+ * Extracts keys starting with a particular namespace from a flattened config
154
+ * object, removing the namespace in the process.
155
+ *
156
+ * @param {Object<string, unknown>} configObject - The object to extract key-value pairs from.
157
+ * @param {string} namespace - The namespace to filter keys with.
158
+ * @returns {Object<string, unknown>} Flattened object with dot-separated key namespace removed
159
+ * @throws {Error} Config object required
160
+ * @throws {Error} Namespace string required
161
+ */
162
+ function extractConfigByNamespace (configObject, namespace) {
163
+ // Check we have what we need
164
+ if (!configObject || typeof configObject !== 'object') {
165
+ throw new Error('Provide a `configObject` of type "object".')
166
+ }
167
+ if (!namespace || typeof namespace !== 'string') {
168
+ throw new Error('Provide a `namespace` of type "string" to filter the `configObject` by.')
169
+ }
170
+ var newObject = {};
171
+ for (var key in configObject) {
172
+ // Split the key into parts, using . as our namespace separator
173
+ var keyParts = key.split('.');
174
+ // Check if the first namespace matches the configured namespace
175
+ if (Object.prototype.hasOwnProperty.call(configObject, key) && keyParts[0] === namespace) {
176
+ // Remove the first item (the namespace) from the parts array,
177
+ // but only if there is more than one part (we don't want blank keys!)
178
+ if (keyParts.length > 1) {
179
+ keyParts.shift();
180
+ }
181
+ // Join the remaining parts back together
182
+ var newKey = keyParts.join('.');
183
+ // Add them to our new object
184
+ newObject[newKey] = configObject[key];
185
+ }
186
+ }
187
+ return newObject
188
+ }
686
189
 
687
- // Polyfill from https://raw.githubusercontent.com/Financial-Times/polyfill-service/master/packages/polyfill-library/polyfills/DOMTokenList/polyfill.js
688
- (function (global) {
689
- var nativeImpl = "DOMTokenList" in global && global.DOMTokenList;
690
-
691
- if (
692
- !nativeImpl ||
693
- (
694
- !!document.createElementNS &&
695
- !!document.createElementNS('http://www.w3.org/2000/svg', 'svg') &&
696
- !(document.createElementNS("http://www.w3.org/2000/svg", "svg").classList instanceof DOMTokenList)
697
- )
698
- ) {
699
- global.DOMTokenList = (function() { // eslint-disable-line no-unused-vars
700
- var dpSupport = true;
701
- var defineGetter = function (object, name, fn, configurable) {
702
- if (Object.defineProperty)
703
- Object.defineProperty(object, name, {
704
- configurable: false === dpSupport ? true : !!configurable,
705
- get: fn
706
- });
190
+ /**
191
+ * @callback nodeListIterator
192
+ * @param {Element} value - The current node being iterated on
193
+ * @param {number} index - The current index in the iteration
194
+ * @param {NodeListOf<Element>} nodes - NodeList from querySelectorAll()
195
+ * @returns {void}
196
+ */
707
197
 
708
- else object.__defineGetter__(name, fn);
709
- };
198
+ (function(undefined) {
710
199
 
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
- }
200
+ // Detection from https://github.com/Financial-Times/polyfill-service/blob/master/packages/polyfill-library/polyfills/Object/defineProperty/detect.js
201
+ var detect = (
202
+ // In IE8, defineProperty could only act on DOM elements, so full support
203
+ // for the feature requires the ability to set a property on an arbitrary object
204
+ 'defineProperty' in Object && (function() {
205
+ try {
206
+ var a = {};
207
+ Object.defineProperty(a, 'test', {value:42});
208
+ return true;
209
+ } catch(e) {
210
+ return false
211
+ }
212
+ }())
213
+ );
718
214
 
215
+ if (detect) return
719
216
 
720
- var _DOMTokenList = function (el, prop) {
721
- var that = this;
722
- var tokens = [];
723
- var tokenMap = {};
724
- var length = 0;
725
- var maxLength = 0;
726
- var addIndexGetter = function (i) {
727
- defineGetter(that, i, function () {
728
- preop();
729
- return tokens[i];
730
- }, false);
217
+ // Polyfill from https://cdn.polyfill.io/v2/polyfill.js?features=Object.defineProperty&flags=always
218
+ (function (nativeDefineProperty) {
219
+
220
+ var supportsAccessors = Object.prototype.hasOwnProperty('__defineGetter__');
221
+ var ERR_ACCESSORS_NOT_SUPPORTED = 'Getters & setters cannot be defined on this javascript engine';
222
+ var ERR_VALUE_ACCESSORS = 'A property cannot both have accessors and be writable or have a value';
223
+
224
+ Object.defineProperty = function defineProperty(object, property, descriptor) {
225
+
226
+ // Where native support exists, assume it
227
+ if (nativeDefineProperty && (object === window || object === document || object === Element.prototype || object instanceof Element)) {
228
+ return nativeDefineProperty(object, property, descriptor);
229
+ }
230
+
231
+ if (object === null || !(object instanceof Object || typeof object === 'object')) {
232
+ throw new TypeError('Object.defineProperty called on non-object');
233
+ }
234
+
235
+ if (!(descriptor instanceof Object)) {
236
+ throw new TypeError('Property description must be an object');
237
+ }
238
+
239
+ var propertyString = String(property);
240
+ var hasValueOrWritable = 'value' in descriptor || 'writable' in descriptor;
241
+ var getterType = 'get' in descriptor && typeof descriptor.get;
242
+ var setterType = 'set' in descriptor && typeof descriptor.set;
243
+
244
+ // handle descriptor.get
245
+ if (getterType) {
246
+ if (getterType !== 'function') {
247
+ throw new TypeError('Getter must be a function');
248
+ }
249
+ if (!supportsAccessors) {
250
+ throw new TypeError(ERR_ACCESSORS_NOT_SUPPORTED);
251
+ }
252
+ if (hasValueOrWritable) {
253
+ throw new TypeError(ERR_VALUE_ACCESSORS);
254
+ }
255
+ Object.__defineGetter__.call(object, propertyString, descriptor.get);
256
+ } else {
257
+ object[propertyString] = descriptor.value;
258
+ }
259
+
260
+ // handle descriptor.set
261
+ if (setterType) {
262
+ if (setterType !== 'function') {
263
+ throw new TypeError('Setter must be a function');
264
+ }
265
+ if (!supportsAccessors) {
266
+ throw new TypeError(ERR_ACCESSORS_NOT_SUPPORTED);
267
+ }
268
+ if (hasValueOrWritable) {
269
+ throw new TypeError(ERR_VALUE_ACCESSORS);
270
+ }
271
+ Object.__defineSetter__.call(object, propertyString, descriptor.set);
272
+ }
273
+
274
+ // OK to define value unconditionally - if a getter has been specified as well, an error would be thrown above
275
+ if ('value' in descriptor) {
276
+ object[propertyString] = descriptor.value;
277
+ }
278
+
279
+ return object;
280
+ };
281
+ }(Object.defineProperty));
282
+ })
283
+ .call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
731
284
 
732
- };
733
- var reindex = function () {
285
+ (function(undefined) {
734
286
 
735
- /** Define getter functions for array-like access to the tokenList's contents. */
736
- if (length >= maxLength)
737
- for (; maxLength < length; ++maxLength) {
738
- addIndexGetter(maxLength);
739
- }
740
- };
287
+ // Detection from https://github.com/Financial-Times/polyfill-service/blob/master/packages/polyfill-library/polyfills/Document/detect.js
288
+ var detect = ("Document" in this);
741
289
 
742
- /** Helper function called at the start of each class method. Internal use only. */
743
- var preop = function () {
744
- var error;
745
- var i;
746
- var args = arguments;
747
- var rSpace = /\s+/;
748
-
749
- /** Validate the token/s passed to an instance method, if any. */
750
- if (args.length)
751
- for (i = 0; i < args.length; ++i)
752
- if (rSpace.test(args[i])) {
753
- error = new SyntaxError('String "' + args[i] + '" ' + "contains" + ' an invalid character');
754
- error.code = 5;
755
- error.name = "InvalidCharacterError";
756
- throw error;
757
- }
290
+ if (detect) return
758
291
 
292
+ // Polyfill from https://cdn.polyfill.io/v2/polyfill.js?features=Document&flags=always
293
+ if ((typeof WorkerGlobalScope === "undefined") && (typeof importScripts !== "function")) {
759
294
 
760
- /** Split the new value apart by whitespace*/
761
- if (typeof el[prop] === "object") {
762
- tokens = ("" + el[prop].baseVal).replace(/^\s+|\s+$/g, "").split(rSpace);
763
- } else {
764
- tokens = ("" + el[prop]).replace(/^\s+|\s+$/g, "").split(rSpace);
765
- }
295
+ if (this.HTMLDocument) { // IE8
766
296
 
767
- /** Avoid treating blank strings as single-item token lists */
768
- if ("" === tokens[0]) tokens = [];
297
+ // HTMLDocument is an extension of Document. If the browser has HTMLDocument but not Document, the former will suffice as an alias for the latter.
298
+ this.Document = this.HTMLDocument;
769
299
 
770
- /** Repopulate the internal token lists */
771
- tokenMap = {};
772
- for (i = 0; i < tokens.length; ++i)
773
- tokenMap[tokens[i]] = true;
774
- length = tokens.length;
775
- reindex();
776
- };
300
+ } else {
777
301
 
778
- /** Populate our internal token list if the targeted attribute of the subject element isn't empty. */
779
- preop();
302
+ // 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.
303
+ this.Document = this.HTMLDocument = document.constructor = (new Function('return function Document() {}')());
304
+ this.Document.prototype = document;
305
+ }
306
+ }
780
307
 
781
- /** Return the number of tokens in the underlying string. Read-only. */
782
- defineGetter(that, "length", function () {
783
- preop();
784
- return length;
785
- });
786
308
 
787
- /** Override the default toString/toLocaleString methods to return a space-delimited list of tokens when typecast. */
788
- that.toLocaleString =
789
- that.toString = function () {
790
- preop();
791
- return tokens.join(" ");
792
- };
309
+ })
310
+ .call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
793
311
 
794
- that.item = function (idx) {
795
- preop();
796
- return tokens[idx];
797
- };
312
+ (function(undefined) {
798
313
 
799
- that.contains = function (token) {
800
- preop();
801
- return !!tokenMap[token];
802
- };
314
+ // Detection from https://github.com/Financial-Times/polyfill-service/blob/master/packages/polyfill-library/polyfills/Element/detect.js
315
+ var detect = ('Element' in this && 'HTMLElement' in this);
803
316
 
804
- that.add = function () {
805
- preop.apply(that, args = arguments);
317
+ if (detect) return
806
318
 
807
- for (var args, token, i = 0, l = args.length; i < l; ++i) {
808
- token = args[i];
809
- if (!tokenMap[token]) {
810
- tokens.push(token);
811
- tokenMap[token] = true;
812
- }
813
- }
319
+ // Polyfill from https://cdn.polyfill.io/v2/polyfill.js?features=Element&flags=always
320
+ (function () {
814
321
 
815
- /** Update the targeted attribute of the attached element if the token list's changed. */
816
- if (length !== tokens.length) {
817
- length = tokens.length >>> 0;
818
- if (typeof el[prop] === "object") {
819
- el[prop].baseVal = tokens.join(" ");
820
- } else {
821
- el[prop] = tokens.join(" ");
822
- }
823
- reindex();
824
- }
825
- };
322
+ // IE8
323
+ if (window.Element && !window.HTMLElement) {
324
+ window.HTMLElement = window.Element;
325
+ return;
326
+ }
826
327
 
827
- that.remove = function () {
828
- preop.apply(that, args = arguments);
829
-
830
- /** Build a hash of token names to compare against when recollecting our token list. */
831
- for (var args, ignore = {}, i = 0, t = []; i < args.length; ++i) {
832
- ignore[args[i]] = true;
833
- delete tokenMap[args[i]];
834
- }
835
-
836
- /** Run through our tokens list and reassign only those that aren't defined in the hash declared above. */
837
- for (i = 0; i < tokens.length; ++i)
838
- if (!ignore[tokens[i]]) t.push(tokens[i]);
839
-
840
- tokens = t;
841
- length = t.length >>> 0;
842
-
843
- /** Update the targeted attribute of the attached element. */
844
- if (typeof el[prop] === "object") {
845
- el[prop].baseVal = tokens.join(" ");
846
- } else {
847
- el[prop] = tokens.join(" ");
848
- }
849
- reindex();
850
- };
328
+ // create Element constructor
329
+ window.Element = window.HTMLElement = new Function('return function Element() {}')();
330
+
331
+ // generate sandboxed iframe
332
+ var vbody = document.appendChild(document.createElement('body'));
333
+ var frame = vbody.appendChild(document.createElement('iframe'));
334
+
335
+ // use sandboxed iframe to replicate Element functionality
336
+ var frameDocument = frame.contentWindow.document;
337
+ var prototype = Element.prototype = frameDocument.appendChild(frameDocument.createElement('*'));
338
+ var cache = {};
339
+
340
+ // polyfill Element.prototype on an element
341
+ var shiv = function (element, deep) {
342
+ var
343
+ childNodes = element.childNodes || [],
344
+ index = -1,
345
+ key, value, childNode;
346
+
347
+ if (element.nodeType === 1 && element.constructor !== Element) {
348
+ element.constructor = Element;
349
+
350
+ for (key in cache) {
351
+ value = cache[key];
352
+ element[key] = value;
353
+ }
354
+ }
355
+
356
+ while (childNode = deep && childNodes[++index]) {
357
+ shiv(childNode, deep);
358
+ }
359
+
360
+ return element;
361
+ };
362
+
363
+ var elements = document.getElementsByTagName('*');
364
+ var nativeCreateElement = document.createElement;
365
+ var interval;
366
+ var loopLimit = 100;
367
+
368
+ prototype.attachEvent('onpropertychange', function (event) {
369
+ var
370
+ propertyName = event.propertyName,
371
+ nonValue = !cache.hasOwnProperty(propertyName),
372
+ newValue = prototype[propertyName],
373
+ oldValue = cache[propertyName],
374
+ index = -1,
375
+ element;
376
+
377
+ while (element = elements[++index]) {
378
+ if (element.nodeType === 1) {
379
+ if (nonValue || element[propertyName] === oldValue) {
380
+ element[propertyName] = newValue;
381
+ }
382
+ }
383
+ }
384
+
385
+ cache[propertyName] = newValue;
386
+ });
387
+
388
+ prototype.constructor = Element;
389
+
390
+ if (!prototype.hasAttribute) {
391
+ // <Element>.hasAttribute
392
+ prototype.hasAttribute = function hasAttribute(name) {
393
+ return this.getAttribute(name) !== null;
394
+ };
395
+ }
851
396
 
852
- that.toggle = function (token, force) {
853
- preop.apply(that, [token]);
397
+ // Apply Element prototype to the pre-existing DOM as soon as the body element appears.
398
+ function bodyCheck() {
399
+ if (!(loopLimit--)) clearTimeout(interval);
400
+ if (document.body && !document.body.prototype && /(complete|interactive)/.test(document.readyState)) {
401
+ shiv(document, true);
402
+ if (interval && document.body.prototype) clearTimeout(interval);
403
+ return (!!document.body.prototype);
404
+ }
405
+ return false;
406
+ }
407
+ if (!bodyCheck()) {
408
+ document.onreadystatechange = bodyCheck;
409
+ interval = setInterval(bodyCheck, 25);
410
+ }
854
411
 
855
- /** Token state's being forced. */
856
- if (undefined !== force) {
857
- if (force) {
858
- that.add(token);
859
- return true;
860
- } else {
861
- that.remove(token);
862
- return false;
863
- }
864
- }
412
+ // Apply to any new elements created after load
413
+ document.createElement = function createElement(nodeName) {
414
+ var element = nativeCreateElement(String(nodeName).toLowerCase());
415
+ return shiv(element);
416
+ };
865
417
 
866
- /** Token already exists in tokenList. Remove it, and return FALSE. */
867
- if (tokenMap[token]) {
868
- that.remove(token);
869
- return false;
870
- }
418
+ // remove sandboxed iframe
419
+ document.removeChild(vbody);
420
+ }());
871
421
 
872
- /** Otherwise, add the token and return TRUE. */
873
- that.add(token);
874
- return true;
875
- };
422
+ })
423
+ .call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
876
424
 
877
- return that;
878
- };
425
+ (function(undefined) {
879
426
 
880
- return _DOMTokenList;
881
- }());
427
+ // Detection from https://raw.githubusercontent.com/Financial-Times/polyfill-library/13cf7c340974d128d557580b5e2dafcd1b1192d1/polyfills/Element/prototype/dataset/detect.js
428
+ var detect = (function(){
429
+ if (!document.documentElement.dataset) {
430
+ return false;
882
431
  }
432
+ var el = document.createElement('div');
433
+ el.setAttribute("data-a-b", "c");
434
+ return el.dataset && el.dataset.aB == "c";
435
+ }());
883
436
 
884
- // Add second argument to native DOMTokenList.toggle() if necessary
885
- (function () {
886
- var e = document.createElement('span');
887
- if (!('classList' in e)) return;
888
- e.classList.toggle('x', false);
889
- if (!e.classList.contains('x')) return;
890
- e.classList.constructor.prototype.toggle = function toggle(token /*, force*/) {
891
- var force = arguments[1];
892
- if (force === undefined) {
893
- var add = !this.contains(token);
894
- this[add ? 'add' : 'remove'](token);
895
- return add;
896
- }
897
- force = !!force;
898
- this[force ? 'add' : 'remove'](token);
899
- return force;
900
- };
901
- }());
902
-
903
- // Add multiple arguments to native DOMTokenList.add() if necessary
904
- (function () {
905
- var e = document.createElement('span');
906
- if (!('classList' in e)) return;
907
- e.classList.add('a', 'b');
908
- if (e.classList.contains('b')) return;
909
- var native = e.classList.constructor.prototype.add;
910
- e.classList.constructor.prototype.add = function () {
911
- var args = arguments;
912
- var l = arguments.length;
913
- for (var i = 0; i < l; i++) {
914
- native.call(this, args[i]);
915
- }
916
- };
917
- }());
918
-
919
- // Add multiple arguments to native DOMTokenList.remove() if necessary
920
- (function () {
921
- var e = document.createElement('span');
922
- if (!('classList' in e)) return;
923
- e.classList.add('a');
924
- e.classList.add('b');
925
- e.classList.remove('a', 'b');
926
- if (!e.classList.contains('b')) return;
927
- var native = e.classList.constructor.prototype.remove;
928
- e.classList.constructor.prototype.remove = function () {
929
- var args = arguments;
930
- var l = arguments.length;
931
- for (var i = 0; i < l; i++) {
932
- native.call(this, args[i]);
933
- }
934
- };
935
- }());
437
+ if (detect) return
936
438
 
937
- }(this));
439
+ // Polyfill derived from https://raw.githubusercontent.com/Financial-Times/polyfill-library/13cf7c340974d128d557580b5e2dafcd1b1192d1/polyfills/Element/prototype/dataset/polyfill.js
440
+ Object.defineProperty(Element.prototype, 'dataset', {
441
+ get: function() {
442
+ var element = this;
443
+ var attributes = this.attributes;
444
+ var map = {};
445
+
446
+ for (var i = 0; i < attributes.length; i++) {
447
+ var attribute = attributes[i];
448
+
449
+ // This regex has been edited from the original polyfill, to add
450
+ // support for period (.) separators in data-* attribute names. These
451
+ // are allowed in the HTML spec, but were not covered by the original
452
+ // polyfill's regex. We use periods in our i18n implementation.
453
+ if (attribute && attribute.name && (/^data-\w[.\w-]*$/).test(attribute.name)) {
454
+ var name = attribute.name;
455
+ var value = attribute.value;
456
+
457
+ var propName = name.substr(5).replace(/-./g, function (prop) {
458
+ return prop.charAt(1).toUpperCase();
459
+ });
460
+
461
+ // If this browser supports __defineGetter__ and __defineSetter__,
462
+ // continue using defineProperty. If not (like IE 8 and below), we use
463
+ // a hacky fallback which at least gives an object in the right format
464
+ if ('__defineGetter__' in Object.prototype && '__defineSetter__' in Object.prototype) {
465
+ Object.defineProperty(map, propName, {
466
+ enumerable: true,
467
+ get: function() {
468
+ return this.value;
469
+ }.bind({value: value || ''}),
470
+ set: function setter(name, value) {
471
+ if (typeof value !== 'undefined') {
472
+ this.setAttribute(name, value);
473
+ } else {
474
+ this.removeAttribute(name);
475
+ }
476
+ }.bind(element, name)
477
+ });
478
+ } else {
479
+ map[propName] = value;
480
+ }
938
481
 
939
- }).call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
482
+ }
483
+ }
484
+
485
+ return map;
486
+ }
487
+ });
940
488
 
941
- (function(undefined) {
489
+ }).call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
942
490
 
943
- // Detection from https://raw.githubusercontent.com/Financial-Times/polyfill-service/8717a9e04ac7aff99b4980fbedead98036b0929a/packages/polyfill-library/polyfills/Element/prototype/classList/detect.js
944
- var detect = (
945
- 'document' in this && "classList" in document.documentElement && 'Element' in this && 'classList' in Element.prototype && (function () {
946
- var e = document.createElement('span');
947
- e.classList.add('a', 'b');
948
- return e.classList.contains('b');
949
- }())
950
- );
491
+ (function(undefined) {
951
492
 
952
- if (detect) return
493
+ // Detection from https://github.com/mdn/content/blob/cf607d68522cd35ee7670782d3ee3a361eaef2e4/files/en-us/web/javascript/reference/global_objects/string/trim/index.md#polyfill
494
+ var detect = ('trim' in String.prototype);
495
+
496
+ if (detect) return
953
497
 
954
- // Polyfill from https://cdn.polyfill.io/v2/polyfill.js?features=Element.prototype.classList&flags=always
955
- (function (global) {
956
- var dpSupport = true;
957
- var defineGetter = function (object, name, fn, configurable) {
958
- if (Object.defineProperty)
959
- Object.defineProperty(object, name, {
960
- configurable: false === dpSupport ? true : !!configurable,
961
- get: fn
962
- });
963
-
964
- else object.__defineGetter__(name, fn);
498
+ // Polyfill from https://github.com/mdn/content/blob/cf607d68522cd35ee7670782d3ee3a361eaef2e4/files/en-us/web/javascript/reference/global_objects/string/trim/index.md#polyfill
499
+ String.prototype.trim = function () {
500
+ return this.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
965
501
  };
966
- /** Ensure the browser allows Object.defineProperty to be used on native JavaScript objects. */
967
- try {
968
- defineGetter({}, "support");
969
- }
970
- catch (e) {
971
- dpSupport = false;
972
- }
973
- /** Polyfills a property with a DOMTokenList */
974
- var addProp = function (o, name, attr) {
975
-
976
- defineGetter(o.prototype, name, function () {
977
- var tokenList;
978
-
979
- var THIS = this,
980
-
981
- /** Prevent this from firing twice for some reason. What the hell, IE. */
982
- gibberishProperty = "__defineGetter__" + "DEFINE_PROPERTY" + name;
983
- if(THIS[gibberishProperty]) return tokenList;
984
- THIS[gibberishProperty] = true;
985
-
986
- /**
987
- * IE8 can't define properties on native JavaScript objects, so we'll use a dumb hack instead.
988
- *
989
- * What this is doing is creating a dummy element ("reflection") inside a detached phantom node ("mirror")
990
- * that serves as the target of Object.defineProperty instead. While we could simply use the subject HTML
991
- * element instead, this would conflict with element types which use indexed properties (such as forms and
992
- * select lists).
993
- */
994
- if (false === dpSupport) {
995
-
996
- var visage;
997
- var mirror = addProp.mirror || document.createElement("div");
998
- var reflections = mirror.childNodes;
999
- var l = reflections.length;
1000
-
1001
- for (var i = 0; i < l; ++i)
1002
- if (reflections[i]._R === THIS) {
1003
- visage = reflections[i];
1004
- break;
1005
- }
1006
-
1007
- /** Couldn't find an element's reflection inside the mirror. Materialise one. */
1008
- visage || (visage = mirror.appendChild(document.createElement("div")));
1009
-
1010
- tokenList = DOMTokenList.call(visage, THIS, attr);
1011
- } else tokenList = new DOMTokenList(THIS, attr);
1012
-
1013
- defineGetter(THIS, name, function () {
1014
- return tokenList;
1015
- });
1016
- delete THIS[gibberishProperty];
1017
502
 
1018
- return tokenList;
1019
- }, true);
1020
- };
503
+ }).call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
504
+
505
+ /* eslint-disable es-x/no-string-prototype-trim -- Polyfill imported */
1021
506
 
1022
- addProp(global.Element, "classList", "className");
1023
- addProp(global.HTMLElement, "classList", "className");
1024
- addProp(global.HTMLLinkElement, "relList", "rel");
1025
- addProp(global.HTMLAnchorElement, "relList", "rel");
1026
- addProp(global.HTMLAreaElement, "relList", "rel");
1027
- }(this));
1028
-
1029
- }).call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
1030
-
1031
- /**
1032
- * Common helpers which do not require polyfill.
1033
- *
1034
- * IMPORTANT: If a helper require a polyfill, please isolate it in its own module
1035
- * so that the polyfill can be properly tree-shaken and does not burden
1036
- * the components that do not need that helper
1037
- *
1038
- * @module common/index
1039
- */
1040
-
1041
- /**
1042
- * Config flattening function
1043
- *
1044
- * Takes any number of objects, flattens them into namespaced key-value pairs,
1045
- * (e.g. {'i18n.showSection': 'Show section'}) and combines them together, with
1046
- * greatest priority on the LAST item passed in.
1047
- *
1048
- * @returns {object} A flattened object of key-value pairs.
1049
- */
1050
- function mergeConfigs (/* configObject1, configObject2, ...configObjects */) {
1051
507
  /**
1052
- * Function to take nested objects and flatten them to a dot-separated keyed
1053
- * object. Doing this means we don't need to do any deep/recursive merging of
1054
- * each of our objects, nor transform our dataset from a flat list into a
1055
- * nested object.
508
+ * Normalise string
509
+ *
510
+ * 'If it looks like a duck, and it quacks like a duck…' 🦆
511
+ *
512
+ * If the passed value looks like a boolean or a number, convert it to a boolean
513
+ * or number.
514
+ *
515
+ * Designed to be used to convert config passed via data attributes (which are
516
+ * always strings) into something sensible.
1056
517
  *
1057
- * @param {object} configObject - Deeply nested object
1058
- * @returns {object} Flattened object with dot-separated keys
518
+ * @param {string} value - The value to normalise
519
+ * @returns {string | boolean | number | undefined} Normalised data
1059
520
  */
1060
- var flattenObject = function (configObject) {
1061
- // Prepare an empty return object
1062
- var flattenedObject = {};
1063
-
1064
- // Our flattening function, this is called recursively for each level of
1065
- // depth in the object. At each level we prepend the previous level names to
1066
- // the key using `prefix`.
1067
- var flattenLoop = function (obj, prefix) {
1068
- // Loop through keys...
1069
- for (var key in obj) {
1070
- // Check to see if this is a prototypical key/value,
1071
- // if it is, skip it.
1072
- if (!Object.prototype.hasOwnProperty.call(obj, key)) {
1073
- continue
1074
- }
1075
- var value = obj[key];
1076
- var prefixedKey = prefix ? prefix + '.' + key : key;
1077
- if (typeof value === 'object') {
1078
- // If the value is a nested object, recurse over that too
1079
- flattenLoop(value, prefixedKey);
1080
- } else {
1081
- // Otherwise, add this value to our return object
1082
- flattenedObject[prefixedKey] = value;
1083
- }
1084
- }
1085
- };
521
+ function normaliseString (value) {
522
+ if (typeof value !== 'string') {
523
+ return value
524
+ }
1086
525
 
1087
- // Kick off the recursive loop
1088
- flattenLoop(configObject);
1089
- return flattenedObject
1090
- };
526
+ var trimmedValue = value.trim();
1091
527
 
1092
- // Start with an empty object as our base
1093
- var formattedConfigObject = {};
1094
-
1095
- // Loop through each of the remaining passed objects and push their keys
1096
- // one-by-one into configObject. Any duplicate keys will override the existing
1097
- // key with the new value.
1098
- for (var i = 0; i < arguments.length; i++) {
1099
- var obj = flattenObject(arguments[i]);
1100
- for (var key in obj) {
1101
- if (Object.prototype.hasOwnProperty.call(obj, key)) {
1102
- formattedConfigObject[key] = obj[key];
1103
- }
528
+ if (trimmedValue === 'true') {
529
+ return true
1104
530
  }
1105
- }
1106
531
 
1107
- return formattedConfigObject
1108
- }
1109
-
1110
- /**
1111
- * Extracts keys starting with a particular namespace from a flattened config
1112
- * object, removing the namespace in the process.
1113
- *
1114
- * @param {object} configObject - The object to extract key-value pairs from.
1115
- * @param {string} namespace - The namespace to filter keys with.
1116
- * @returns {object} Flattened object with dot-separated key namespace removed
1117
- */
1118
- function extractConfigByNamespace (configObject, namespace) {
1119
- // Check we have what we need
1120
- if (!configObject || typeof configObject !== 'object') {
1121
- throw new Error('Provide a `configObject` of type "object".')
1122
- }
1123
- if (!namespace || typeof namespace !== 'string') {
1124
- throw new Error('Provide a `namespace` of type "string" to filter the `configObject` by.')
1125
- }
1126
- var newObject = {};
1127
- for (var key in configObject) {
1128
- // Split the key into parts, using . as our namespace separator
1129
- var keyParts = key.split('.');
1130
- // Check if the first namespace matches the configured namespace
1131
- if (Object.prototype.hasOwnProperty.call(configObject, key) && keyParts[0] === namespace) {
1132
- // Remove the first item (the namespace) from the parts array,
1133
- // but only if there is more than one part (we don't want blank keys!)
1134
- if (keyParts.length > 1) {
1135
- keyParts.shift();
1136
- }
1137
- // Join the remaining parts back together
1138
- var newKey = keyParts.join('.');
1139
- // Add them to our new object
1140
- newObject[newKey] = configObject[key];
532
+ if (trimmedValue === 'false') {
533
+ return false
1141
534
  }
1142
- }
1143
- return newObject
1144
- }
1145
-
1146
- /**
1147
- * @callback nodeListIterator
1148
- * @param {Element} value - The current node being iterated on
1149
- * @param {number} index - The current index in the iteration
1150
- * @param {NodeListOf<Element>} nodes - NodeList from querySelectorAll()
1151
- * @returns {undefined}
1152
- */
1153
-
1154
- /**
1155
- * Internal support for selecting messages to render, with placeholder
1156
- * interpolation and locale-aware number formatting and pluralisation
1157
- *
1158
- * @class
1159
- * @private
1160
- * @param {TranslationsFlattened} translations - Key-value pairs of the translation strings to use.
1161
- * @param {object} [config] - Configuration options for the function.
1162
- * @param {string} config.locale - An overriding locale for the PluralRules functionality.
1163
- */
1164
- function I18n (translations, config) {
1165
- // Make list of translations available throughout function
1166
- this.translations = translations || {};
1167
-
1168
- // The locale to use for PluralRules and NumberFormat
1169
- this.locale = (config && config.locale) || document.documentElement.lang || 'en';
1170
- }
1171
-
1172
- /**
1173
- * The most used function - takes the key for a given piece of UI text and
1174
- * returns the appropriate string.
1175
- *
1176
- * @param {string} lookupKey - The lookup key of the string to use.
1177
- * @param {object} options - Any options passed with the translation string, e.g: for string interpolation.
1178
- * @returns {string} The appropriate translation string.
1179
- */
1180
- I18n.prototype.t = function (lookupKey, options) {
1181
- if (!lookupKey) {
1182
- // Print a console error if no lookup key has been provided
1183
- throw new Error('i18n: lookup key missing')
1184
- }
1185
-
1186
- // If the `count` option is set, determine which plural suffix is needed and
1187
- // change the lookupKey to match. We check to see if it's undefined instead of
1188
- // falsy, as this could legitimately be 0.
1189
- if (options && typeof options.count !== 'undefined') {
1190
- // Get the plural suffix
1191
- lookupKey = lookupKey + '.' + this.getPluralSuffix(lookupKey, options.count);
1192
- }
1193
535
 
1194
- if (lookupKey in this.translations) {
1195
- // Fetch the translation string for that lookup key
1196
- var translationString = this.translations[lookupKey];
1197
-
1198
- // Check for ${} placeholders in the translation string
1199
- if (translationString.match(/%{(.\S+)}/)) {
1200
- if (!options) {
1201
- throw new Error('i18n: cannot replace placeholders in string if no option data provided')
1202
- }
1203
-
1204
- return this.replacePlaceholders(translationString, options)
1205
- } else {
1206
- return translationString
536
+ // Empty / whitespace-only strings are considered finite so we need to check
537
+ // the length of the trimmed string as well
538
+ if (trimmedValue.length > 0 && isFinite(trimmedValue)) {
539
+ return Number(trimmedValue)
1207
540
  }
1208
- } else {
1209
- // If the key wasn't found in our translations object,
1210
- // return the lookup key itself as the fallback
1211
- return lookupKey
1212
- }
1213
- };
1214
-
1215
- /**
1216
- * Takes a translation string with placeholders, and replaces the placeholders
1217
- * with the provided data
1218
- *
1219
- * @param {string} translationString - The translation string
1220
- * @param {object} options - Any options passed with the translation string, e.g: for string interpolation.
1221
- * @returns {string} The translation string to output, with ${} placeholders replaced
1222
- */
1223
- I18n.prototype.replacePlaceholders = function (translationString, options) {
1224
- var formatter;
1225
-
1226
- if (this.hasIntlNumberFormatSupport()) {
1227
- formatter = new Intl.NumberFormat(this.locale);
1228
- }
1229
541
 
1230
- return translationString.replace(/%{(.\S+)}/g, function (placeholderWithBraces, placeholderKey) {
1231
- if (Object.prototype.hasOwnProperty.call(options, placeholderKey)) {
1232
- var placeholderValue = options[placeholderKey];
1233
-
1234
- // If a user has passed `false` as the value for the placeholder
1235
- // treat it as though the value should not be displayed
1236
- if (placeholderValue === false) {
1237
- return ''
1238
- }
542
+ return value
543
+ }
1239
544
 
1240
- // If the placeholder's value is a number, localise the number formatting
1241
- if (typeof placeholderValue === 'number' && formatter) {
1242
- return formatter.format(placeholderValue)
1243
- }
545
+ /**
546
+ * Normalise dataset
547
+ *
548
+ * Loop over an object and normalise each value using normaliseData function
549
+ *
550
+ * @param {DOMStringMap} dataset - HTML element dataset
551
+ * @returns {Object<string, unknown>} Normalised dataset
552
+ */
553
+ function normaliseDataset (dataset) {
554
+ var out = {};
1244
555
 
1245
- return placeholderValue
1246
- } else {
1247
- throw new Error('i18n: no data found to replace ' + placeholderWithBraces + ' placeholder in string')
556
+ for (var key in dataset) {
557
+ out[key] = normaliseString(dataset[key]);
1248
558
  }
1249
- })
1250
- };
1251
-
1252
- /**
1253
- * Check to see if the browser supports Intl and Intl.PluralRules.
1254
- *
1255
- * It requires all conditions to be met in order to be supported:
1256
- * - The browser supports the Intl class (true in IE11)
1257
- * - The implementation of Intl supports PluralRules (NOT true in IE11)
1258
- * - The browser/OS has plural rules for the current locale (browser dependent)
1259
- *
1260
- * @returns {boolean} Returns true if all conditions are met. Returns false otherwise.
1261
- */
1262
- I18n.prototype.hasIntlPluralRulesSupport = function () {
1263
- return Boolean(window.Intl && ('PluralRules' in window.Intl && Intl.PluralRules.supportedLocalesOf(this.locale).length))
1264
- };
1265
-
1266
- /**
1267
- * Check to see if the browser supports Intl and Intl.NumberFormat.
1268
- *
1269
- * It requires all conditions to be met in order to be supported:
1270
- * - The browser supports the Intl class (true in IE11)
1271
- * - The implementation of Intl supports NumberFormat (also true in IE11)
1272
- * - The browser/OS has number formatting rules for the current locale (browser dependent)
1273
- *
1274
- * @returns {boolean} Returns true if all conditions are met. Returns false otherwise.
1275
- */
1276
- I18n.prototype.hasIntlNumberFormatSupport = function () {
1277
- return Boolean(window.Intl && ('NumberFormat' in window.Intl && Intl.NumberFormat.supportedLocalesOf(this.locale).length))
1278
- };
1279
-
1280
- /**
1281
- * Get the appropriate suffix for the plural form.
1282
- *
1283
- * Uses Intl.PluralRules (or our own fallback implementation) to get the
1284
- * 'preferred' form to use for the given count.
1285
- *
1286
- * Checks that a translation has been provided for that plural form – if it
1287
- * hasn't, it'll fall back to the 'other' plural form (unless that doesn't exist
1288
- * either, in which case an error will be thrown)
1289
- *
1290
- * @param {string} lookupKey - The lookup key of the string to use.
1291
- * @param {number} count - Number used to determine which pluralisation to use.
1292
- * @returns {PluralRule} The suffix associated with the correct pluralisation for this locale.
1293
- */
1294
- I18n.prototype.getPluralSuffix = function (lookupKey, count) {
1295
- // Validate that the number is actually a number.
1296
- //
1297
- // Number(count) will turn anything that can't be converted to a Number type
1298
- // into 'NaN'. isFinite filters out NaN, as it isn't a finite number.
1299
- count = Number(count);
1300
- if (!isFinite(count)) { return 'other' }
1301
-
1302
- var preferredForm;
1303
-
1304
- // Check to verify that all the requirements for Intl.PluralRules are met.
1305
- // If so, we can use that instead of our custom implementation. Otherwise,
1306
- // use the hardcoded fallback.
1307
- if (this.hasIntlPluralRulesSupport()) {
1308
- preferredForm = new Intl.PluralRules(this.locale).select(count);
1309
- } else {
1310
- preferredForm = this.selectPluralFormUsingFallbackRules(count);
559
+
560
+ return out
1311
561
  }
1312
562
 
1313
- // Use the correct plural form if provided
1314
- if (lookupKey + '.' + preferredForm in this.translations) {
1315
- return preferredForm
1316
- // Fall back to `other` if the plural form is missing, but log a warning
1317
- // to the console
1318
- } else if (lookupKey + '.other' in this.translations) {
1319
- if (console && 'warn' in console) {
1320
- console.warn('i18n: Missing plural form ".' + preferredForm + '" for "' +
1321
- this.locale + '" locale. Falling back to ".other".');
1322
- }
563
+ /**
564
+ * Internal support for selecting messages to render, with placeholder
565
+ * interpolation and locale-aware number formatting and pluralisation
566
+ *
567
+ * @class
568
+ * @private
569
+ * @param {Object<string, unknown>} translations - Key-value pairs of the translation strings to use.
570
+ * @param {object} [config] - Configuration options for the function.
571
+ * @param {string} [config.locale] - An overriding locale for the PluralRules functionality.
572
+ */
573
+ function I18n (translations, config) {
574
+ // Make list of translations available throughout function
575
+ this.translations = translations || {};
1323
576
 
1324
- return 'other'
1325
- // If the required `other` plural form is missing, all we can do is error
1326
- } else {
1327
- throw new Error(
1328
- 'i18n: Plural form ".other" is required for "' + this.locale + '" locale'
1329
- )
1330
- }
1331
- };
1332
-
1333
- /**
1334
- * Get the plural form using our fallback implementation
1335
- *
1336
- * This is split out into a separate function to make it easier to test the
1337
- * fallback behaviour in an environment where Intl.PluralRules exists.
1338
- *
1339
- * @param {number} count - Number used to determine which pluralisation to use.
1340
- * @returns {PluralRule} The pluralisation form for count in this locale.
1341
- */
1342
- I18n.prototype.selectPluralFormUsingFallbackRules = function (count) {
1343
- // Currently our custom code can only handle positive integers, so let's
1344
- // make sure our number is one of those.
1345
- count = Math.abs(Math.floor(count));
1346
-
1347
- var ruleset = this.getPluralRulesForLocale();
1348
-
1349
- if (ruleset) {
1350
- return I18n.pluralRules[ruleset](count)
577
+ // The locale to use for PluralRules and NumberFormat
578
+ this.locale = (config && config.locale) || document.documentElement.lang || 'en';
1351
579
  }
1352
580
 
1353
- return 'other'
1354
- };
1355
-
1356
- /**
1357
- * Work out which pluralisation rules to use for the current locale
1358
- *
1359
- * The locale may include a regional indicator (such as en-GB), but we don't
1360
- * usually care about this part, as pluralisation rules are usually the same
1361
- * regardless of region. There are exceptions, however, (e.g. Portuguese) so
1362
- * this searches by both the full and shortened locale codes, just to be sure.
1363
- *
1364
- * @returns {PluralRuleName | undefined} The name of the pluralisation rule to use (a key for one
1365
- * of the functions in this.pluralRules)
1366
- */
1367
- I18n.prototype.getPluralRulesForLocale = function () {
1368
- var locale = this.locale;
1369
- var localeShort = locale.split('-')[0];
1370
-
1371
- // Look through the plural rules map to find which `pluralRule` is
1372
- // appropriate for our current `locale`.
1373
- for (var pluralRule in I18n.pluralRulesMap) {
1374
- if (Object.prototype.hasOwnProperty.call(I18n.pluralRulesMap, pluralRule)) {
1375
- var languages = I18n.pluralRulesMap[pluralRule];
1376
- for (var i = 0; i < languages.length; i++) {
1377
- if (languages[i] === locale || languages[i] === localeShort) {
1378
- return pluralRule
1379
- }
1380
- }
1381
- }
1382
- }
1383
- };
1384
-
1385
- /**
1386
- * Map of plural rules to languages where those rules apply.
1387
- *
1388
- * Note: These groups are named for the most dominant or recognisable language
1389
- * that uses each system. The groupings do not imply that the languages are
1390
- * related to one another. Many languages have evolved the same systems
1391
- * independently of one another.
1392
- *
1393
- * Code to support more languages can be found in the i18n spike:
1394
- * {@link https://github.com/alphagov/govuk-frontend/blob/spike-i18n-support/src/govuk/i18n.mjs}
1395
- *
1396
- * Languages currently supported:
1397
- *
1398
- * Arabic: Arabic (ar)
1399
- * Chinese: Burmese (my), Chinese (zh), Indonesian (id), Japanese (ja),
1400
- * Javanese (jv), Korean (ko), Malay (ms), Thai (th), Vietnamese (vi)
1401
- * French: Armenian (hy), Bangla (bn), French (fr), Gujarati (gu), Hindi (hi),
1402
- * Persian Farsi (fa), Punjabi (pa), Zulu (zu)
1403
- * German: Afrikaans (af), Albanian (sq), Azerbaijani (az), Basque (eu),
1404
- * Bulgarian (bg), Catalan (ca), Danish (da), Dutch (nl), English (en),
1405
- * Estonian (et), Finnish (fi), Georgian (ka), German (de), Greek (el),
1406
- * Hungarian (hu), Luxembourgish (lb), Norwegian (no), Somali (so),
1407
- * Swahili (sw), Swedish (sv), Tamil (ta), Telugu (te), Turkish (tr),
1408
- * Urdu (ur)
1409
- * Irish: Irish Gaelic (ga)
1410
- * Russian: Russian (ru), Ukrainian (uk)
1411
- * Scottish: Scottish Gaelic (gd)
1412
- * Spanish: European Portuguese (pt-PT), Italian (it), Spanish (es)
1413
- * Welsh: Welsh (cy)
1414
- *
1415
- * @type {Object<PluralRuleName, string[]>}
1416
- */
1417
- I18n.pluralRulesMap = {
1418
- arabic: ['ar'],
1419
- chinese: ['my', 'zh', 'id', 'ja', 'jv', 'ko', 'ms', 'th', 'vi'],
1420
- french: ['hy', 'bn', 'fr', 'gu', 'hi', 'fa', 'pa', 'zu'],
1421
- german: [
1422
- 'af', 'sq', 'az', 'eu', 'bg', 'ca', 'da', 'nl', 'en', 'et', 'fi', 'ka',
1423
- 'de', 'el', 'hu', 'lb', 'no', 'so', 'sw', 'sv', 'ta', 'te', 'tr', 'ur'
1424
- ],
1425
- irish: ['ga'],
1426
- russian: ['ru', 'uk'],
1427
- scottish: ['gd'],
1428
- spanish: ['pt-PT', 'it', 'es'],
1429
- welsh: ['cy']
1430
- };
1431
-
1432
- /**
1433
- * Different pluralisation rule sets
1434
- *
1435
- * Returns the appropriate suffix for the plural form associated with `n`.
1436
- * Possible suffixes: 'zero', 'one', 'two', 'few', 'many', 'other' (the actual
1437
- * meaning of each differs per locale). 'other' should always exist, even in
1438
- * languages without plurals, such as Chinese.
1439
- * {@link https://cldr.unicode.org/index/cldr-spec/plural-rules}
1440
- *
1441
- * The count must be a positive integer. Negative numbers and decimals aren't accounted for
1442
- *
1443
- * @type {Object<string, function(number): PluralRule>}
1444
- */
1445
- I18n.pluralRules = {
1446
- arabic: function (n) {
1447
- if (n === 0) { return 'zero' }
1448
- if (n === 1) { return 'one' }
1449
- if (n === 2) { return 'two' }
1450
- if (n % 100 >= 3 && n % 100 <= 10) { return 'few' }
1451
- if (n % 100 >= 11 && n % 100 <= 99) { return 'many' }
1452
- return 'other'
1453
- },
1454
- chinese: function () {
1455
- return 'other'
1456
- },
1457
- french: function (n) {
1458
- return n === 0 || n === 1 ? 'one' : 'other'
1459
- },
1460
- german: function (n) {
1461
- return n === 1 ? 'one' : 'other'
1462
- },
1463
- irish: function (n) {
1464
- if (n === 1) { return 'one' }
1465
- if (n === 2) { return 'two' }
1466
- if (n >= 3 && n <= 6) { return 'few' }
1467
- if (n >= 7 && n <= 10) { return 'many' }
1468
- return 'other'
1469
- },
1470
- russian: function (n) {
1471
- var lastTwo = n % 100;
1472
- var last = lastTwo % 10;
1473
- if (last === 1 && lastTwo !== 11) { return 'one' }
1474
- if (last >= 2 && last <= 4 && !(lastTwo >= 12 && lastTwo <= 14)) { return 'few' }
1475
- if (last === 0 || (last >= 5 && last <= 9) || (lastTwo >= 11 && lastTwo <= 14)) { return 'many' }
1476
- // Note: The 'other' suffix is only used by decimal numbers in Russian.
1477
- // We don't anticipate it being used, but it's here for consistency.
1478
- return 'other'
1479
- },
1480
- scottish: function (n) {
1481
- if (n === 1 || n === 11) { return 'one' }
1482
- if (n === 2 || n === 12) { return 'two' }
1483
- if ((n >= 3 && n <= 10) || (n >= 13 && n <= 19)) { return 'few' }
1484
- return 'other'
1485
- },
1486
- spanish: function (n) {
1487
- if (n === 1) { return 'one' }
1488
- if (n % 1000000 === 0 && n !== 0) { return 'many' }
1489
- return 'other'
1490
- },
1491
- welsh: function (n) {
1492
- if (n === 0) { return 'zero' }
1493
- if (n === 1) { return 'one' }
1494
- if (n === 2) { return 'two' }
1495
- if (n === 3) { return 'few' }
1496
- if (n === 6) { return 'many' }
1497
- return 'other'
1498
- }
1499
- };
1500
-
1501
- /**
1502
- * Supported languages for plural rules
1503
- *
1504
- * @typedef {'arabic' | 'chinese' | 'french' | 'german' | 'irish' | 'russian' | 'scottish' | 'spanish' | 'welsh'} PluralRuleName
1505
- */
1506
-
1507
- /**
1508
- * Plural rule category mnemonic tags
1509
- *
1510
- * @typedef {'zero' | 'one' | 'two' | 'few' | 'many' | 'other'} PluralRule
1511
- */
1512
-
1513
- /**
1514
- * Translated message by plural rule they correspond to.
1515
- *
1516
- * Allows to group pluralised messages under a single key when passing
1517
- * translations to a component's constructor
1518
- *
1519
- * @typedef {object} TranslationPluralForms
1520
- * @property {string} [other] - General plural form
1521
- * @property {string} [zero] - Plural form used with 0
1522
- * @property {string} [one] - Plural form used with 1
1523
- * @property {string} [two] - Plural form used with 2
1524
- * @property {string} [few] - Plural form used for a few
1525
- * @property {string} [many] - Plural form used for many
1526
- */
1527
-
1528
- /**
1529
- * Translated messages (flattened)
1530
- *
1531
- * @private
1532
- * @typedef {Object<string, string> | {}} TranslationsFlattened
1533
- */
1534
-
1535
- (function(undefined) {
1536
-
1537
- // Detection from https://raw.githubusercontent.com/Financial-Times/polyfill-library/13cf7c340974d128d557580b5e2dafcd1b1192d1/polyfills/Element/prototype/dataset/detect.js
1538
- var detect = (function(){
1539
- if (!document.documentElement.dataset) {
1540
- return false;
581
+ /**
582
+ * The most used function - takes the key for a given piece of UI text and
583
+ * returns the appropriate string.
584
+ *
585
+ * @param {string} lookupKey - The lookup key of the string to use.
586
+ * @param {Object<string, unknown>} [options] - Any options passed with the translation string, e.g: for string interpolation.
587
+ * @returns {string} The appropriate translation string.
588
+ * @throws {Error} Lookup key required
589
+ * @throws {Error} Options required for `${}` placeholders
590
+ */
591
+ I18n.prototype.t = function (lookupKey, options) {
592
+ if (!lookupKey) {
593
+ // Print a console error if no lookup key has been provided
594
+ throw new Error('i18n: lookup key missing')
1541
595
  }
1542
- var el = document.createElement('div');
1543
- el.setAttribute("data-a-b", "c");
1544
- return el.dataset && el.dataset.aB == "c";
1545
- }());
1546
596
 
1547
- if (detect) return
597
+ // If the `count` option is set, determine which plural suffix is needed and
598
+ // change the lookupKey to match. We check to see if it's undefined instead of
599
+ // falsy, as this could legitimately be 0.
600
+ if (options && typeof options.count !== 'undefined') {
601
+ // Get the plural suffix
602
+ lookupKey = lookupKey + '.' + this.getPluralSuffix(lookupKey, options.count);
603
+ }
1548
604
 
1549
- // Polyfill derived from https://raw.githubusercontent.com/Financial-Times/polyfill-library/13cf7c340974d128d557580b5e2dafcd1b1192d1/polyfills/Element/prototype/dataset/polyfill.js
1550
- Object.defineProperty(Element.prototype, 'dataset', {
1551
- get: function() {
1552
- var element = this;
1553
- var attributes = this.attributes;
1554
- var map = {};
1555
-
1556
- for (var i = 0; i < attributes.length; i++) {
1557
- var attribute = attributes[i];
1558
-
1559
- // This regex has been edited from the original polyfill, to add
1560
- // support for period (.) separators in data-* attribute names. These
1561
- // are allowed in the HTML spec, but were not covered by the original
1562
- // polyfill's regex. We use periods in our i18n implementation.
1563
- if (attribute && attribute.name && (/^data-\w[.\w-]*$/).test(attribute.name)) {
1564
- var name = attribute.name;
1565
- var value = attribute.value;
1566
-
1567
- var propName = name.substr(5).replace(/-./g, function (prop) {
1568
- return prop.charAt(1).toUpperCase();
1569
- });
1570
-
1571
- // If this browser supports __defineGetter__ and __defineSetter__,
1572
- // continue using defineProperty. If not (like IE 8 and below), we use
1573
- // a hacky fallback which at least gives an object in the right format
1574
- if ('__defineGetter__' in Object.prototype && '__defineSetter__' in Object.prototype) {
1575
- Object.defineProperty(map, propName, {
1576
- enumerable: true,
1577
- get: function() {
1578
- return this.value;
1579
- }.bind({value: value || ''}),
1580
- set: function setter(name, value) {
1581
- if (typeof value !== 'undefined') {
1582
- this.setAttribute(name, value);
1583
- } else {
1584
- this.removeAttribute(name);
1585
- }
1586
- }.bind(element, name)
1587
- });
1588
- } else {
1589
- map[propName] = value;
1590
- }
605
+ if (lookupKey in this.translations) {
606
+ // Fetch the translation string for that lookup key
607
+ var translationString = this.translations[lookupKey];
1591
608
 
609
+ // Check for ${} placeholders in the translation string
610
+ if (translationString.match(/%{(.\S+)}/)) {
611
+ if (!options) {
612
+ throw new Error('i18n: cannot replace placeholders in string if no option data provided')
1592
613
  }
614
+
615
+ return this.replacePlaceholders(translationString, options)
616
+ } else {
617
+ return translationString
1593
618
  }
1594
-
1595
- return map;
619
+ } else {
620
+ // If the key wasn't found in our translations object,
621
+ // return the lookup key itself as the fallback
622
+ return lookupKey
1596
623
  }
1597
- });
1598
-
1599
- }).call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
1600
-
1601
- (function(undefined) {
624
+ };
1602
625
 
1603
- // Detection from https://github.com/mdn/content/blob/cf607d68522cd35ee7670782d3ee3a361eaef2e4/files/en-us/web/javascript/reference/global_objects/string/trim/index.md#polyfill
1604
- var detect = ('trim' in String.prototype);
1605
-
1606
- if (detect) return
626
+ /**
627
+ * Takes a translation string with placeholders, and replaces the placeholders
628
+ * with the provided data
629
+ *
630
+ * @param {string} translationString - The translation string
631
+ * @param {Object<string, unknown>} options - Any options passed with the translation string, e.g: for string interpolation.
632
+ * @returns {string} The translation string to output, with ${} placeholders replaced
633
+ */
634
+ I18n.prototype.replacePlaceholders = function (translationString, options) {
635
+ var formatter;
1607
636
 
1608
- // Polyfill from https://github.com/mdn/content/blob/cf607d68522cd35ee7670782d3ee3a361eaef2e4/files/en-us/web/javascript/reference/global_objects/string/trim/index.md#polyfill
1609
- String.prototype.trim = function () {
1610
- return this.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
1611
- };
637
+ if (this.hasIntlNumberFormatSupport()) {
638
+ formatter = new Intl.NumberFormat(this.locale);
639
+ }
1612
640
 
1613
- }).call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
1614
-
1615
- /**
1616
- * Normalise string
1617
- *
1618
- * 'If it looks like a duck, and it quacks like a duck…' 🦆
1619
- *
1620
- * If the passed value looks like a boolean or a number, convert it to a boolean
1621
- * or number.
1622
- *
1623
- * Designed to be used to convert config passed via data attributes (which are
1624
- * always strings) into something sensible.
1625
- *
1626
- * @param {string} value - The value to normalise
1627
- * @returns {string | boolean | number | undefined} Normalised data
1628
- */
1629
- function normaliseString (value) {
1630
- if (typeof value !== 'string') {
1631
- return value
1632
- }
641
+ return translationString.replace(/%{(.\S+)}/g, function (placeholderWithBraces, placeholderKey) {
642
+ if (Object.prototype.hasOwnProperty.call(options, placeholderKey)) {
643
+ var placeholderValue = options[placeholderKey];
1633
644
 
1634
- var trimmedValue = value.trim();
645
+ // If a user has passed `false` as the value for the placeholder
646
+ // treat it as though the value should not be displayed
647
+ if (placeholderValue === false) {
648
+ return ''
649
+ }
1635
650
 
1636
- if (trimmedValue === 'true') {
1637
- return true
1638
- }
651
+ // If the placeholder's value is a number, localise the number formatting
652
+ if (typeof placeholderValue === 'number' && formatter) {
653
+ return formatter.format(placeholderValue)
654
+ }
1639
655
 
1640
- if (trimmedValue === 'false') {
1641
- return false
1642
- }
656
+ return placeholderValue
657
+ } else {
658
+ throw new Error('i18n: no data found to replace ' + placeholderWithBraces + ' placeholder in string')
659
+ }
660
+ })
661
+ };
1643
662
 
1644
- // Empty / whitespace-only strings are considered finite so we need to check
1645
- // the length of the trimmed string as well
1646
- if (trimmedValue.length > 0 && isFinite(trimmedValue)) {
1647
- return Number(trimmedValue)
1648
- }
663
+ /**
664
+ * Check to see if the browser supports Intl and Intl.PluralRules.
665
+ *
666
+ * It requires all conditions to be met in order to be supported:
667
+ * - The browser supports the Intl class (true in IE11)
668
+ * - The implementation of Intl supports PluralRules (NOT true in IE11)
669
+ * - The browser/OS has plural rules for the current locale (browser dependent)
670
+ *
671
+ * @returns {boolean} Returns true if all conditions are met. Returns false otherwise.
672
+ */
673
+ I18n.prototype.hasIntlPluralRulesSupport = function () {
674
+ return Boolean(window.Intl && ('PluralRules' in window.Intl && Intl.PluralRules.supportedLocalesOf(this.locale).length))
675
+ };
1649
676
 
1650
- return value
1651
- }
1652
-
1653
- /**
1654
- * Normalise dataset
1655
- *
1656
- * Loop over an object and normalise each value using normaliseData function
1657
- *
1658
- * @param {DOMStringMap} dataset - HTML element dataset
1659
- * @returns {Object<string, string | boolean | number | undefined>} Normalised dataset
1660
- */
1661
- function normaliseDataset (dataset) {
1662
- var out = {};
1663
-
1664
- for (var key in dataset) {
1665
- out[key] = normaliseString(dataset[key]);
1666
- }
677
+ /**
678
+ * Check to see if the browser supports Intl and Intl.NumberFormat.
679
+ *
680
+ * It requires all conditions to be met in order to be supported:
681
+ * - The browser supports the Intl class (true in IE11)
682
+ * - The implementation of Intl supports NumberFormat (also true in IE11)
683
+ * - The browser/OS has number formatting rules for the current locale (browser dependent)
684
+ *
685
+ * @returns {boolean} Returns true if all conditions are met. Returns false otherwise.
686
+ */
687
+ I18n.prototype.hasIntlNumberFormatSupport = function () {
688
+ return Boolean(window.Intl && ('NumberFormat' in window.Intl && Intl.NumberFormat.supportedLocalesOf(this.locale).length))
689
+ };
1667
690
 
1668
- return out
1669
- }
691
+ /**
692
+ * Get the appropriate suffix for the plural form.
693
+ *
694
+ * Uses Intl.PluralRules (or our own fallback implementation) to get the
695
+ * 'preferred' form to use for the given count.
696
+ *
697
+ * Checks that a translation has been provided for that plural form – if it
698
+ * hasn't, it'll fall back to the 'other' plural form (unless that doesn't exist
699
+ * either, in which case an error will be thrown)
700
+ *
701
+ * @param {string} lookupKey - The lookup key of the string to use.
702
+ * @param {number} count - Number used to determine which pluralisation to use.
703
+ * @returns {PluralRule} The suffix associated with the correct pluralisation for this locale.
704
+ * @throws {Error} Plural form `.other` required when preferred plural form is missing
705
+ */
706
+ I18n.prototype.getPluralSuffix = function (lookupKey, count) {
707
+ // Validate that the number is actually a number.
708
+ //
709
+ // Number(count) will turn anything that can't be converted to a Number type
710
+ // into 'NaN'. isFinite filters out NaN, as it isn't a finite number.
711
+ count = Number(count);
712
+ if (!isFinite(count)) { return 'other' }
713
+
714
+ var preferredForm;
715
+
716
+ // Check to verify that all the requirements for Intl.PluralRules are met.
717
+ // If so, we can use that instead of our custom implementation. Otherwise,
718
+ // use the hardcoded fallback.
719
+ if (this.hasIntlPluralRulesSupport()) {
720
+ preferredForm = new Intl.PluralRules(this.locale).select(count);
721
+ } else {
722
+ preferredForm = this.selectPluralFormUsingFallbackRules(count);
723
+ }
1670
724
 
1671
- (function(undefined) {
725
+ // Use the correct plural form if provided
726
+ if (lookupKey + '.' + preferredForm in this.translations) {
727
+ return preferredForm
728
+ // Fall back to `other` if the plural form is missing, but log a warning
729
+ // to the console
730
+ } else if (lookupKey + '.other' in this.translations) {
731
+ if (console && 'warn' in console) {
732
+ console.warn('i18n: Missing plural form ".' + preferredForm + '" for "' +
733
+ this.locale + '" locale. Falling back to ".other".');
734
+ }
1672
735
 
1673
- // Detection from https://raw.githubusercontent.com/Financial-Times/polyfill-service/1f3c09b402f65bf6e393f933a15ba63f1b86ef1f/packages/polyfill-library/polyfills/Element/prototype/matches/detect.js
1674
- var detect = (
1675
- 'document' in this && "matches" in document.documentElement
1676
- );
736
+ return 'other'
737
+ // If the required `other` plural form is missing, all we can do is error
738
+ } else {
739
+ throw new Error(
740
+ 'i18n: Plural form ".other" is required for "' + this.locale + '" locale'
741
+ )
742
+ }
743
+ };
1677
744
 
1678
- if (detect) return
745
+ /**
746
+ * Get the plural form using our fallback implementation
747
+ *
748
+ * This is split out into a separate function to make it easier to test the
749
+ * fallback behaviour in an environment where Intl.PluralRules exists.
750
+ *
751
+ * @param {number} count - Number used to determine which pluralisation to use.
752
+ * @returns {PluralRule} The pluralisation form for count in this locale.
753
+ */
754
+ I18n.prototype.selectPluralFormUsingFallbackRules = function (count) {
755
+ // Currently our custom code can only handle positive integers, so let's
756
+ // make sure our number is one of those.
757
+ count = Math.abs(Math.floor(count));
1679
758
 
1680
- // Polyfill from https://raw.githubusercontent.com/Financial-Times/polyfill-service/1f3c09b402f65bf6e393f933a15ba63f1b86ef1f/packages/polyfill-library/polyfills/Element/prototype/matches/polyfill.js
1681
- Element.prototype.matches = Element.prototype.webkitMatchesSelector || Element.prototype.oMatchesSelector || Element.prototype.msMatchesSelector || Element.prototype.mozMatchesSelector || function matches(selector) {
1682
- var element = this;
1683
- var elements = (element.document || element.ownerDocument).querySelectorAll(selector);
1684
- var index = 0;
759
+ var ruleset = this.getPluralRulesForLocale();
1685
760
 
1686
- while (elements[index] && elements[index] !== element) {
1687
- ++index;
761
+ if (ruleset) {
762
+ return I18n.pluralRules[ruleset](count)
1688
763
  }
1689
764
 
1690
- return !!elements[index];
765
+ return 'other'
1691
766
  };
1692
767
 
1693
- }).call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
1694
-
1695
- (function(undefined) {
768
+ /**
769
+ * Work out which pluralisation rules to use for the current locale
770
+ *
771
+ * The locale may include a regional indicator (such as en-GB), but we don't
772
+ * usually care about this part, as pluralisation rules are usually the same
773
+ * regardless of region. There are exceptions, however, (e.g. Portuguese) so
774
+ * this searches by both the full and shortened locale codes, just to be sure.
775
+ *
776
+ * @returns {PluralRuleName | undefined} The name of the pluralisation rule to use (a key for one
777
+ * of the functions in this.pluralRules)
778
+ */
779
+ I18n.prototype.getPluralRulesForLocale = function () {
780
+ var locale = this.locale;
781
+ var localeShort = locale.split('-')[0];
782
+
783
+ // Look through the plural rules map to find which `pluralRule` is
784
+ // appropriate for our current `locale`.
785
+ for (var pluralRule in I18n.pluralRulesMap) {
786
+ if (Object.prototype.hasOwnProperty.call(I18n.pluralRulesMap, pluralRule)) {
787
+ var languages = I18n.pluralRulesMap[pluralRule];
788
+ for (var i = 0; i < languages.length; i++) {
789
+ if (languages[i] === locale || languages[i] === localeShort) {
790
+ return pluralRule
791
+ }
792
+ }
793
+ }
794
+ }
795
+ };
796
+
797
+ /**
798
+ * Map of plural rules to languages where those rules apply.
799
+ *
800
+ * Note: These groups are named for the most dominant or recognisable language
801
+ * that uses each system. The groupings do not imply that the languages are
802
+ * related to one another. Many languages have evolved the same systems
803
+ * independently of one another.
804
+ *
805
+ * Code to support more languages can be found in the i18n spike:
806
+ * {@link https://github.com/alphagov/govuk-frontend/blob/spike-i18n-support/src/govuk/i18n.mjs}
807
+ *
808
+ * Languages currently supported:
809
+ *
810
+ * Arabic: Arabic (ar)
811
+ * Chinese: Burmese (my), Chinese (zh), Indonesian (id), Japanese (ja),
812
+ * Javanese (jv), Korean (ko), Malay (ms), Thai (th), Vietnamese (vi)
813
+ * French: Armenian (hy), Bangla (bn), French (fr), Gujarati (gu), Hindi (hi),
814
+ * Persian Farsi (fa), Punjabi (pa), Zulu (zu)
815
+ * German: Afrikaans (af), Albanian (sq), Azerbaijani (az), Basque (eu),
816
+ * Bulgarian (bg), Catalan (ca), Danish (da), Dutch (nl), English (en),
817
+ * Estonian (et), Finnish (fi), Georgian (ka), German (de), Greek (el),
818
+ * Hungarian (hu), Luxembourgish (lb), Norwegian (no), Somali (so),
819
+ * Swahili (sw), Swedish (sv), Tamil (ta), Telugu (te), Turkish (tr),
820
+ * Urdu (ur)
821
+ * Irish: Irish Gaelic (ga)
822
+ * Russian: Russian (ru), Ukrainian (uk)
823
+ * Scottish: Scottish Gaelic (gd)
824
+ * Spanish: European Portuguese (pt-PT), Italian (it), Spanish (es)
825
+ * Welsh: Welsh (cy)
826
+ *
827
+ * @type {Object<PluralRuleName, string[]>}
828
+ */
829
+ I18n.pluralRulesMap = {
830
+ arabic: ['ar'],
831
+ chinese: ['my', 'zh', 'id', 'ja', 'jv', 'ko', 'ms', 'th', 'vi'],
832
+ french: ['hy', 'bn', 'fr', 'gu', 'hi', 'fa', 'pa', 'zu'],
833
+ german: [
834
+ 'af', 'sq', 'az', 'eu', 'bg', 'ca', 'da', 'nl', 'en', 'et', 'fi', 'ka',
835
+ 'de', 'el', 'hu', 'lb', 'no', 'so', 'sw', 'sv', 'ta', 'te', 'tr', 'ur'
836
+ ],
837
+ irish: ['ga'],
838
+ russian: ['ru', 'uk'],
839
+ scottish: ['gd'],
840
+ spanish: ['pt-PT', 'it', 'es'],
841
+ welsh: ['cy']
842
+ };
843
+
844
+ /**
845
+ * Different pluralisation rule sets
846
+ *
847
+ * Returns the appropriate suffix for the plural form associated with `n`.
848
+ * Possible suffixes: 'zero', 'one', 'two', 'few', 'many', 'other' (the actual
849
+ * meaning of each differs per locale). 'other' should always exist, even in
850
+ * languages without plurals, such as Chinese.
851
+ * {@link https://cldr.unicode.org/index/cldr-spec/plural-rules}
852
+ *
853
+ * The count must be a positive integer. Negative numbers and decimals aren't accounted for
854
+ *
855
+ * @type {Object<string, function(number): PluralRule>}
856
+ */
857
+ I18n.pluralRules = {
858
+ /* eslint-disable jsdoc/require-jsdoc */
859
+ arabic: function (n) {
860
+ if (n === 0) { return 'zero' }
861
+ if (n === 1) { return 'one' }
862
+ if (n === 2) { return 'two' }
863
+ if (n % 100 >= 3 && n % 100 <= 10) { return 'few' }
864
+ if (n % 100 >= 11 && n % 100 <= 99) { return 'many' }
865
+ return 'other'
866
+ },
867
+ chinese: function () {
868
+ return 'other'
869
+ },
870
+ french: function (n) {
871
+ return n === 0 || n === 1 ? 'one' : 'other'
872
+ },
873
+ german: function (n) {
874
+ return n === 1 ? 'one' : 'other'
875
+ },
876
+ irish: function (n) {
877
+ if (n === 1) { return 'one' }
878
+ if (n === 2) { return 'two' }
879
+ if (n >= 3 && n <= 6) { return 'few' }
880
+ if (n >= 7 && n <= 10) { return 'many' }
881
+ return 'other'
882
+ },
883
+ russian: function (n) {
884
+ var lastTwo = n % 100;
885
+ var last = lastTwo % 10;
886
+ if (last === 1 && lastTwo !== 11) { return 'one' }
887
+ if (last >= 2 && last <= 4 && !(lastTwo >= 12 && lastTwo <= 14)) { return 'few' }
888
+ if (last === 0 || (last >= 5 && last <= 9) || (lastTwo >= 11 && lastTwo <= 14)) { return 'many' }
889
+ // Note: The 'other' suffix is only used by decimal numbers in Russian.
890
+ // We don't anticipate it being used, but it's here for consistency.
891
+ return 'other'
892
+ },
893
+ scottish: function (n) {
894
+ if (n === 1 || n === 11) { return 'one' }
895
+ if (n === 2 || n === 12) { return 'two' }
896
+ if ((n >= 3 && n <= 10) || (n >= 13 && n <= 19)) { return 'few' }
897
+ return 'other'
898
+ },
899
+ spanish: function (n) {
900
+ if (n === 1) { return 'one' }
901
+ if (n % 1000000 === 0 && n !== 0) { return 'many' }
902
+ return 'other'
903
+ },
904
+ welsh: function (n) {
905
+ if (n === 0) { return 'zero' }
906
+ if (n === 1) { return 'one' }
907
+ if (n === 2) { return 'two' }
908
+ if (n === 3) { return 'few' }
909
+ if (n === 6) { return 'many' }
910
+ return 'other'
911
+ }
912
+ /* eslint-enable jsdoc/require-jsdoc */
913
+ };
914
+
915
+ /**
916
+ * Supported languages for plural rules
917
+ *
918
+ * @typedef {'arabic' | 'chinese' | 'french' | 'german' | 'irish' | 'russian' | 'scottish' | 'spanish' | 'welsh'} PluralRuleName
919
+ */
920
+
921
+ /**
922
+ * Plural rule category mnemonic tags
923
+ *
924
+ * @typedef {'zero' | 'one' | 'two' | 'few' | 'many' | 'other'} PluralRule
925
+ */
926
+
927
+ /**
928
+ * Translated message by plural rule they correspond to.
929
+ *
930
+ * Allows to group pluralised messages under a single key when passing
931
+ * translations to a component's constructor
932
+ *
933
+ * @typedef {object} TranslationPluralForms
934
+ * @property {string} [other] - General plural form
935
+ * @property {string} [zero] - Plural form used with 0
936
+ * @property {string} [one] - Plural form used with 1
937
+ * @property {string} [two] - Plural form used with 2
938
+ * @property {string} [few] - Plural form used for a few
939
+ * @property {string} [many] - Plural form used for many
940
+ */
941
+
942
+ (function(undefined) {
943
+
944
+ // Detection from https://github.com/Financial-Times/polyfill-library/blob/v3.111.0/polyfills/Date/now/detect.js
945
+ var detect = ('Date' in self && 'now' in self.Date && 'getTime' in self.Date.prototype);
946
+
947
+ if (detect) return
948
+
949
+ // Polyfill from https://polyfill.io/v3/polyfill.js?version=3.111.0&features=Date.now&flags=always
950
+ Date.now = function () {
951
+ return new Date().getTime();
952
+ };
953
+
954
+ }).call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
955
+
956
+ (function(undefined) {
957
+
958
+ // Detection from https://raw.githubusercontent.com/Financial-Times/polyfill-service/master/packages/polyfill-library/polyfills/DOMTokenList/detect.js
959
+ var detect = (
960
+ 'DOMTokenList' in this && (function (x) {
961
+ return 'classList' in x ? !x.classList.toggle('x', false) && !x.className : true;
962
+ })(document.createElement('x'))
963
+ );
964
+
965
+ if (detect) return
966
+
967
+ // Polyfill from https://raw.githubusercontent.com/Financial-Times/polyfill-service/master/packages/polyfill-library/polyfills/DOMTokenList/polyfill.js
968
+ (function (global) {
969
+ var nativeImpl = "DOMTokenList" in global && global.DOMTokenList;
970
+
971
+ if (
972
+ !nativeImpl ||
973
+ (
974
+ !!document.createElementNS &&
975
+ !!document.createElementNS('http://www.w3.org/2000/svg', 'svg') &&
976
+ !(document.createElementNS("http://www.w3.org/2000/svg", "svg").classList instanceof DOMTokenList)
977
+ )
978
+ ) {
979
+ global.DOMTokenList = (function() { // eslint-disable-line no-unused-vars
980
+ var dpSupport = true;
981
+ var defineGetter = function (object, name, fn, configurable) {
982
+ if (Object.defineProperty)
983
+ Object.defineProperty(object, name, {
984
+ configurable: false === dpSupport ? true : !!configurable,
985
+ get: fn
986
+ });
987
+
988
+ else object.__defineGetter__(name, fn);
989
+ };
990
+
991
+ /** Ensure the browser allows Object.defineProperty to be used on native JavaScript objects. */
992
+ try {
993
+ defineGetter({}, "support");
994
+ }
995
+ catch (e) {
996
+ dpSupport = false;
997
+ }
998
+
999
+
1000
+ var _DOMTokenList = function (el, prop) {
1001
+ var that = this;
1002
+ var tokens = [];
1003
+ var tokenMap = {};
1004
+ var length = 0;
1005
+ var maxLength = 0;
1006
+ var addIndexGetter = function (i) {
1007
+ defineGetter(that, i, function () {
1008
+ preop();
1009
+ return tokens[i];
1010
+ }, false);
1011
+
1012
+ };
1013
+ var reindex = function () {
1014
+
1015
+ /** Define getter functions for array-like access to the tokenList's contents. */
1016
+ if (length >= maxLength)
1017
+ for (; maxLength < length; ++maxLength) {
1018
+ addIndexGetter(maxLength);
1019
+ }
1020
+ };
1021
+
1022
+ /** Helper function called at the start of each class method. Internal use only. */
1023
+ var preop = function () {
1024
+ var error;
1025
+ var i;
1026
+ var args = arguments;
1027
+ var rSpace = /\s+/;
1028
+
1029
+ /** Validate the token/s passed to an instance method, if any. */
1030
+ if (args.length)
1031
+ for (i = 0; i < args.length; ++i)
1032
+ if (rSpace.test(args[i])) {
1033
+ error = new SyntaxError('String "' + args[i] + '" ' + "contains" + ' an invalid character');
1034
+ error.code = 5;
1035
+ error.name = "InvalidCharacterError";
1036
+ throw error;
1037
+ }
1038
+
1039
+
1040
+ /** Split the new value apart by whitespace*/
1041
+ if (typeof el[prop] === "object") {
1042
+ tokens = ("" + el[prop].baseVal).replace(/^\s+|\s+$/g, "").split(rSpace);
1043
+ } else {
1044
+ tokens = ("" + el[prop]).replace(/^\s+|\s+$/g, "").split(rSpace);
1045
+ }
1046
+
1047
+ /** Avoid treating blank strings as single-item token lists */
1048
+ if ("" === tokens[0]) tokens = [];
1049
+
1050
+ /** Repopulate the internal token lists */
1051
+ tokenMap = {};
1052
+ for (i = 0; i < tokens.length; ++i)
1053
+ tokenMap[tokens[i]] = true;
1054
+ length = tokens.length;
1055
+ reindex();
1056
+ };
1057
+
1058
+ /** Populate our internal token list if the targeted attribute of the subject element isn't empty. */
1059
+ preop();
1060
+
1061
+ /** Return the number of tokens in the underlying string. Read-only. */
1062
+ defineGetter(that, "length", function () {
1063
+ preop();
1064
+ return length;
1065
+ });
1066
+
1067
+ /** Override the default toString/toLocaleString methods to return a space-delimited list of tokens when typecast. */
1068
+ that.toLocaleString =
1069
+ that.toString = function () {
1070
+ preop();
1071
+ return tokens.join(" ");
1072
+ };
1073
+
1074
+ that.item = function (idx) {
1075
+ preop();
1076
+ return tokens[idx];
1077
+ };
1078
+
1079
+ that.contains = function (token) {
1080
+ preop();
1081
+ return !!tokenMap[token];
1082
+ };
1083
+
1084
+ that.add = function () {
1085
+ preop.apply(that, args = arguments);
1086
+
1087
+ for (var args, token, i = 0, l = args.length; i < l; ++i) {
1088
+ token = args[i];
1089
+ if (!tokenMap[token]) {
1090
+ tokens.push(token);
1091
+ tokenMap[token] = true;
1092
+ }
1093
+ }
1094
+
1095
+ /** Update the targeted attribute of the attached element if the token list's changed. */
1096
+ if (length !== tokens.length) {
1097
+ length = tokens.length >>> 0;
1098
+ if (typeof el[prop] === "object") {
1099
+ el[prop].baseVal = tokens.join(" ");
1100
+ } else {
1101
+ el[prop] = tokens.join(" ");
1102
+ }
1103
+ reindex();
1104
+ }
1105
+ };
1106
+
1107
+ that.remove = function () {
1108
+ preop.apply(that, args = arguments);
1109
+
1110
+ /** Build a hash of token names to compare against when recollecting our token list. */
1111
+ for (var args, ignore = {}, i = 0, t = []; i < args.length; ++i) {
1112
+ ignore[args[i]] = true;
1113
+ delete tokenMap[args[i]];
1114
+ }
1115
+
1116
+ /** Run through our tokens list and reassign only those that aren't defined in the hash declared above. */
1117
+ for (i = 0; i < tokens.length; ++i)
1118
+ if (!ignore[tokens[i]]) t.push(tokens[i]);
1119
+
1120
+ tokens = t;
1121
+ length = t.length >>> 0;
1122
+
1123
+ /** Update the targeted attribute of the attached element. */
1124
+ if (typeof el[prop] === "object") {
1125
+ el[prop].baseVal = tokens.join(" ");
1126
+ } else {
1127
+ el[prop] = tokens.join(" ");
1128
+ }
1129
+ reindex();
1130
+ };
1131
+
1132
+ that.toggle = function (token, force) {
1133
+ preop.apply(that, [token]);
1134
+
1135
+ /** Token state's being forced. */
1136
+ if (undefined !== force) {
1137
+ if (force) {
1138
+ that.add(token);
1139
+ return true;
1140
+ } else {
1141
+ that.remove(token);
1142
+ return false;
1143
+ }
1144
+ }
1145
+
1146
+ /** Token already exists in tokenList. Remove it, and return FALSE. */
1147
+ if (tokenMap[token]) {
1148
+ that.remove(token);
1149
+ return false;
1150
+ }
1151
+
1152
+ /** Otherwise, add the token and return TRUE. */
1153
+ that.add(token);
1154
+ return true;
1155
+ };
1156
+
1157
+ return that;
1158
+ };
1159
+
1160
+ return _DOMTokenList;
1161
+ }());
1162
+ }
1163
+
1164
+ // Add second argument to native DOMTokenList.toggle() if necessary
1165
+ (function () {
1166
+ var e = document.createElement('span');
1167
+ if (!('classList' in e)) return;
1168
+ e.classList.toggle('x', false);
1169
+ if (!e.classList.contains('x')) return;
1170
+ e.classList.constructor.prototype.toggle = function toggle(token /*, force*/) {
1171
+ var force = arguments[1];
1172
+ if (force === undefined) {
1173
+ var add = !this.contains(token);
1174
+ this[add ? 'add' : 'remove'](token);
1175
+ return add;
1176
+ }
1177
+ force = !!force;
1178
+ this[force ? 'add' : 'remove'](token);
1179
+ return force;
1180
+ };
1181
+ }());
1182
+
1183
+ // Add multiple arguments to native DOMTokenList.add() if necessary
1184
+ (function () {
1185
+ var e = document.createElement('span');
1186
+ if (!('classList' in e)) return;
1187
+ e.classList.add('a', 'b');
1188
+ if (e.classList.contains('b')) return;
1189
+ var native = e.classList.constructor.prototype.add;
1190
+ e.classList.constructor.prototype.add = function () {
1191
+ var args = arguments;
1192
+ var l = arguments.length;
1193
+ for (var i = 0; i < l; i++) {
1194
+ native.call(this, args[i]);
1195
+ }
1196
+ };
1197
+ }());
1198
+
1199
+ // Add multiple arguments to native DOMTokenList.remove() if necessary
1200
+ (function () {
1201
+ var e = document.createElement('span');
1202
+ if (!('classList' in e)) return;
1203
+ e.classList.add('a');
1204
+ e.classList.add('b');
1205
+ e.classList.remove('a', 'b');
1206
+ if (!e.classList.contains('b')) return;
1207
+ var native = e.classList.constructor.prototype.remove;
1208
+ e.classList.constructor.prototype.remove = function () {
1209
+ var args = arguments;
1210
+ var l = arguments.length;
1211
+ for (var i = 0; i < l; i++) {
1212
+ native.call(this, args[i]);
1213
+ }
1214
+ };
1215
+ }());
1216
+
1217
+ }(this));
1696
1218
 
1697
- // Detection from https://raw.githubusercontent.com/Financial-Times/polyfill-service/1f3c09b402f65bf6e393f933a15ba63f1b86ef1f/packages/polyfill-library/polyfills/Element/prototype/closest/detect.js
1219
+ }).call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
1220
+
1221
+ (function(undefined) {
1222
+
1223
+ // Detection from https://raw.githubusercontent.com/Financial-Times/polyfill-service/8717a9e04ac7aff99b4980fbedead98036b0929a/packages/polyfill-library/polyfills/Element/prototype/classList/detect.js
1224
+ var detect = (
1225
+ 'document' in this && "classList" in document.documentElement && 'Element' in this && 'classList' in Element.prototype && (function () {
1226
+ var e = document.createElement('span');
1227
+ e.classList.add('a', 'b');
1228
+ return e.classList.contains('b');
1229
+ }())
1230
+ );
1231
+
1232
+ if (detect) return
1233
+
1234
+ // Polyfill from https://cdn.polyfill.io/v2/polyfill.js?features=Element.prototype.classList&flags=always
1235
+ (function (global) {
1236
+ var dpSupport = true;
1237
+ var defineGetter = function (object, name, fn, configurable) {
1238
+ if (Object.defineProperty)
1239
+ Object.defineProperty(object, name, {
1240
+ configurable: false === dpSupport ? true : !!configurable,
1241
+ get: fn
1242
+ });
1243
+
1244
+ else object.__defineGetter__(name, fn);
1245
+ };
1246
+ /** Ensure the browser allows Object.defineProperty to be used on native JavaScript objects. */
1247
+ try {
1248
+ defineGetter({}, "support");
1249
+ }
1250
+ catch (e) {
1251
+ dpSupport = false;
1252
+ }
1253
+ /** Polyfills a property with a DOMTokenList */
1254
+ var addProp = function (o, name, attr) {
1255
+
1256
+ defineGetter(o.prototype, name, function () {
1257
+ var tokenList;
1258
+
1259
+ var THIS = this,
1260
+
1261
+ /** Prevent this from firing twice for some reason. What the hell, IE. */
1262
+ gibberishProperty = "__defineGetter__" + "DEFINE_PROPERTY" + name;
1263
+ if(THIS[gibberishProperty]) return tokenList;
1264
+ THIS[gibberishProperty] = true;
1265
+
1266
+ /**
1267
+ * IE8 can't define properties on native JavaScript objects, so we'll use a dumb hack instead.
1268
+ *
1269
+ * What this is doing is creating a dummy element ("reflection") inside a detached phantom node ("mirror")
1270
+ * that serves as the target of Object.defineProperty instead. While we could simply use the subject HTML
1271
+ * element instead, this would conflict with element types which use indexed properties (such as forms and
1272
+ * select lists).
1273
+ */
1274
+ if (false === dpSupport) {
1275
+
1276
+ var visage;
1277
+ var mirror = addProp.mirror || document.createElement("div");
1278
+ var reflections = mirror.childNodes;
1279
+ var l = reflections.length;
1280
+
1281
+ for (var i = 0; i < l; ++i)
1282
+ if (reflections[i]._R === THIS) {
1283
+ visage = reflections[i];
1284
+ break;
1285
+ }
1286
+
1287
+ /** Couldn't find an element's reflection inside the mirror. Materialise one. */
1288
+ visage || (visage = mirror.appendChild(document.createElement("div")));
1289
+
1290
+ tokenList = DOMTokenList.call(visage, THIS, attr);
1291
+ } else tokenList = new DOMTokenList(THIS, attr);
1292
+
1293
+ defineGetter(THIS, name, function () {
1294
+ return tokenList;
1295
+ });
1296
+ delete THIS[gibberishProperty];
1297
+
1298
+ return tokenList;
1299
+ }, true);
1300
+ };
1301
+
1302
+ addProp(global.Element, "classList", "className");
1303
+ addProp(global.HTMLElement, "classList", "className");
1304
+ addProp(global.HTMLLinkElement, "relList", "rel");
1305
+ addProp(global.HTMLAnchorElement, "relList", "rel");
1306
+ addProp(global.HTMLAreaElement, "relList", "rel");
1307
+ }(this));
1308
+
1309
+ }).call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
1310
+
1311
+ (function(undefined) {
1312
+
1313
+ // Detection from https://github.com/Financial-Times/polyfill-service/blob/master/packages/polyfill-library/polyfills/Window/detect.js
1314
+ var detect = ('Window' in this);
1315
+
1316
+ if (detect) return
1317
+
1318
+ // Polyfill from https://cdn.polyfill.io/v2/polyfill.js?features=Window&flags=always
1319
+ if ((typeof WorkerGlobalScope === "undefined") && (typeof importScripts !== "function")) {
1320
+ (function (global) {
1321
+ if (global.constructor) {
1322
+ global.Window = global.constructor;
1323
+ } else {
1324
+ (global.Window = global.constructor = new Function('return function Window() {}')()).prototype = this;
1325
+ }
1326
+ }(this));
1327
+ }
1328
+
1329
+ })
1330
+ .call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
1331
+
1332
+ (function(undefined) {
1333
+
1334
+ // Detection from https://github.com/Financial-Times/polyfill-service/blob/master/packages/polyfill-library/polyfills/Event/detect.js
1698
1335
  var detect = (
1699
- 'document' in this && "closest" in document.documentElement
1336
+ (function(global) {
1337
+
1338
+ if (!('Event' in global)) return false;
1339
+ if (typeof global.Event === 'function') return true;
1340
+
1341
+ try {
1342
+
1343
+ // In IE 9-11, the Event object exists but cannot be instantiated
1344
+ new Event('click');
1345
+ return true;
1346
+ } catch(e) {
1347
+ return false;
1348
+ }
1349
+ }(this))
1700
1350
  );
1701
1351
 
1702
1352
  if (detect) return
1703
1353
 
1704
- // Polyfill from https://raw.githubusercontent.com/Financial-Times/polyfill-service/1f3c09b402f65bf6e393f933a15ba63f1b86ef1f/packages/polyfill-library/polyfills/Element/prototype/closest/polyfill.js
1705
- Element.prototype.closest = function closest(selector) {
1706
- var node = this;
1354
+ // Polyfill from https://cdn.polyfill.io/v2/polyfill.js?features=Event&flags=always
1355
+ (function () {
1356
+ var unlistenableWindowEvents = {
1357
+ click: 1,
1358
+ dblclick: 1,
1359
+ keyup: 1,
1360
+ keypress: 1,
1361
+ keydown: 1,
1362
+ mousedown: 1,
1363
+ mouseup: 1,
1364
+ mousemove: 1,
1365
+ mouseover: 1,
1366
+ mouseenter: 1,
1367
+ mouseleave: 1,
1368
+ mouseout: 1,
1369
+ storage: 1,
1370
+ storagecommit: 1,
1371
+ textinput: 1
1372
+ };
1373
+
1374
+ // This polyfill depends on availability of `document` so will not run in a worker
1375
+ // However, we asssume there are no browsers with worker support that lack proper
1376
+ // support for `Event` within the worker
1377
+ if (typeof document === 'undefined' || typeof window === 'undefined') return;
1378
+
1379
+ function indexOf(array, element) {
1380
+ var
1381
+ index = -1,
1382
+ length = array.length;
1383
+
1384
+ while (++index < length) {
1385
+ if (index in array && array[index] === element) {
1386
+ return index;
1387
+ }
1388
+ }
1389
+
1390
+ return -1;
1391
+ }
1707
1392
 
1708
- while (node) {
1709
- if (node.matches(selector)) return node;
1710
- else node = 'SVGElement' in window && node instanceof SVGElement ? node.parentNode : node.parentElement;
1711
- }
1393
+ var existingProto = (window.Event && window.Event.prototype) || null;
1394
+ window.Event = Window.prototype.Event = function Event(type, eventInitDict) {
1395
+ if (!type) {
1396
+ throw new Error('Not enough arguments');
1397
+ }
1398
+
1399
+ var event;
1400
+ // Shortcut if browser supports createEvent
1401
+ if ('createEvent' in document) {
1402
+ event = document.createEvent('Event');
1403
+ var bubbles = eventInitDict && eventInitDict.bubbles !== undefined ? eventInitDict.bubbles : false;
1404
+ var cancelable = eventInitDict && eventInitDict.cancelable !== undefined ? eventInitDict.cancelable : false;
1405
+
1406
+ event.initEvent(type, bubbles, cancelable);
1407
+
1408
+ return event;
1409
+ }
1410
+
1411
+ event = document.createEventObject();
1412
+
1413
+ event.type = type;
1414
+ event.bubbles = eventInitDict && eventInitDict.bubbles !== undefined ? eventInitDict.bubbles : false;
1415
+ event.cancelable = eventInitDict && eventInitDict.cancelable !== undefined ? eventInitDict.cancelable : false;
1416
+
1417
+ return event;
1418
+ };
1419
+ if (existingProto) {
1420
+ Object.defineProperty(window.Event, 'prototype', {
1421
+ configurable: false,
1422
+ enumerable: false,
1423
+ writable: true,
1424
+ value: existingProto
1425
+ });
1426
+ }
1712
1427
 
1713
- return null;
1714
- };
1428
+ if (!('createEvent' in document)) {
1429
+ window.addEventListener = Window.prototype.addEventListener = Document.prototype.addEventListener = Element.prototype.addEventListener = function addEventListener() {
1430
+ var
1431
+ element = this,
1432
+ type = arguments[0],
1433
+ listener = arguments[1];
1434
+
1435
+ if (element === window && type in unlistenableWindowEvents) {
1436
+ 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.');
1437
+ }
1438
+
1439
+ if (!element._events) {
1440
+ element._events = {};
1441
+ }
1442
+
1443
+ if (!element._events[type]) {
1444
+ element._events[type] = function (event) {
1445
+ var
1446
+ list = element._events[event.type].list,
1447
+ events = list.slice(),
1448
+ index = -1,
1449
+ length = events.length,
1450
+ eventElement;
1451
+
1452
+ event.preventDefault = function preventDefault() {
1453
+ if (event.cancelable !== false) {
1454
+ event.returnValue = false;
1455
+ }
1456
+ };
1457
+
1458
+ event.stopPropagation = function stopPropagation() {
1459
+ event.cancelBubble = true;
1460
+ };
1461
+
1462
+ event.stopImmediatePropagation = function stopImmediatePropagation() {
1463
+ event.cancelBubble = true;
1464
+ event.cancelImmediate = true;
1465
+ };
1466
+
1467
+ event.currentTarget = element;
1468
+ event.relatedTarget = event.fromElement || null;
1469
+ event.target = event.target || event.srcElement || element;
1470
+ event.timeStamp = new Date().getTime();
1471
+
1472
+ if (event.clientX) {
1473
+ event.pageX = event.clientX + document.documentElement.scrollLeft;
1474
+ event.pageY = event.clientY + document.documentElement.scrollTop;
1475
+ }
1476
+
1477
+ while (++index < length && !event.cancelImmediate) {
1478
+ if (index in events) {
1479
+ eventElement = events[index];
1480
+
1481
+ if (indexOf(list, eventElement) !== -1 && typeof eventElement === 'function') {
1482
+ eventElement.call(element, event);
1483
+ }
1484
+ }
1485
+ }
1486
+ };
1487
+
1488
+ element._events[type].list = [];
1489
+
1490
+ if (element.attachEvent) {
1491
+ element.attachEvent('on' + type, element._events[type]);
1492
+ }
1493
+ }
1494
+
1495
+ element._events[type].list.push(listener);
1496
+ };
1497
+
1498
+ window.removeEventListener = Window.prototype.removeEventListener = Document.prototype.removeEventListener = Element.prototype.removeEventListener = function removeEventListener() {
1499
+ var
1500
+ element = this,
1501
+ type = arguments[0],
1502
+ listener = arguments[1],
1503
+ index;
1504
+
1505
+ if (element._events && element._events[type] && element._events[type].list) {
1506
+ index = indexOf(element._events[type].list, listener);
1507
+
1508
+ if (index !== -1) {
1509
+ element._events[type].list.splice(index, 1);
1510
+
1511
+ if (!element._events[type].list.length) {
1512
+ if (element.detachEvent) {
1513
+ element.detachEvent('on' + type, element._events[type]);
1514
+ }
1515
+ delete element._events[type];
1516
+ }
1517
+ }
1518
+ }
1519
+ };
1520
+
1521
+ window.dispatchEvent = Window.prototype.dispatchEvent = Document.prototype.dispatchEvent = Element.prototype.dispatchEvent = function dispatchEvent(event) {
1522
+ if (!arguments.length) {
1523
+ throw new Error('Not enough arguments');
1524
+ }
1525
+
1526
+ if (!event || typeof event.type !== 'string') {
1527
+ throw new Error('DOM Events Exception 0');
1528
+ }
1529
+
1530
+ var element = this, type = event.type;
1531
+
1532
+ try {
1533
+ if (!event.bubbles) {
1534
+ event.cancelBubble = true;
1535
+
1536
+ var cancelBubbleEvent = function (event) {
1537
+ event.cancelBubble = true;
1538
+
1539
+ (element || window).detachEvent('on' + type, cancelBubbleEvent);
1540
+ };
1541
+
1542
+ this.attachEvent('on' + type, cancelBubbleEvent);
1543
+ }
1544
+
1545
+ this.fireEvent('on' + type, event);
1546
+ } catch (error) {
1547
+ event.target = element;
1548
+
1549
+ do {
1550
+ event.currentTarget = element;
1551
+
1552
+ if ('_events' in element && typeof element._events[type] === 'function') {
1553
+ element._events[type].call(element, event);
1554
+ }
1555
+
1556
+ if (typeof element['on' + type] === 'function') {
1557
+ element['on' + type].call(element, event);
1558
+ }
1559
+
1560
+ element = element.nodeType === 9 ? element.parentWindow : element.parentNode;
1561
+ } while (element && !event.cancelBubble);
1562
+ }
1563
+
1564
+ return true;
1565
+ };
1566
+
1567
+ // Add the DOMContentLoaded Event
1568
+ document.attachEvent('onreadystatechange', function() {
1569
+ if (document.readyState === 'complete') {
1570
+ document.dispatchEvent(new Event('DOMContentLoaded', {
1571
+ bubbles: true
1572
+ }));
1573
+ }
1574
+ });
1575
+ }
1576
+ }());
1715
1577
 
1716
- }).call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
1717
-
1718
- /**
1719
- * Returns the value of the given attribute closest to the given element (including itself)
1720
- *
1721
- * @param {HTMLElement} $element - The element to start walking the DOM tree up
1722
- * @param {string} attributeName - The name of the attribute
1723
- * @returns {string | undefined} Attribute value
1724
- */
1725
- function closestAttributeValue ($element, attributeName) {
1726
- var closestElementWithAttribute = $element.closest('[' + attributeName + ']');
1727
- if (closestElementWithAttribute) {
1728
- return closestElementWithAttribute.getAttribute(attributeName)
1729
- }
1730
- }
1731
-
1732
- /**
1733
- * @constant
1734
- * @type {CharacterCountTranslations}
1735
- * @see Default value for {@link CharacterCountConfig.i18n}
1736
- * @default
1737
- */
1738
- var CHARACTER_COUNT_TRANSLATIONS = {
1739
- // Characters
1740
- charactersUnderLimit: {
1741
- one: 'You have %{count} character remaining',
1742
- other: 'You have %{count} characters remaining'
1743
- },
1744
- charactersAtLimit: 'You have 0 characters remaining',
1745
- charactersOverLimit: {
1746
- one: 'You have %{count} character too many',
1747
- other: 'You have %{count} characters too many'
1748
- },
1749
- // Words
1750
- wordsUnderLimit: {
1751
- one: 'You have %{count} word remaining',
1752
- other: 'You have %{count} words remaining'
1753
- },
1754
- wordsAtLimit: 'You have 0 words remaining',
1755
- wordsOverLimit: {
1756
- one: 'You have %{count} word too many',
1757
- other: 'You have %{count} words too many'
1758
- },
1759
- textareaDescription: {
1760
- other: ''
1761
- }
1762
- };
1763
-
1764
- /**
1765
- * JavaScript enhancements for the CharacterCount component
1766
- *
1767
- * Tracks the number of characters or words in the `.govuk-js-character-count`
1768
- * `<textarea>` inside the element. Displays a message with the remaining number
1769
- * of characters/words available, or the number of characters/words in excess.
1770
- *
1771
- * You can configure the message to only appear after a certain percentage
1772
- * of the available characters/words has been entered.
1773
- *
1774
- * @class
1775
- * @param {HTMLElement} $module - The element this component controls
1776
- * @param {CharacterCountConfig} [config] - Character count config
1777
- */
1778
- function CharacterCount ($module, config) {
1779
- if (!$module) {
1780
- return this
1781
- }
1578
+ })
1579
+ .call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
1580
+
1581
+ (function(undefined) {
1582
+ // Detection from https://github.com/Financial-Times/polyfill-service/blob/master/packages/polyfill-library/polyfills/Function/prototype/bind/detect.js
1583
+ var detect = 'bind' in Function.prototype;
1584
+
1585
+ if (detect) return
1586
+
1587
+ // Polyfill from https://cdn.polyfill.io/v2/polyfill.js?features=Function.prototype.bind&flags=always
1588
+ Object.defineProperty(Function.prototype, 'bind', {
1589
+ value: function bind(that) { // .length is 1
1590
+ // add necessary es5-shim utilities
1591
+ var $Array = Array;
1592
+ var $Object = Object;
1593
+ var ObjectPrototype = $Object.prototype;
1594
+ var ArrayPrototype = $Array.prototype;
1595
+ var Empty = function Empty() {};
1596
+ var to_string = ObjectPrototype.toString;
1597
+ var hasToStringTag = typeof Symbol === 'function' && typeof Symbol.toStringTag === 'symbol';
1598
+ 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; };
1599
+ var array_slice = ArrayPrototype.slice;
1600
+ var array_concat = ArrayPrototype.concat;
1601
+ var array_push = ArrayPrototype.push;
1602
+ var max = Math.max;
1603
+ // /add necessary es5-shim utilities
1604
+
1605
+ // 1. Let Target be the this value.
1606
+ var target = this;
1607
+ // 2. If IsCallable(Target) is false, throw a TypeError exception.
1608
+ if (!isCallable(target)) {
1609
+ throw new TypeError('Function.prototype.bind called on incompatible ' + target);
1610
+ }
1611
+ // 3. Let A be a new (possibly empty) internal list of all of the
1612
+ // argument values provided after thisArg (arg1, arg2 etc), in order.
1613
+ // XXX slicedArgs will stand in for "A" if used
1614
+ var args = array_slice.call(arguments, 1); // for normal call
1615
+ // 4. Let F be a new native ECMAScript object.
1616
+ // 11. Set the [[Prototype]] internal property of F to the standard
1617
+ // built-in Function prototype object as specified in 15.3.3.1.
1618
+ // 12. Set the [[Call]] internal property of F as described in
1619
+ // 15.3.4.5.1.
1620
+ // 13. Set the [[Construct]] internal property of F as described in
1621
+ // 15.3.4.5.2.
1622
+ // 14. Set the [[HasInstance]] internal property of F as described in
1623
+ // 15.3.4.5.3.
1624
+ var bound;
1625
+ var binder = function () {
1626
+
1627
+ if (this instanceof bound) {
1628
+ // 15.3.4.5.2 [[Construct]]
1629
+ // When the [[Construct]] internal method of a function object,
1630
+ // F that was created using the bind function is called with a
1631
+ // list of arguments ExtraArgs, the following steps are taken:
1632
+ // 1. Let target be the value of F's [[TargetFunction]]
1633
+ // internal property.
1634
+ // 2. If target has no [[Construct]] internal method, a
1635
+ // TypeError exception is thrown.
1636
+ // 3. Let boundArgs be the value of F's [[BoundArgs]] internal
1637
+ // property.
1638
+ // 4. Let args be a new list containing the same values as the
1639
+ // list boundArgs in the same order followed by the same
1640
+ // values as the list ExtraArgs in the same order.
1641
+ // 5. Return the result of calling the [[Construct]] internal
1642
+ // method of target providing args as the arguments.
1643
+
1644
+ var result = target.apply(
1645
+ this,
1646
+ array_concat.call(args, array_slice.call(arguments))
1647
+ );
1648
+ if ($Object(result) === result) {
1649
+ return result;
1650
+ }
1651
+ return this;
1652
+
1653
+ } else {
1654
+ // 15.3.4.5.1 [[Call]]
1655
+ // When the [[Call]] internal method of a function object, F,
1656
+ // which was created using the bind function is called with a
1657
+ // this value and a list of arguments ExtraArgs, the following
1658
+ // steps are taken:
1659
+ // 1. Let boundArgs be the value of F's [[BoundArgs]] internal
1660
+ // property.
1661
+ // 2. Let boundThis be the value of F's [[BoundThis]] internal
1662
+ // property.
1663
+ // 3. Let target be the value of F's [[TargetFunction]] internal
1664
+ // property.
1665
+ // 4. Let args be a new list containing the same values as the
1666
+ // list boundArgs in the same order followed by the same
1667
+ // values as the list ExtraArgs in the same order.
1668
+ // 5. Return the result of calling the [[Call]] internal method
1669
+ // of target providing boundThis as the this value and
1670
+ // providing args as the arguments.
1671
+
1672
+ // equiv: target.call(this, ...boundArgs, ...args)
1673
+ return target.apply(
1674
+ that,
1675
+ array_concat.call(args, array_slice.call(arguments))
1676
+ );
1782
1677
 
1783
- var defaultConfig = {
1784
- threshold: 0,
1785
- i18n: CHARACTER_COUNT_TRANSLATIONS
1678
+ }
1679
+
1680
+ };
1681
+
1682
+ // 15. If the [[Class]] internal property of Target is "Function", then
1683
+ // a. Let L be the length property of Target minus the length of A.
1684
+ // b. Set the length own property of F to either 0 or L, whichever is
1685
+ // larger.
1686
+ // 16. Else set the length own property of F to 0.
1687
+
1688
+ var boundLength = max(0, target.length - args.length);
1689
+
1690
+ // 17. Set the attributes of the length own property of F to the values
1691
+ // specified in 15.3.5.1.
1692
+ var boundArgs = [];
1693
+ for (var i = 0; i < boundLength; i++) {
1694
+ array_push.call(boundArgs, '$' + i);
1695
+ }
1696
+
1697
+ // XXX Build a dynamic function with desired amount of arguments is the only
1698
+ // way to set the length property of a function.
1699
+ // In environments where Content Security Policies enabled (Chrome extensions,
1700
+ // for ex.) all use of eval or Function costructor throws an exception.
1701
+ // However in all of these environments Function.prototype.bind exists
1702
+ // and so this code will never be executed.
1703
+ bound = Function('binder', 'return function (' + boundArgs.join(',') + '){ return binder.apply(this, arguments); }')(binder);
1704
+
1705
+ if (target.prototype) {
1706
+ Empty.prototype = target.prototype;
1707
+ bound.prototype = new Empty();
1708
+ // Clean up dangling references.
1709
+ Empty.prototype = null;
1710
+ }
1711
+
1712
+ // TODO
1713
+ // 18. Set the [[Extensible]] internal property of F to true.
1714
+
1715
+ // TODO
1716
+ // 19. Let thrower be the [[ThrowTypeError]] function Object (13.2.3).
1717
+ // 20. Call the [[DefineOwnProperty]] internal method of F with
1718
+ // arguments "caller", PropertyDescriptor {[[Get]]: thrower, [[Set]]:
1719
+ // thrower, [[Enumerable]]: false, [[Configurable]]: false}, and
1720
+ // false.
1721
+ // 21. Call the [[DefineOwnProperty]] internal method of F with
1722
+ // arguments "arguments", PropertyDescriptor {[[Get]]: thrower,
1723
+ // [[Set]]: thrower, [[Enumerable]]: false, [[Configurable]]: false},
1724
+ // and false.
1725
+
1726
+ // TODO
1727
+ // NOTE Function objects created using Function.prototype.bind do not
1728
+ // have a prototype property or the [[Code]], [[FormalParameters]], and
1729
+ // [[Scope]] internal properties.
1730
+ // XXX can't delete prototype in pure-js.
1731
+
1732
+ // 22. Return F.
1733
+ return bound;
1734
+ }
1735
+ });
1736
+ })
1737
+ .call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
1738
+
1739
+ /* eslint-disable es-x/no-date-now -- Polyfill imported */
1740
+
1741
+ /**
1742
+ * @constant
1743
+ * @type {CharacterCountTranslations}
1744
+ * @see Default value for {@link CharacterCountConfig.i18n}
1745
+ * @default
1746
+ */
1747
+ var CHARACTER_COUNT_TRANSLATIONS = {
1748
+ // Characters
1749
+ charactersUnderLimit: {
1750
+ one: 'You have %{count} character remaining',
1751
+ other: 'You have %{count} characters remaining'
1752
+ },
1753
+ charactersAtLimit: 'You have 0 characters remaining',
1754
+ charactersOverLimit: {
1755
+ one: 'You have %{count} character too many',
1756
+ other: 'You have %{count} characters too many'
1757
+ },
1758
+ // Words
1759
+ wordsUnderLimit: {
1760
+ one: 'You have %{count} word remaining',
1761
+ other: 'You have %{count} words remaining'
1762
+ },
1763
+ wordsAtLimit: 'You have 0 words remaining',
1764
+ wordsOverLimit: {
1765
+ one: 'You have %{count} word too many',
1766
+ other: 'You have %{count} words too many'
1767
+ },
1768
+ textareaDescription: {
1769
+ other: ''
1770
+ }
1786
1771
  };
1787
1772
 
1788
- // Read config set using dataset ('data-' values)
1789
- var datasetConfig = normaliseDataset($module.dataset);
1790
-
1791
- // To ensure data-attributes take complete precedence, even if they change the
1792
- // type of count, we need to reset the `maxlength` and `maxwords` from the
1793
- // JavaScript config.
1794
- //
1795
- // We can't mutate `config`, though, as it may be shared across multiple
1796
- // components inside `initAll`.
1797
- var configOverrides = {};
1798
- if ('maxwords' in datasetConfig || 'maxlength' in datasetConfig) {
1799
- configOverrides = {
1800
- maxlength: false,
1801
- maxwords: false
1773
+ /**
1774
+ * JavaScript enhancements for the CharacterCount component
1775
+ *
1776
+ * Tracks the number of characters or words in the `.govuk-js-character-count`
1777
+ * `<textarea>` inside the element. Displays a message with the remaining number
1778
+ * of characters/words available, or the number of characters/words in excess.
1779
+ *
1780
+ * You can configure the message to only appear after a certain percentage
1781
+ * of the available characters/words has been entered.
1782
+ *
1783
+ * @class
1784
+ * @param {HTMLElement} $module - HTML element to use for character count
1785
+ * @param {CharacterCountConfig} [config] - Character count config
1786
+ */
1787
+ function CharacterCount ($module, config) {
1788
+ if (!$module) {
1789
+ return this
1790
+ }
1791
+
1792
+ var defaultConfig = {
1793
+ threshold: 0,
1794
+ i18n: CHARACTER_COUNT_TRANSLATIONS
1802
1795
  };
1803
- }
1804
1796
 
1805
- this.config = mergeConfigs(
1806
- defaultConfig,
1807
- config || {},
1808
- configOverrides,
1809
- datasetConfig
1810
- );
1797
+ // Read config set using dataset ('data-' values)
1798
+ var datasetConfig = normaliseDataset($module.dataset);
1799
+
1800
+ // To ensure data-attributes take complete precedence, even if they change the
1801
+ // type of count, we need to reset the `maxlength` and `maxwords` from the
1802
+ // JavaScript config.
1803
+ //
1804
+ // We can't mutate `config`, though, as it may be shared across multiple
1805
+ // components inside `initAll`.
1806
+ var configOverrides = {};
1807
+ if ('maxwords' in datasetConfig || 'maxlength' in datasetConfig) {
1808
+ configOverrides = {
1809
+ maxlength: false,
1810
+ maxwords: false
1811
+ };
1812
+ }
1811
1813
 
1812
- this.i18n = new I18n(extractConfigByNamespace(this.config, 'i18n'), {
1813
- // Read the fallback if necessary rather than have it set in the defaults
1814
- locale: closestAttributeValue($module, 'lang')
1815
- });
1816
-
1817
- // Determine the limit attribute (characters or words)
1818
- if (this.config.maxwords) {
1819
- this.maxLength = this.config.maxwords;
1820
- } else if (this.config.maxlength) {
1821
- this.maxLength = this.config.maxlength;
1822
- } else {
1823
- return
1824
- }
1814
+ this.config = mergeConfigs(
1815
+ defaultConfig,
1816
+ config || {},
1817
+ configOverrides,
1818
+ datasetConfig
1819
+ );
1825
1820
 
1826
- this.$module = $module;
1827
- this.$textarea = $module.querySelector('.govuk-js-character-count');
1828
- this.$visibleCountMessage = null;
1829
- this.$screenReaderCountMessage = null;
1830
- this.lastInputTimestamp = null;
1831
- }
1832
-
1833
- /**
1834
- * Initialise component
1835
- */
1836
- CharacterCount.prototype.init = function () {
1837
- // Check that required elements are present
1838
- if (!this.$textarea) {
1839
- return
1840
- }
1821
+ this.i18n = new I18n(extractConfigByNamespace(this.config, 'i18n'), {
1822
+ // Read the fallback if necessary rather than have it set in the defaults
1823
+ locale: closestAttributeValue($module, 'lang')
1824
+ });
1841
1825
 
1842
- var $textarea = this.$textarea;
1843
- var $textareaDescription = document.getElementById($textarea.id + '-info');
1826
+ // Determine the limit attribute (characters or words)
1827
+ if (this.config.maxwords) {
1828
+ this.maxLength = this.config.maxwords;
1829
+ } else if (this.config.maxlength) {
1830
+ this.maxLength = this.config.maxlength;
1831
+ } else {
1832
+ return
1833
+ }
1844
1834
 
1845
- // Inject a decription for the textarea if none is present already
1846
- // for when the component was rendered with no maxlength, maxwords
1847
- // nor custom textareaDescriptionText
1848
- if ($textareaDescription.innerText.match(/^\s*$/)) {
1849
- $textareaDescription.innerText = this.i18n.t('textareaDescription', { count: this.maxLength });
1835
+ this.$module = $module;
1836
+ this.$textarea = $module.querySelector('.govuk-js-character-count');
1837
+ this.$visibleCountMessage = null;
1838
+ this.$screenReaderCountMessage = null;
1839
+ this.lastInputTimestamp = null;
1850
1840
  }
1851
1841
 
1852
- // Move the textarea description to be immediately after the textarea
1853
- // Kept for backwards compatibility
1854
- $textarea.insertAdjacentElement('afterend', $textareaDescription);
1855
-
1856
- // Create the *screen reader* specific live-updating counter
1857
- // This doesn't need any styling classes, as it is never visible
1858
- var $screenReaderCountMessage = document.createElement('div');
1859
- $screenReaderCountMessage.className = 'govuk-character-count__sr-status govuk-visually-hidden';
1860
- $screenReaderCountMessage.setAttribute('aria-live', 'polite');
1861
- this.$screenReaderCountMessage = $screenReaderCountMessage;
1862
- $textareaDescription.insertAdjacentElement('afterend', $screenReaderCountMessage);
1863
-
1864
- // Create our live-updating counter element, copying the classes from the
1865
- // textarea description for backwards compatibility as these may have been
1866
- // configured
1867
- var $visibleCountMessage = document.createElement('div');
1868
- $visibleCountMessage.className = $textareaDescription.className;
1869
- $visibleCountMessage.classList.add('govuk-character-count__status');
1870
- $visibleCountMessage.setAttribute('aria-hidden', 'true');
1871
- this.$visibleCountMessage = $visibleCountMessage;
1872
- $textareaDescription.insertAdjacentElement('afterend', $visibleCountMessage);
1873
-
1874
- // Hide the textarea description
1875
- $textareaDescription.classList.add('govuk-visually-hidden');
1876
-
1877
- // Remove hard limit if set
1878
- $textarea.removeAttribute('maxlength');
1879
-
1880
- this.bindChangeEvents();
1881
-
1882
- // When the page is restored after navigating 'back' in some browsers the
1883
- // state of the character count is not restored until *after* the
1884
- // DOMContentLoaded event is fired, so we need to manually update it after the
1885
- // pageshow event in browsers that support it.
1886
- if ('onpageshow' in window) {
1887
- window.addEventListener('pageshow', this.updateCountMessage.bind(this));
1888
- } else {
1889
- window.addEventListener('DOMContentLoaded', this.updateCountMessage.bind(this));
1890
- }
1891
- this.updateCountMessage();
1892
- };
1893
-
1894
- /**
1895
- * Bind change events
1896
- *
1897
- * Set up event listeners on the $textarea so that the count messages update
1898
- * when the user types.
1899
- */
1900
- CharacterCount.prototype.bindChangeEvents = function () {
1901
- var $textarea = this.$textarea;
1902
- $textarea.addEventListener('keyup', this.handleKeyUp.bind(this));
1903
-
1904
- // Bind focus/blur events to start/stop polling
1905
- $textarea.addEventListener('focus', this.handleFocus.bind(this));
1906
- $textarea.addEventListener('blur', this.handleBlur.bind(this));
1907
- };
1908
-
1909
- /**
1910
- * Handle key up event
1911
- *
1912
- * Update the visible character counter and keep track of when the last update
1913
- * happened for each keypress
1914
- */
1915
- CharacterCount.prototype.handleKeyUp = function () {
1916
- this.updateVisibleCountMessage();
1917
- this.lastInputTimestamp = Date.now();
1918
- };
1919
-
1920
- /**
1921
- * Handle focus event
1922
- *
1923
- * Speech recognition software such as Dragon NaturallySpeaking will modify the
1924
- * fields by directly changing its `value`. These changes don't trigger events
1925
- * in JavaScript, so we need to poll to handle when and if they occur.
1926
- *
1927
- * Once the keyup event hasn't been detected for at least 1000 ms (1s), check if
1928
- * the textarea value has changed and update the count message if it has.
1929
- *
1930
- * This is so that the update triggered by the manual comparison doesn't
1931
- * conflict with debounced KeyboardEvent updates.
1932
- */
1933
- CharacterCount.prototype.handleFocus = function () {
1934
- this.valueChecker = setInterval(function () {
1935
- if (!this.lastInputTimestamp || (Date.now() - 500) >= this.lastInputTimestamp) {
1936
- this.updateIfValueChanged();
1842
+ /**
1843
+ * Initialise component
1844
+ */
1845
+ CharacterCount.prototype.init = function () {
1846
+ // Check that required elements are present
1847
+ if (!this.$textarea) {
1848
+ return
1849
+ }
1850
+
1851
+ var $textarea = this.$textarea;
1852
+ var $textareaDescription = document.getElementById($textarea.id + '-info');
1853
+
1854
+ // Inject a decription for the textarea if none is present already
1855
+ // for when the component was rendered with no maxlength, maxwords
1856
+ // nor custom textareaDescriptionText
1857
+ if ($textareaDescription.innerText.match(/^\s*$/)) {
1858
+ $textareaDescription.innerText = this.i18n.t('textareaDescription', { count: this.maxLength });
1859
+ }
1860
+
1861
+ // Move the textarea description to be immediately after the textarea
1862
+ // Kept for backwards compatibility
1863
+ $textarea.insertAdjacentElement('afterend', $textareaDescription);
1864
+
1865
+ // Create the *screen reader* specific live-updating counter
1866
+ // This doesn't need any styling classes, as it is never visible
1867
+ var $screenReaderCountMessage = document.createElement('div');
1868
+ $screenReaderCountMessage.className = 'govuk-character-count__sr-status govuk-visually-hidden';
1869
+ $screenReaderCountMessage.setAttribute('aria-live', 'polite');
1870
+ this.$screenReaderCountMessage = $screenReaderCountMessage;
1871
+ $textareaDescription.insertAdjacentElement('afterend', $screenReaderCountMessage);
1872
+
1873
+ // Create our live-updating counter element, copying the classes from the
1874
+ // textarea description for backwards compatibility as these may have been
1875
+ // configured
1876
+ var $visibleCountMessage = document.createElement('div');
1877
+ $visibleCountMessage.className = $textareaDescription.className;
1878
+ $visibleCountMessage.classList.add('govuk-character-count__status');
1879
+ $visibleCountMessage.setAttribute('aria-hidden', 'true');
1880
+ this.$visibleCountMessage = $visibleCountMessage;
1881
+ $textareaDescription.insertAdjacentElement('afterend', $visibleCountMessage);
1882
+
1883
+ // Hide the textarea description
1884
+ $textareaDescription.classList.add('govuk-visually-hidden');
1885
+
1886
+ // Remove hard limit if set
1887
+ $textarea.removeAttribute('maxlength');
1888
+
1889
+ this.bindChangeEvents();
1890
+
1891
+ // When the page is restored after navigating 'back' in some browsers the
1892
+ // state of the character count is not restored until *after* the
1893
+ // DOMContentLoaded event is fired, so we need to manually update it after the
1894
+ // pageshow event in browsers that support it.
1895
+ if ('onpageshow' in window) {
1896
+ window.addEventListener('pageshow', this.updateCountMessage.bind(this));
1897
+ } else {
1898
+ window.addEventListener('DOMContentLoaded', this.updateCountMessage.bind(this));
1937
1899
  }
1938
- }.bind(this), 1000);
1939
- };
1940
-
1941
- /**
1942
- * Handle blur event
1943
- *
1944
- * Stop checking the textarea value once the textarea no longer has focus
1945
- */
1946
- CharacterCount.prototype.handleBlur = function () {
1947
- // Cancel value checking on blur
1948
- clearInterval(this.valueChecker);
1949
- };
1950
-
1951
- /**
1952
- * Update count message if textarea value has changed
1953
- */
1954
- CharacterCount.prototype.updateIfValueChanged = function () {
1955
- if (!this.$textarea.oldValue) this.$textarea.oldValue = '';
1956
- if (this.$textarea.value !== this.$textarea.oldValue) {
1957
- this.$textarea.oldValue = this.$textarea.value;
1958
1900
  this.updateCountMessage();
1959
- }
1960
- };
1961
-
1962
- /**
1963
- * Update count message
1964
- *
1965
- * Helper function to update both the visible and screen reader-specific
1966
- * counters simultaneously (e.g. on init)
1967
- */
1968
- CharacterCount.prototype.updateCountMessage = function () {
1969
- this.updateVisibleCountMessage();
1970
- this.updateScreenReaderCountMessage();
1971
- };
1972
-
1973
- /**
1974
- * Update visible count message
1975
- */
1976
- CharacterCount.prototype.updateVisibleCountMessage = function () {
1977
- var $textarea = this.$textarea;
1978
- var $visibleCountMessage = this.$visibleCountMessage;
1979
- var remainingNumber = this.maxLength - this.count($textarea.value);
1980
-
1981
- // If input is over the threshold, remove the disabled class which renders the
1982
- // counter invisible.
1983
- if (this.isOverThreshold()) {
1984
- $visibleCountMessage.classList.remove('govuk-character-count__message--disabled');
1985
- } else {
1986
- $visibleCountMessage.classList.add('govuk-character-count__message--disabled');
1987
- }
1901
+ };
1988
1902
 
1989
- // Update styles
1990
- if (remainingNumber < 0) {
1991
- $textarea.classList.add('govuk-textarea--error');
1992
- $visibleCountMessage.classList.remove('govuk-hint');
1993
- $visibleCountMessage.classList.add('govuk-error-message');
1994
- } else {
1995
- $textarea.classList.remove('govuk-textarea--error');
1996
- $visibleCountMessage.classList.remove('govuk-error-message');
1997
- $visibleCountMessage.classList.add('govuk-hint');
1998
- }
1903
+ /**
1904
+ * Bind change events
1905
+ *
1906
+ * Set up event listeners on the $textarea so that the count messages update
1907
+ * when the user types.
1908
+ */
1909
+ CharacterCount.prototype.bindChangeEvents = function () {
1910
+ var $textarea = this.$textarea;
1911
+ $textarea.addEventListener('keyup', this.handleKeyUp.bind(this));
1999
1912
 
2000
- // Update message
2001
- $visibleCountMessage.innerText = this.getCountMessage();
2002
- };
2003
-
2004
- /**
2005
- * Update screen reader count message
2006
- */
2007
- CharacterCount.prototype.updateScreenReaderCountMessage = function () {
2008
- var $screenReaderCountMessage = this.$screenReaderCountMessage;
2009
-
2010
- // If over the threshold, remove the aria-hidden attribute, allowing screen
2011
- // readers to announce the content of the element.
2012
- if (this.isOverThreshold()) {
2013
- $screenReaderCountMessage.removeAttribute('aria-hidden');
2014
- } else {
2015
- $screenReaderCountMessage.setAttribute('aria-hidden', true);
2016
- }
1913
+ // Bind focus/blur events to start/stop polling
1914
+ $textarea.addEventListener('focus', this.handleFocus.bind(this));
1915
+ $textarea.addEventListener('blur', this.handleBlur.bind(this));
1916
+ };
2017
1917
 
2018
- // Update message
2019
- $screenReaderCountMessage.innerText = this.getCountMessage();
2020
- };
2021
-
2022
- /**
2023
- * Count the number of characters (or words, if `config.maxwords` is set)
2024
- * in the given text
2025
- *
2026
- * @param {string} text - The text to count the characters of
2027
- * @returns {number} the number of characters (or words) in the text
2028
- */
2029
- CharacterCount.prototype.count = function (text) {
2030
- if (this.config.maxwords) {
2031
- var tokens = text.match(/\S+/g) || []; // Matches consecutive non-whitespace chars
2032
- return tokens.length
2033
- } else {
2034
- return text.length
2035
- }
2036
- };
2037
-
2038
- /**
2039
- * Get count message
2040
- *
2041
- * @returns {string} Status message
2042
- */
2043
- CharacterCount.prototype.getCountMessage = function () {
2044
- var remainingNumber = this.maxLength - this.count(this.$textarea.value);
2045
-
2046
- var countType = this.config.maxwords ? 'words' : 'characters';
2047
- return this.formatCountMessage(remainingNumber, countType)
2048
- };
2049
-
2050
- /**
2051
- * Formats the message shown to users according to what's counted
2052
- * and how many remain
2053
- *
2054
- * @param {number} remainingNumber - The number of words/characaters remaining
2055
- * @param {string} countType - "words" or "characters"
2056
- * @returns {string} Status message
2057
- */
2058
- CharacterCount.prototype.formatCountMessage = function (remainingNumber, countType) {
2059
- if (remainingNumber === 0) {
2060
- return this.i18n.t(countType + 'AtLimit')
2061
- }
1918
+ /**
1919
+ * Handle key up event
1920
+ *
1921
+ * Update the visible character counter and keep track of when the last update
1922
+ * happened for each keypress
1923
+ */
1924
+ CharacterCount.prototype.handleKeyUp = function () {
1925
+ this.updateVisibleCountMessage();
1926
+ this.lastInputTimestamp = Date.now();
1927
+ };
2062
1928
 
2063
- var translationKeySuffix = remainingNumber < 0 ? 'OverLimit' : 'UnderLimit';
2064
-
2065
- return this.i18n.t(countType + translationKeySuffix, { count: Math.abs(remainingNumber) })
2066
- };
2067
-
2068
- /**
2069
- * Check if count is over threshold
2070
- *
2071
- * Checks whether the value is over the configured threshold for the input.
2072
- * If there is no configured threshold, it is set to 0 and this function will
2073
- * always return true.
2074
- *
2075
- * @returns {boolean} true if the current count is over the config.threshold
2076
- * (or no threshold is set)
2077
- */
2078
- CharacterCount.prototype.isOverThreshold = function () {
2079
- // No threshold means we're always above threshold so save some computation
2080
- if (!this.config.threshold) {
2081
- return true
2082
- }
1929
+ /**
1930
+ * Handle focus event
1931
+ *
1932
+ * Speech recognition software such as Dragon NaturallySpeaking will modify the
1933
+ * fields by directly changing its `value`. These changes don't trigger events
1934
+ * in JavaScript, so we need to poll to handle when and if they occur.
1935
+ *
1936
+ * Once the keyup event hasn't been detected for at least 1000 ms (1s), check if
1937
+ * the textarea value has changed and update the count message if it has.
1938
+ *
1939
+ * This is so that the update triggered by the manual comparison doesn't
1940
+ * conflict with debounced KeyboardEvent updates.
1941
+ */
1942
+ CharacterCount.prototype.handleFocus = function () {
1943
+ this.valueChecker = setInterval(function () {
1944
+ if (!this.lastInputTimestamp || (Date.now() - 500) >= this.lastInputTimestamp) {
1945
+ this.updateIfValueChanged();
1946
+ }
1947
+ }.bind(this), 1000);
1948
+ };
1949
+
1950
+ /**
1951
+ * Handle blur event
1952
+ *
1953
+ * Stop checking the textarea value once the textarea no longer has focus
1954
+ */
1955
+ CharacterCount.prototype.handleBlur = function () {
1956
+ // Cancel value checking on blur
1957
+ clearInterval(this.valueChecker);
1958
+ };
1959
+
1960
+ /**
1961
+ * Update count message if textarea value has changed
1962
+ */
1963
+ CharacterCount.prototype.updateIfValueChanged = function () {
1964
+ if (!this.$textarea.oldValue) this.$textarea.oldValue = '';
1965
+ if (this.$textarea.value !== this.$textarea.oldValue) {
1966
+ this.$textarea.oldValue = this.$textarea.value;
1967
+ this.updateCountMessage();
1968
+ }
1969
+ };
1970
+
1971
+ /**
1972
+ * Update count message
1973
+ *
1974
+ * Helper function to update both the visible and screen reader-specific
1975
+ * counters simultaneously (e.g. on init)
1976
+ */
1977
+ CharacterCount.prototype.updateCountMessage = function () {
1978
+ this.updateVisibleCountMessage();
1979
+ this.updateScreenReaderCountMessage();
1980
+ };
1981
+
1982
+ /**
1983
+ * Update visible count message
1984
+ */
1985
+ CharacterCount.prototype.updateVisibleCountMessage = function () {
1986
+ var $textarea = this.$textarea;
1987
+ var $visibleCountMessage = this.$visibleCountMessage;
1988
+ var remainingNumber = this.maxLength - this.count($textarea.value);
1989
+
1990
+ // If input is over the threshold, remove the disabled class which renders the
1991
+ // counter invisible.
1992
+ if (this.isOverThreshold()) {
1993
+ $visibleCountMessage.classList.remove('govuk-character-count__message--disabled');
1994
+ } else {
1995
+ $visibleCountMessage.classList.add('govuk-character-count__message--disabled');
1996
+ }
1997
+
1998
+ // Update styles
1999
+ if (remainingNumber < 0) {
2000
+ $textarea.classList.add('govuk-textarea--error');
2001
+ $visibleCountMessage.classList.remove('govuk-hint');
2002
+ $visibleCountMessage.classList.add('govuk-error-message');
2003
+ } else {
2004
+ $textarea.classList.remove('govuk-textarea--error');
2005
+ $visibleCountMessage.classList.remove('govuk-error-message');
2006
+ $visibleCountMessage.classList.add('govuk-hint');
2007
+ }
2008
+
2009
+ // Update message
2010
+ $visibleCountMessage.innerText = this.getCountMessage();
2011
+ };
2012
+
2013
+ /**
2014
+ * Update screen reader count message
2015
+ */
2016
+ CharacterCount.prototype.updateScreenReaderCountMessage = function () {
2017
+ var $screenReaderCountMessage = this.$screenReaderCountMessage;
2018
+
2019
+ // If over the threshold, remove the aria-hidden attribute, allowing screen
2020
+ // readers to announce the content of the element.
2021
+ if (this.isOverThreshold()) {
2022
+ $screenReaderCountMessage.removeAttribute('aria-hidden');
2023
+ } else {
2024
+ $screenReaderCountMessage.setAttribute('aria-hidden', true);
2025
+ }
2026
+
2027
+ // Update message
2028
+ $screenReaderCountMessage.innerText = this.getCountMessage();
2029
+ };
2030
+
2031
+ /**
2032
+ * Count the number of characters (or words, if `config.maxwords` is set)
2033
+ * in the given text
2034
+ *
2035
+ * @param {string} text - The text to count the characters of
2036
+ * @returns {number} the number of characters (or words) in the text
2037
+ */
2038
+ CharacterCount.prototype.count = function (text) {
2039
+ if (this.config.maxwords) {
2040
+ var tokens = text.match(/\S+/g) || []; // Matches consecutive non-whitespace chars
2041
+ return tokens.length
2042
+ } else {
2043
+ return text.length
2044
+ }
2045
+ };
2046
+
2047
+ /**
2048
+ * Get count message
2049
+ *
2050
+ * @returns {string} Status message
2051
+ */
2052
+ CharacterCount.prototype.getCountMessage = function () {
2053
+ var remainingNumber = this.maxLength - this.count(this.$textarea.value);
2054
+
2055
+ var countType = this.config.maxwords ? 'words' : 'characters';
2056
+ return this.formatCountMessage(remainingNumber, countType)
2057
+ };
2058
+
2059
+ /**
2060
+ * Formats the message shown to users according to what's counted
2061
+ * and how many remain
2062
+ *
2063
+ * @param {number} remainingNumber - The number of words/characaters remaining
2064
+ * @param {string} countType - "words" or "characters"
2065
+ * @returns {string} Status message
2066
+ */
2067
+ CharacterCount.prototype.formatCountMessage = function (remainingNumber, countType) {
2068
+ if (remainingNumber === 0) {
2069
+ return this.i18n.t(countType + 'AtLimit')
2070
+ }
2071
+
2072
+ var translationKeySuffix = remainingNumber < 0 ? 'OverLimit' : 'UnderLimit';
2073
+
2074
+ return this.i18n.t(countType + translationKeySuffix, { count: Math.abs(remainingNumber) })
2075
+ };
2076
+
2077
+ /**
2078
+ * Check if count is over threshold
2079
+ *
2080
+ * Checks whether the value is over the configured threshold for the input.
2081
+ * If there is no configured threshold, it is set to 0 and this function will
2082
+ * always return true.
2083
+ *
2084
+ * @returns {boolean} true if the current count is over the config.threshold
2085
+ * (or no threshold is set)
2086
+ */
2087
+ CharacterCount.prototype.isOverThreshold = function () {
2088
+ // No threshold means we're always above threshold so save some computation
2089
+ if (!this.config.threshold) {
2090
+ return true
2091
+ }
2092
+
2093
+ var $textarea = this.$textarea;
2094
+
2095
+ // Determine the remaining number of characters/words
2096
+ var currentLength = this.count($textarea.value);
2097
+ var maxLength = this.maxLength;
2098
+
2099
+ var thresholdValue = maxLength * this.config.threshold / 100;
2100
+
2101
+ return (thresholdValue <= currentLength)
2102
+ };
2103
+
2104
+ /**
2105
+ * Character count config
2106
+ *
2107
+ * @typedef {CharacterCountConfigWithMaxLength | CharacterCountConfigWithMaxWords} CharacterCountConfig
2108
+ */
2109
+
2110
+ /**
2111
+ * Character count config (with maximum number of characters)
2112
+ *
2113
+ * @typedef {object} CharacterCountConfigWithMaxLength
2114
+ * @property {number} [maxlength] - The maximum number of characters.
2115
+ * If maxwords is provided, the maxlength option will be ignored.
2116
+ * @property {number} [threshold = 0] - The percentage value of the limit at
2117
+ * which point the count message is displayed. If this attribute is set, the
2118
+ * count message will be hidden by default.
2119
+ * @property {CharacterCountTranslations} [i18n = CHARACTER_COUNT_TRANSLATIONS] - See constant {@link CHARACTER_COUNT_TRANSLATIONS}
2120
+ */
2121
+
2122
+ /**
2123
+ * Character count config (with maximum number of words)
2124
+ *
2125
+ * @typedef {object} CharacterCountConfigWithMaxWords
2126
+ * @property {number} [maxwords] - The maximum number of words. If maxwords is
2127
+ * provided, the maxlength option will be ignored.
2128
+ * @property {number} [threshold = 0] - The percentage value of the limit at
2129
+ * which point the count message is displayed. If this attribute is set, the
2130
+ * count message will be hidden by default.
2131
+ * @property {CharacterCountTranslations} [i18n = CHARACTER_COUNT_TRANSLATIONS] - See constant {@link CHARACTER_COUNT_TRANSLATIONS}
2132
+ */
2133
+
2134
+ /**
2135
+ * Character count translations
2136
+ *
2137
+ * @typedef {object} CharacterCountTranslations
2138
+ *
2139
+ * Messages shown to users as they type. It provides feedback on how many words
2140
+ * or characters they have remaining or if they are over the limit. This also
2141
+ * includes a message used as an accessible description for the textarea.
2142
+ * @property {TranslationPluralForms} [charactersUnderLimit] - Message displayed
2143
+ * when the number of characters is under the configured maximum, `maxlength`.
2144
+ * This message is displayed visually and through assistive technologies. The
2145
+ * component will replace the `%{count}` placeholder with the number of
2146
+ * remaining characters. This is a [pluralised list of
2147
+ * messages](https://frontend.design-system.service.gov.uk/localise-govuk-frontend).
2148
+ * @property {string} [charactersAtLimit] - Message displayed when the number of
2149
+ * characters reaches the configured maximum, `maxlength`. This message is
2150
+ * displayed visually and through assistive technologies.
2151
+ * @property {TranslationPluralForms} [charactersOverLimit] - Message displayed
2152
+ * when the number of characters is over the configured maximum, `maxlength`.
2153
+ * This message is displayed visually and through assistive technologies. The
2154
+ * component will replace the `%{count}` placeholder with the number of
2155
+ * remaining characters. This is a [pluralised list of
2156
+ * messages](https://frontend.design-system.service.gov.uk/localise-govuk-frontend).
2157
+ * @property {TranslationPluralForms} [wordsUnderLimit] - Message displayed when
2158
+ * the number of words is under the configured maximum, `maxlength`. This
2159
+ * message is displayed visually and through assistive technologies. The
2160
+ * component will replace the `%{count}` placeholder with the number of
2161
+ * remaining words. This is a [pluralised list of
2162
+ * messages](https://frontend.design-system.service.gov.uk/localise-govuk-frontend).
2163
+ * @property {string} [wordsAtLimit] - Message displayed when the number of
2164
+ * words reaches the configured maximum, `maxlength`. This message is
2165
+ * displayed visually and through assistive technologies.
2166
+ * @property {TranslationPluralForms} [wordsOverLimit] - Message displayed when
2167
+ * the number of words is over the configured maximum, `maxlength`. This
2168
+ * message is displayed visually and through assistive technologies. The
2169
+ * component will replace the `%{count}` placeholder with the number of
2170
+ * remaining words. This is a [pluralised list of
2171
+ * messages](https://frontend.design-system.service.gov.uk/localise-govuk-frontend).
2172
+ * @property {TranslationPluralForms} [textareaDescription] - Message made
2173
+ * available to assistive technologies, if none is already present in the
2174
+ * HTML, to describe that the component accepts only a limited amount of
2175
+ * content. It is visible on the page when JavaScript is unavailable. The
2176
+ * component will replace the `%{count}` placeholder with the value of the
2177
+ * `maxlength` or `maxwords` parameter.
2178
+ */
2179
+
2180
+ /**
2181
+ * @typedef {import('../../i18n.mjs').TranslationPluralForms} TranslationPluralForms
2182
+ */
2083
2183
 
2084
- var $textarea = this.$textarea;
2085
-
2086
- // Determine the remaining number of characters/words
2087
- var currentLength = this.count($textarea.value);
2088
- var maxLength = this.maxLength;
2089
-
2090
- var thresholdValue = maxLength * this.config.threshold / 100;
2091
-
2092
- return (thresholdValue <= currentLength)
2093
- };
2094
-
2095
- /**
2096
- * Character count config
2097
- *
2098
- * @typedef {CharacterCountConfigWithMaxLength | CharacterCountConfigWithMaxWords} CharacterCountConfig
2099
- */
2100
-
2101
- /**
2102
- * Character count config (with maximum number of characters)
2103
- *
2104
- * @typedef {object} CharacterCountConfigWithMaxLength
2105
- * @property {number} [maxlength] - The maximum number of characters.
2106
- * If maxwords is provided, the maxlength option will be ignored.
2107
- * @property {number} [threshold = 0] - The percentage value of the limit at
2108
- * which point the count message is displayed. If this attribute is set, the
2109
- * count message will be hidden by default.
2110
- * @property {CharacterCountTranslations} [i18n = CHARACTER_COUNT_TRANSLATIONS] - See constant {@link CHARACTER_COUNT_TRANSLATIONS}
2111
- */
2112
-
2113
- /**
2114
- * Character count config (with maximum number of words)
2115
- *
2116
- * @typedef {object} CharacterCountConfigWithMaxWords
2117
- * @property {number} [maxwords] - The maximum number of words. If maxwords is
2118
- * provided, the maxlength option will be ignored.
2119
- * @property {number} [threshold = 0] - The percentage value of the limit at
2120
- * which point the count message is displayed. If this attribute is set, the
2121
- * count message will be hidden by default.
2122
- * @property {CharacterCountTranslations} [i18n = CHARACTER_COUNT_TRANSLATIONS] - See constant {@link CHARACTER_COUNT_TRANSLATIONS}
2123
- */
2124
-
2125
- /**
2126
- * Character count translations
2127
- *
2128
- * @typedef {object} CharacterCountTranslations
2129
- *
2130
- * Messages shown to users as they type. It provides feedback on how many words
2131
- * or characters they have remaining or if they are over the limit. This also
2132
- * includes a message used as an accessible description for the textarea.
2133
- * @property {TranslationPluralForms} [charactersUnderLimit] - Message displayed
2134
- * when the number of characters is under the configured maximum, `maxlength`.
2135
- * This message is displayed visually and through assistive technologies. The
2136
- * component will replace the `%{count}` placeholder with the number of
2137
- * remaining characters. This is a [pluralised list of
2138
- * messages](https://frontend.design-system.service.gov.uk/localise-govuk-frontend).
2139
- * @property {string} [charactersAtLimit] - Message displayed when the number of
2140
- * characters reaches the configured maximum, `maxlength`. This message is
2141
- * displayed visually and through assistive technologies.
2142
- * @property {TranslationPluralForms} [charactersOverLimit] - Message displayed
2143
- * when the number of characters is over the configured maximum, `maxlength`.
2144
- * This message is displayed visually and through assistive technologies. The
2145
- * component will replace the `%{count}` placeholder with the number of
2146
- * remaining characters. This is a [pluralised list of
2147
- * messages](https://frontend.design-system.service.gov.uk/localise-govuk-frontend).
2148
- * @property {TranslationPluralForms} [wordsUnderLimit] - Message displayed when
2149
- * the number of words is under the configured maximum, `maxlength`. This
2150
- * message is displayed visually and through assistive technologies. The
2151
- * component will replace the `%{count}` placeholder with the number of
2152
- * remaining words. This is a [pluralised list of
2153
- * messages](https://frontend.design-system.service.gov.uk/localise-govuk-frontend).
2154
- * @property {string} [wordsAtLimit] - Message displayed when the number of
2155
- * words reaches the configured maximum, `maxlength`. This message is
2156
- * displayed visually and through assistive technologies.
2157
- * @property {TranslationPluralForms} [wordsOverLimit] - Message displayed when
2158
- * the number of words is over the configured maximum, `maxlength`. This
2159
- * message is displayed visually and through assistive technologies. The
2160
- * component will replace the `%{count}` placeholder with the number of
2161
- * remaining words. This is a [pluralised list of
2162
- * messages](https://frontend.design-system.service.gov.uk/localise-govuk-frontend).
2163
- * @property {TranslationPluralForms} [textareaDescription] - Message made
2164
- * available to assistive technologies, if none is already present in the
2165
- * HTML, to describe that the component accepts only a limited amount of
2166
- * content. It is visible on the page when JavaScript is unavailable. The
2167
- * component will replace the `%{count}` placeholder with the value of the
2168
- * `maxlength` or `maxwords` parameter.
2169
- */
2170
-
2171
- /**
2172
- * @typedef {import('../../i18n.mjs').TranslationPluralForms} TranslationPluralForms
2173
- */
2174
-
2175
- return CharacterCount;
2184
+ return CharacterCount;
2176
2185
 
2177
2186
  })));
2187
+ //# sourceMappingURL=character-count.js.map