katalyst-govuk-formbuilder 1.2.2 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -7,18 +7,122 @@ function nodeListForEach(nodes, callback) {
7
7
  }
8
8
  }
9
9
 
10
+ function mergeConfigs() {
11
+ var flattenObject = function(configObject) {
12
+ var flattenedObject = {};
13
+ var flattenLoop = function(obj, prefix) {
14
+ for (var key in obj) {
15
+ if (!Object.prototype.hasOwnProperty.call(obj, key)) {
16
+ continue;
17
+ }
18
+ var value = obj[key];
19
+ var prefixedKey = prefix ? prefix + "." + key : key;
20
+ if (typeof value === "object") {
21
+ flattenLoop(value, prefixedKey);
22
+ } else {
23
+ flattenedObject[prefixedKey] = value;
24
+ }
25
+ }
26
+ };
27
+ flattenLoop(configObject);
28
+ return flattenedObject;
29
+ };
30
+ var formattedConfigObject = {};
31
+ for (var i = 0; i < arguments.length; i++) {
32
+ var obj = flattenObject(arguments[i]);
33
+ for (var key in obj) {
34
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
35
+ formattedConfigObject[key] = obj[key];
36
+ }
37
+ }
38
+ }
39
+ return formattedConfigObject;
40
+ }
41
+
42
+ function extractConfigByNamespace(configObject, namespace) {
43
+ if (!configObject || typeof configObject !== "object") {
44
+ throw new Error('Provide a `configObject` of type "object".');
45
+ }
46
+ if (!namespace || typeof namespace !== "string") {
47
+ throw new Error('Provide a `namespace` of type "string" to filter the `configObject` by.');
48
+ }
49
+ var newObject = {};
50
+ for (var key in configObject) {
51
+ var keyParts = key.split(".");
52
+ if (Object.prototype.hasOwnProperty.call(configObject, key) && keyParts[0] === namespace) {
53
+ if (keyParts.length > 1) {
54
+ keyParts.shift();
55
+ }
56
+ var newKey = keyParts.join(".");
57
+ newObject[newKey] = configObject[key];
58
+ }
59
+ }
60
+ return newObject;
61
+ }
62
+
10
63
  (function(undefined$1) {
11
- var detect = "Window" in this;
64
+ var detect = "defineProperty" in Object && function() {
65
+ try {
66
+ var a = {};
67
+ Object.defineProperty(a, "test", {
68
+ value: 42
69
+ });
70
+ return true;
71
+ } catch (e) {
72
+ return false;
73
+ }
74
+ }();
12
75
  if (detect) return;
13
- if (typeof WorkerGlobalScope === "undefined" && typeof importScripts !== "function") {
14
- (function(global) {
15
- if (global.constructor) {
16
- global.Window = global.constructor;
76
+ (function(nativeDefineProperty) {
77
+ var supportsAccessors = Object.prototype.hasOwnProperty("__defineGetter__");
78
+ var ERR_ACCESSORS_NOT_SUPPORTED = "Getters & setters cannot be defined on this javascript engine";
79
+ var ERR_VALUE_ACCESSORS = "A property cannot both have accessors and be writable or have a value";
80
+ Object.defineProperty = function defineProperty(object, property, descriptor) {
81
+ if (nativeDefineProperty && (object === window || object === document || object === Element.prototype || object instanceof Element)) {
82
+ return nativeDefineProperty(object, property, descriptor);
83
+ }
84
+ if (object === null || !(object instanceof Object || typeof object === "object")) {
85
+ throw new TypeError("Object.defineProperty called on non-object");
86
+ }
87
+ if (!(descriptor instanceof Object)) {
88
+ throw new TypeError("Property description must be an object");
89
+ }
90
+ var propertyString = String(property);
91
+ var hasValueOrWritable = "value" in descriptor || "writable" in descriptor;
92
+ var getterType = "get" in descriptor && typeof descriptor.get;
93
+ var setterType = "set" in descriptor && typeof descriptor.set;
94
+ if (getterType) {
95
+ if (getterType !== "function") {
96
+ throw new TypeError("Getter must be a function");
97
+ }
98
+ if (!supportsAccessors) {
99
+ throw new TypeError(ERR_ACCESSORS_NOT_SUPPORTED);
100
+ }
101
+ if (hasValueOrWritable) {
102
+ throw new TypeError(ERR_VALUE_ACCESSORS);
103
+ }
104
+ Object.__defineGetter__.call(object, propertyString, descriptor.get);
17
105
  } else {
18
- (global.Window = global.constructor = new Function("return function Window() {}")()).prototype = this;
106
+ object[propertyString] = descriptor.value;
19
107
  }
20
- })(this);
21
- }
108
+ if (setterType) {
109
+ if (setterType !== "function") {
110
+ throw new TypeError("Setter must be a function");
111
+ }
112
+ if (!supportsAccessors) {
113
+ throw new TypeError(ERR_ACCESSORS_NOT_SUPPORTED);
114
+ }
115
+ if (hasValueOrWritable) {
116
+ throw new TypeError(ERR_VALUE_ACCESSORS);
117
+ }
118
+ Object.__defineSetter__.call(object, propertyString, descriptor.set);
119
+ }
120
+ if ("value" in descriptor) {
121
+ object[propertyString] = descriptor.value;
122
+ }
123
+ return object;
124
+ };
125
+ })(Object.defineProperty);
22
126
  }).call("object" === typeof window && window || "object" === typeof self && self || "object" === typeof global && global || {});
23
127
 
24
128
  (function(undefined$1) {
@@ -105,68 +209,99 @@ function nodeListForEach(nodes, callback) {
105
209
  }).call("object" === typeof window && window || "object" === typeof self && self || "object" === typeof global && global || {});
106
210
 
107
211
  (function(undefined$1) {
108
- var detect = "defineProperty" in Object && function() {
109
- try {
110
- var a = {};
111
- Object.defineProperty(a, "test", {
112
- value: 42
113
- });
114
- return true;
115
- } catch (e) {
212
+ var detect = function() {
213
+ if (!document.documentElement.dataset) {
116
214
  return false;
117
215
  }
216
+ var el = document.createElement("div");
217
+ el.setAttribute("data-a-b", "c");
218
+ return el.dataset && el.dataset.aB == "c";
118
219
  }();
119
220
  if (detect) return;
120
- (function(nativeDefineProperty) {
121
- var supportsAccessors = Object.prototype.hasOwnProperty("__defineGetter__");
122
- var ERR_ACCESSORS_NOT_SUPPORTED = "Getters & setters cannot be defined on this javascript engine";
123
- var ERR_VALUE_ACCESSORS = "A property cannot both have accessors and be writable or have a value";
124
- Object.defineProperty = function defineProperty(object, property, descriptor) {
125
- if (nativeDefineProperty && (object === window || object === document || object === Element.prototype || object instanceof Element)) {
126
- return nativeDefineProperty(object, property, descriptor);
127
- }
128
- if (object === null || !(object instanceof Object || typeof object === "object")) {
129
- throw new TypeError("Object.defineProperty called on non-object");
130
- }
131
- if (!(descriptor instanceof Object)) {
132
- throw new TypeError("Property description must be an object");
133
- }
134
- var propertyString = String(property);
135
- var hasValueOrWritable = "value" in descriptor || "writable" in descriptor;
136
- var getterType = "get" in descriptor && typeof descriptor.get;
137
- var setterType = "set" in descriptor && typeof descriptor.set;
138
- if (getterType) {
139
- if (getterType !== "function") {
140
- throw new TypeError("Getter must be a function");
141
- }
142
- if (!supportsAccessors) {
143
- throw new TypeError(ERR_ACCESSORS_NOT_SUPPORTED);
144
- }
145
- if (hasValueOrWritable) {
146
- throw new TypeError(ERR_VALUE_ACCESSORS);
147
- }
148
- Object.__defineGetter__.call(object, propertyString, descriptor.get);
149
- } else {
150
- object[propertyString] = descriptor.value;
151
- }
152
- if (setterType) {
153
- if (setterType !== "function") {
154
- throw new TypeError("Setter must be a function");
155
- }
156
- if (!supportsAccessors) {
157
- throw new TypeError(ERR_ACCESSORS_NOT_SUPPORTED);
158
- }
159
- if (hasValueOrWritable) {
160
- throw new TypeError(ERR_VALUE_ACCESSORS);
221
+ Object.defineProperty(Element.prototype, "dataset", {
222
+ get: function() {
223
+ var element = this;
224
+ var attributes = this.attributes;
225
+ var map = {};
226
+ for (var i = 0; i < attributes.length; i++) {
227
+ var attribute = attributes[i];
228
+ if (attribute && attribute.name && /^data-\w[.\w-]*$/.test(attribute.name)) {
229
+ var name = attribute.name;
230
+ var value = attribute.value;
231
+ var propName = name.substr(5).replace(/-./g, (function(prop) {
232
+ return prop.charAt(1).toUpperCase();
233
+ }));
234
+ if ("__defineGetter__" in Object.prototype && "__defineSetter__" in Object.prototype) {
235
+ Object.defineProperty(map, propName, {
236
+ enumerable: true,
237
+ get: function() {
238
+ return this.value;
239
+ }.bind({
240
+ value: value || ""
241
+ }),
242
+ set: function setter(name, value) {
243
+ if (typeof value !== "undefined") {
244
+ this.setAttribute(name, value);
245
+ } else {
246
+ this.removeAttribute(name);
247
+ }
248
+ }.bind(element, name)
249
+ });
250
+ } else {
251
+ map[propName] = value;
252
+ }
161
253
  }
162
- Object.__defineSetter__.call(object, propertyString, descriptor.set);
163
254
  }
164
- if ("value" in descriptor) {
165
- object[propertyString] = descriptor.value;
255
+ return map;
256
+ }
257
+ });
258
+ }).call("object" === typeof window && window || "object" === typeof self && self || "object" === typeof global && global || {});
259
+
260
+ (function(undefined$1) {
261
+ var detect = "trim" in String.prototype;
262
+ if (detect) return;
263
+ String.prototype.trim = function() {
264
+ return this.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, "");
265
+ };
266
+ }).call("object" === typeof window && window || "object" === typeof self && self || "object" === typeof global && global || {});
267
+
268
+ function normaliseString(value) {
269
+ if (typeof value !== "string") {
270
+ return value;
271
+ }
272
+ var trimmedValue = value.trim();
273
+ if (trimmedValue === "true") {
274
+ return true;
275
+ }
276
+ if (trimmedValue === "false") {
277
+ return false;
278
+ }
279
+ if (trimmedValue.length > 0 && isFinite(trimmedValue)) {
280
+ return Number(trimmedValue);
281
+ }
282
+ return value;
283
+ }
284
+
285
+ function normaliseDataset(dataset) {
286
+ var out = {};
287
+ for (var key in dataset) {
288
+ out[key] = normaliseString(dataset[key]);
289
+ }
290
+ return out;
291
+ }
292
+
293
+ (function(undefined$1) {
294
+ var detect = "Window" in this;
295
+ if (detect) return;
296
+ if (typeof WorkerGlobalScope === "undefined" && typeof importScripts !== "function") {
297
+ (function(global) {
298
+ if (global.constructor) {
299
+ global.Window = global.constructor;
300
+ } else {
301
+ (global.Window = global.constructor = new Function("return function Window() {}")()).prototype = this;
166
302
  }
167
- return object;
168
- };
169
- })(Object.defineProperty);
303
+ })(this);
304
+ }
170
305
  }).call("object" === typeof window && window || "object" === typeof self && self || "object" === typeof global && global || {});
171
306
 
172
307
  (function(undefined$1) {
@@ -415,22 +550,36 @@ var KEY_SPACE = 32;
415
550
 
416
551
  var DEBOUNCE_TIMEOUT_IN_SECONDS = 1;
417
552
 
418
- function Button($module) {
553
+ function Button($module, config) {
554
+ if (!$module) {
555
+ return this;
556
+ }
419
557
  this.$module = $module;
420
558
  this.debounceFormSubmitTimer = null;
559
+ var defaultConfig = {
560
+ preventDoubleClick: false
561
+ };
562
+ this.config = mergeConfigs(defaultConfig, config || {}, normaliseDataset($module.dataset));
421
563
  }
422
564
 
565
+ Button.prototype.init = function() {
566
+ if (!this.$module) {
567
+ return;
568
+ }
569
+ this.$module.addEventListener("keydown", this.handleKeyDown);
570
+ this.$module.addEventListener("click", this.debounce.bind(this));
571
+ };
572
+
423
573
  Button.prototype.handleKeyDown = function(event) {
424
- var target = event.target;
425
- if (target.getAttribute("role") === "button" && event.keyCode === KEY_SPACE) {
574
+ var $target = event.target;
575
+ if ($target.getAttribute("role") === "button" && event.keyCode === KEY_SPACE) {
426
576
  event.preventDefault();
427
- target.click();
577
+ $target.click();
428
578
  }
429
579
  };
430
580
 
431
581
  Button.prototype.debounce = function(event) {
432
- var target = event.target;
433
- if (target.getAttribute("data-prevent-double-click") !== "true") {
582
+ if (!this.config.preventDoubleClick) {
434
583
  return;
435
584
  }
436
585
  if (this.debounceFormSubmitTimer) {
@@ -442,11 +591,260 @@ Button.prototype.debounce = function(event) {
442
591
  }.bind(this), DEBOUNCE_TIMEOUT_IN_SECONDS * 1e3);
443
592
  };
444
593
 
445
- Button.prototype.init = function() {
446
- this.$module.addEventListener("keydown", this.handleKeyDown);
447
- this.$module.addEventListener("click", this.debounce);
594
+ (function(undefined$1) {
595
+ var detect = "document" in this && "matches" in document.documentElement;
596
+ if (detect) return;
597
+ Element.prototype.matches = Element.prototype.webkitMatchesSelector || Element.prototype.oMatchesSelector || Element.prototype.msMatchesSelector || Element.prototype.mozMatchesSelector || function matches(selector) {
598
+ var element = this;
599
+ var elements = (element.document || element.ownerDocument).querySelectorAll(selector);
600
+ var index = 0;
601
+ while (elements[index] && elements[index] !== element) {
602
+ ++index;
603
+ }
604
+ return !!elements[index];
605
+ };
606
+ }).call("object" === typeof window && window || "object" === typeof self && self || "object" === typeof global && global || {});
607
+
608
+ (function(undefined$1) {
609
+ var detect = "document" in this && "closest" in document.documentElement;
610
+ if (detect) return;
611
+ Element.prototype.closest = function closest(selector) {
612
+ var node = this;
613
+ while (node) {
614
+ if (node.matches(selector)) return node; else node = "SVGElement" in window && node instanceof SVGElement ? node.parentNode : node.parentElement;
615
+ }
616
+ return null;
617
+ };
618
+ }).call("object" === typeof window && window || "object" === typeof self && self || "object" === typeof global && global || {});
619
+
620
+ function closestAttributeValue($element, attributeName) {
621
+ var closestElementWithAttribute = $element.closest("[" + attributeName + "]");
622
+ if (closestElementWithAttribute) {
623
+ return closestElementWithAttribute.getAttribute(attributeName);
624
+ }
625
+ }
626
+
627
+ function I18n(translations, config) {
628
+ this.translations = translations || {};
629
+ this.locale = config && config.locale || document.documentElement.lang || "en";
630
+ }
631
+
632
+ I18n.prototype.t = function(lookupKey, options) {
633
+ if (!lookupKey) {
634
+ throw new Error("i18n: lookup key missing");
635
+ }
636
+ if (options && typeof options.count !== "undefined") {
637
+ lookupKey = lookupKey + "." + this.getPluralSuffix(lookupKey, options.count);
638
+ }
639
+ if (lookupKey in this.translations) {
640
+ var translationString = this.translations[lookupKey];
641
+ if (translationString.match(/%{(.\S+)}/)) {
642
+ if (!options) {
643
+ throw new Error("i18n: cannot replace placeholders in string if no option data provided");
644
+ }
645
+ return this.replacePlaceholders(translationString, options);
646
+ } else {
647
+ return translationString;
648
+ }
649
+ } else {
650
+ return lookupKey;
651
+ }
652
+ };
653
+
654
+ I18n.prototype.replacePlaceholders = function(translationString, options) {
655
+ var formatter;
656
+ if (this.hasIntlNumberFormatSupport()) {
657
+ formatter = new Intl.NumberFormat(this.locale);
658
+ }
659
+ return translationString.replace(/%{(.\S+)}/g, (function(placeholderWithBraces, placeholderKey) {
660
+ if (Object.prototype.hasOwnProperty.call(options, placeholderKey)) {
661
+ var placeholderValue = options[placeholderKey];
662
+ if (placeholderValue === false) {
663
+ return "";
664
+ }
665
+ if (typeof placeholderValue === "number" && formatter) {
666
+ return formatter.format(placeholderValue);
667
+ }
668
+ return placeholderValue;
669
+ } else {
670
+ throw new Error("i18n: no data found to replace " + placeholderWithBraces + " placeholder in string");
671
+ }
672
+ }));
673
+ };
674
+
675
+ I18n.prototype.hasIntlPluralRulesSupport = function() {
676
+ return Boolean(window.Intl && ("PluralRules" in window.Intl && Intl.PluralRules.supportedLocalesOf(this.locale).length));
677
+ };
678
+
679
+ I18n.prototype.hasIntlNumberFormatSupport = function() {
680
+ return Boolean(window.Intl && ("NumberFormat" in window.Intl && Intl.NumberFormat.supportedLocalesOf(this.locale).length));
681
+ };
682
+
683
+ I18n.prototype.getPluralSuffix = function(lookupKey, count) {
684
+ count = Number(count);
685
+ if (!isFinite(count)) {
686
+ return "other";
687
+ }
688
+ var preferredForm;
689
+ if (this.hasIntlPluralRulesSupport()) {
690
+ preferredForm = new Intl.PluralRules(this.locale).select(count);
691
+ } else {
692
+ preferredForm = this.selectPluralFormUsingFallbackRules(count);
693
+ }
694
+ if (lookupKey + "." + preferredForm in this.translations) {
695
+ return preferredForm;
696
+ } else if (lookupKey + ".other" in this.translations) {
697
+ if (console && "warn" in console) {
698
+ console.warn('i18n: Missing plural form ".' + preferredForm + '" for "' + this.locale + '" locale. Falling back to ".other".');
699
+ }
700
+ return "other";
701
+ } else {
702
+ throw new Error('i18n: Plural form ".other" is required for "' + this.locale + '" locale');
703
+ }
704
+ };
705
+
706
+ I18n.prototype.selectPluralFormUsingFallbackRules = function(count) {
707
+ count = Math.abs(Math.floor(count));
708
+ var ruleset = this.getPluralRulesForLocale();
709
+ if (ruleset) {
710
+ return I18n.pluralRules[ruleset](count);
711
+ }
712
+ return "other";
448
713
  };
449
714
 
715
+ I18n.prototype.getPluralRulesForLocale = function() {
716
+ var locale = this.locale;
717
+ var localeShort = locale.split("-")[0];
718
+ for (var pluralRule in I18n.pluralRulesMap) {
719
+ if (Object.prototype.hasOwnProperty.call(I18n.pluralRulesMap, pluralRule)) {
720
+ var languages = I18n.pluralRulesMap[pluralRule];
721
+ for (var i = 0; i < languages.length; i++) {
722
+ if (languages[i] === locale || languages[i] === localeShort) {
723
+ return pluralRule;
724
+ }
725
+ }
726
+ }
727
+ }
728
+ };
729
+
730
+ I18n.pluralRulesMap = {
731
+ arabic: [ "ar" ],
732
+ chinese: [ "my", "zh", "id", "ja", "jv", "ko", "ms", "th", "vi" ],
733
+ french: [ "hy", "bn", "fr", "gu", "hi", "fa", "pa", "zu" ],
734
+ german: [ "af", "sq", "az", "eu", "bg", "ca", "da", "nl", "en", "et", "fi", "ka", "de", "el", "hu", "lb", "no", "so", "sw", "sv", "ta", "te", "tr", "ur" ],
735
+ irish: [ "ga" ],
736
+ russian: [ "ru", "uk" ],
737
+ scottish: [ "gd" ],
738
+ spanish: [ "pt-PT", "it", "es" ],
739
+ welsh: [ "cy" ]
740
+ };
741
+
742
+ I18n.pluralRules = {
743
+ arabic: function(n) {
744
+ if (n === 0) {
745
+ return "zero";
746
+ }
747
+ if (n === 1) {
748
+ return "one";
749
+ }
750
+ if (n === 2) {
751
+ return "two";
752
+ }
753
+ if (n % 100 >= 3 && n % 100 <= 10) {
754
+ return "few";
755
+ }
756
+ if (n % 100 >= 11 && n % 100 <= 99) {
757
+ return "many";
758
+ }
759
+ return "other";
760
+ },
761
+ chinese: function() {
762
+ return "other";
763
+ },
764
+ french: function(n) {
765
+ return n === 0 || n === 1 ? "one" : "other";
766
+ },
767
+ german: function(n) {
768
+ return n === 1 ? "one" : "other";
769
+ },
770
+ irish: function(n) {
771
+ if (n === 1) {
772
+ return "one";
773
+ }
774
+ if (n === 2) {
775
+ return "two";
776
+ }
777
+ if (n >= 3 && n <= 6) {
778
+ return "few";
779
+ }
780
+ if (n >= 7 && n <= 10) {
781
+ return "many";
782
+ }
783
+ return "other";
784
+ },
785
+ russian: function(n) {
786
+ var lastTwo = n % 100;
787
+ var last = lastTwo % 10;
788
+ if (last === 1 && lastTwo !== 11) {
789
+ return "one";
790
+ }
791
+ if (last >= 2 && last <= 4 && !(lastTwo >= 12 && lastTwo <= 14)) {
792
+ return "few";
793
+ }
794
+ if (last === 0 || last >= 5 && last <= 9 || lastTwo >= 11 && lastTwo <= 14) {
795
+ return "many";
796
+ }
797
+ return "other";
798
+ },
799
+ scottish: function(n) {
800
+ if (n === 1 || n === 11) {
801
+ return "one";
802
+ }
803
+ if (n === 2 || n === 12) {
804
+ return "two";
805
+ }
806
+ if (n >= 3 && n <= 10 || n >= 13 && n <= 19) {
807
+ return "few";
808
+ }
809
+ return "other";
810
+ },
811
+ spanish: function(n) {
812
+ if (n === 1) {
813
+ return "one";
814
+ }
815
+ if (n % 1e6 === 0 && n !== 0) {
816
+ return "many";
817
+ }
818
+ return "other";
819
+ },
820
+ welsh: function(n) {
821
+ if (n === 0) {
822
+ return "zero";
823
+ }
824
+ if (n === 1) {
825
+ return "one";
826
+ }
827
+ if (n === 2) {
828
+ return "two";
829
+ }
830
+ if (n === 3) {
831
+ return "few";
832
+ }
833
+ if (n === 6) {
834
+ return "many";
835
+ }
836
+ return "other";
837
+ }
838
+ };
839
+
840
+ (function(undefined$1) {
841
+ var detect = "Date" in self && "now" in self.Date && "getTime" in self.Date.prototype;
842
+ if (detect) return;
843
+ Date.now = function() {
844
+ return (new Date).getTime();
845
+ };
846
+ }).call("object" === typeof window && window || "object" === typeof self && self || "object" === typeof global && global || {});
847
+
450
848
  (function(undefined$1) {
451
849
  var detect = "DOMTokenList" in this && function(x) {
452
850
  return "classList" in x ? !x.classList.toggle("x", false) && !x.className : true;
@@ -685,7 +1083,57 @@ Button.prototype.init = function() {
685
1083
  })(this);
686
1084
  }).call("object" === typeof window && window || "object" === typeof self && self || "object" === typeof global && global || {});
687
1085
 
688
- function CharacterCount($module) {
1086
+ var CHARACTER_COUNT_TRANSLATIONS = {
1087
+ charactersUnderLimit: {
1088
+ one: "You have %{count} character remaining",
1089
+ other: "You have %{count} characters remaining"
1090
+ },
1091
+ charactersAtLimit: "You have 0 characters remaining",
1092
+ charactersOverLimit: {
1093
+ one: "You have %{count} character too many",
1094
+ other: "You have %{count} characters too many"
1095
+ },
1096
+ wordsUnderLimit: {
1097
+ one: "You have %{count} word remaining",
1098
+ other: "You have %{count} words remaining"
1099
+ },
1100
+ wordsAtLimit: "You have 0 words remaining",
1101
+ wordsOverLimit: {
1102
+ one: "You have %{count} word too many",
1103
+ other: "You have %{count} words too many"
1104
+ },
1105
+ textareaDescription: {
1106
+ other: ""
1107
+ }
1108
+ };
1109
+
1110
+ function CharacterCount($module, config) {
1111
+ if (!$module) {
1112
+ return this;
1113
+ }
1114
+ var defaultConfig = {
1115
+ threshold: 0,
1116
+ i18n: CHARACTER_COUNT_TRANSLATIONS
1117
+ };
1118
+ var datasetConfig = normaliseDataset($module.dataset);
1119
+ var configOverrides = {};
1120
+ if ("maxwords" in datasetConfig || "maxlength" in datasetConfig) {
1121
+ configOverrides = {
1122
+ maxlength: false,
1123
+ maxwords: false
1124
+ };
1125
+ }
1126
+ this.config = mergeConfigs(defaultConfig, config || {}, configOverrides, datasetConfig);
1127
+ this.i18n = new I18n(extractConfigByNamespace(this.config, "i18n"), {
1128
+ locale: closestAttributeValue($module, "lang")
1129
+ });
1130
+ if (this.config.maxwords) {
1131
+ this.maxLength = this.config.maxwords;
1132
+ } else if (this.config.maxlength) {
1133
+ this.maxLength = this.config.maxlength;
1134
+ } else {
1135
+ return;
1136
+ }
689
1137
  this.$module = $module;
690
1138
  this.$textarea = $module.querySelector(".govuk-js-character-count");
691
1139
  this.$visibleCountMessage = null;
@@ -693,40 +1141,30 @@ function CharacterCount($module) {
693
1141
  this.lastInputTimestamp = null;
694
1142
  }
695
1143
 
696
- CharacterCount.prototype.defaults = {
697
- characterCountAttribute: "data-maxlength",
698
- wordCountAttribute: "data-maxwords"
699
- };
700
-
701
1144
  CharacterCount.prototype.init = function() {
702
1145
  if (!this.$textarea) {
703
1146
  return;
704
1147
  }
705
- var $module = this.$module;
706
1148
  var $textarea = this.$textarea;
707
- var $fallbackLimitMessage = document.getElementById($textarea.id + "-info");
708
- $textarea.insertAdjacentElement("afterend", $fallbackLimitMessage);
1149
+ var $textareaDescription = document.getElementById($textarea.id + "-info");
1150
+ if ($textareaDescription.innerText.match(/^\s*$/)) {
1151
+ $textareaDescription.innerText = this.i18n.t("textareaDescription", {
1152
+ count: this.maxLength
1153
+ });
1154
+ }
1155
+ $textarea.insertAdjacentElement("afterend", $textareaDescription);
709
1156
  var $screenReaderCountMessage = document.createElement("div");
710
1157
  $screenReaderCountMessage.className = "govuk-character-count__sr-status govuk-visually-hidden";
711
1158
  $screenReaderCountMessage.setAttribute("aria-live", "polite");
712
1159
  this.$screenReaderCountMessage = $screenReaderCountMessage;
713
- $fallbackLimitMessage.insertAdjacentElement("afterend", $screenReaderCountMessage);
1160
+ $textareaDescription.insertAdjacentElement("afterend", $screenReaderCountMessage);
714
1161
  var $visibleCountMessage = document.createElement("div");
715
- $visibleCountMessage.className = $fallbackLimitMessage.className;
1162
+ $visibleCountMessage.className = $textareaDescription.className;
716
1163
  $visibleCountMessage.classList.add("govuk-character-count__status");
717
1164
  $visibleCountMessage.setAttribute("aria-hidden", "true");
718
1165
  this.$visibleCountMessage = $visibleCountMessage;
719
- $fallbackLimitMessage.insertAdjacentElement("afterend", $visibleCountMessage);
720
- $fallbackLimitMessage.classList.add("govuk-visually-hidden");
721
- this.options = this.getDataset($module);
722
- var countAttribute = this.defaults.characterCountAttribute;
723
- if (this.options.maxwords) {
724
- countAttribute = this.defaults.wordCountAttribute;
725
- }
726
- this.maxLength = $module.getAttribute(countAttribute);
727
- if (!this.maxLength) {
728
- return;
729
- }
1166
+ $textareaDescription.insertAdjacentElement("afterend", $visibleCountMessage);
1167
+ $textareaDescription.classList.add("govuk-visually-hidden");
730
1168
  $textarea.removeAttribute("maxlength");
731
1169
  this.bindChangeEvents();
732
1170
  if ("onpageshow" in window) {
@@ -737,32 +1175,6 @@ CharacterCount.prototype.init = function() {
737
1175
  this.updateCountMessage();
738
1176
  };
739
1177
 
740
- CharacterCount.prototype.getDataset = function(element) {
741
- var dataset = {};
742
- var attributes = element.attributes;
743
- if (attributes) {
744
- for (var i = 0; i < attributes.length; i++) {
745
- var attribute = attributes[i];
746
- var match = attribute.name.match(/^data-(.+)/);
747
- if (match) {
748
- dataset[match[1]] = attribute.value;
749
- }
750
- }
751
- }
752
- return dataset;
753
- };
754
-
755
- CharacterCount.prototype.count = function(text) {
756
- var length;
757
- if (this.options.maxwords) {
758
- var tokens = text.match(/\S+/g) || [];
759
- length = tokens.length;
760
- } else {
761
- length = text.length;
762
- }
763
- return length;
764
- };
765
-
766
1178
  CharacterCount.prototype.bindChangeEvents = function() {
767
1179
  var $textarea = this.$textarea;
768
1180
  $textarea.addEventListener("keyup", this.handleKeyUp.bind(this));
@@ -770,7 +1182,24 @@ CharacterCount.prototype.bindChangeEvents = function() {
770
1182
  $textarea.addEventListener("blur", this.handleBlur.bind(this));
771
1183
  };
772
1184
 
773
- CharacterCount.prototype.checkIfValueChanged = function() {
1185
+ CharacterCount.prototype.handleKeyUp = function() {
1186
+ this.updateVisibleCountMessage();
1187
+ this.lastInputTimestamp = Date.now();
1188
+ };
1189
+
1190
+ CharacterCount.prototype.handleFocus = function() {
1191
+ this.valueChecker = setInterval(function() {
1192
+ if (!this.lastInputTimestamp || Date.now() - 500 >= this.lastInputTimestamp) {
1193
+ this.updateIfValueChanged();
1194
+ }
1195
+ }.bind(this), 1e3);
1196
+ };
1197
+
1198
+ CharacterCount.prototype.handleBlur = function() {
1199
+ clearInterval(this.valueChecker);
1200
+ };
1201
+
1202
+ CharacterCount.prototype.updateIfValueChanged = function() {
774
1203
  if (!this.$textarea.oldValue) this.$textarea.oldValue = "";
775
1204
  if (this.$textarea.value !== this.$textarea.oldValue) {
776
1205
  this.$textarea.oldValue = this.$textarea.value;
@@ -801,7 +1230,7 @@ CharacterCount.prototype.updateVisibleCountMessage = function() {
801
1230
  $visibleCountMessage.classList.remove("govuk-error-message");
802
1231
  $visibleCountMessage.classList.add("govuk-hint");
803
1232
  }
804
- $visibleCountMessage.innerHTML = this.formattedUpdateMessage();
1233
+ $visibleCountMessage.innerText = this.getCountMessage();
805
1234
  };
806
1235
 
807
1236
  CharacterCount.prototype.updateScreenReaderCountMessage = function() {
@@ -811,52 +1240,45 @@ CharacterCount.prototype.updateScreenReaderCountMessage = function() {
811
1240
  } else {
812
1241
  $screenReaderCountMessage.setAttribute("aria-hidden", true);
813
1242
  }
814
- $screenReaderCountMessage.innerHTML = this.formattedUpdateMessage();
1243
+ $screenReaderCountMessage.innerText = this.getCountMessage();
815
1244
  };
816
1245
 
817
- CharacterCount.prototype.formattedUpdateMessage = function() {
818
- var $textarea = this.$textarea;
819
- var options = this.options;
820
- var remainingNumber = this.maxLength - this.count($textarea.value);
821
- var charVerb = "remaining";
822
- var charNoun = "character";
823
- var displayNumber = remainingNumber;
824
- if (options.maxwords) {
825
- charNoun = "word";
1246
+ CharacterCount.prototype.count = function(text) {
1247
+ if (this.config.maxwords) {
1248
+ var tokens = text.match(/\S+/g) || [];
1249
+ return tokens.length;
1250
+ } else {
1251
+ return text.length;
1252
+ }
1253
+ };
1254
+
1255
+ CharacterCount.prototype.getCountMessage = function() {
1256
+ var remainingNumber = this.maxLength - this.count(this.$textarea.value);
1257
+ var countType = this.config.maxwords ? "words" : "characters";
1258
+ return this.formatCountMessage(remainingNumber, countType);
1259
+ };
1260
+
1261
+ CharacterCount.prototype.formatCountMessage = function(remainingNumber, countType) {
1262
+ if (remainingNumber === 0) {
1263
+ return this.i18n.t(countType + "AtLimit");
826
1264
  }
827
- charNoun = charNoun + (remainingNumber === -1 || remainingNumber === 1 ? "" : "s");
828
- charVerb = remainingNumber < 0 ? "too many" : "remaining";
829
- displayNumber = Math.abs(remainingNumber);
830
- return "You have " + displayNumber + " " + charNoun + " " + charVerb;
1265
+ var translationKeySuffix = remainingNumber < 0 ? "OverLimit" : "UnderLimit";
1266
+ return this.i18n.t(countType + translationKeySuffix, {
1267
+ count: Math.abs(remainingNumber)
1268
+ });
831
1269
  };
832
1270
 
833
1271
  CharacterCount.prototype.isOverThreshold = function() {
1272
+ if (!this.config.threshold) {
1273
+ return true;
1274
+ }
834
1275
  var $textarea = this.$textarea;
835
- var options = this.options;
836
1276
  var currentLength = this.count($textarea.value);
837
1277
  var maxLength = this.maxLength;
838
- var thresholdPercent = options.threshold ? options.threshold : 0;
839
- var thresholdValue = maxLength * thresholdPercent / 100;
1278
+ var thresholdValue = maxLength * this.config.threshold / 100;
840
1279
  return thresholdValue <= currentLength;
841
1280
  };
842
1281
 
843
- CharacterCount.prototype.handleKeyUp = function() {
844
- this.updateVisibleCountMessage();
845
- this.lastInputTimestamp = Date.now();
846
- };
847
-
848
- CharacterCount.prototype.handleFocus = function() {
849
- this.valueChecker = setInterval(function() {
850
- if (!this.lastInputTimestamp || Date.now() - 500 >= this.lastInputTimestamp) {
851
- this.checkIfValueChanged();
852
- }
853
- }.bind(this), 1e3);
854
- };
855
-
856
- CharacterCount.prototype.handleBlur = function() {
857
- clearInterval(this.valueChecker);
858
- };
859
-
860
1282
  function Checkboxes($module) {
861
1283
  this.$module = $module;
862
1284
  this.$inputs = $module.querySelectorAll('input[type="checkbox"]');
@@ -866,11 +1288,11 @@ Checkboxes.prototype.init = function() {
866
1288
  var $module = this.$module;
867
1289
  var $inputs = this.$inputs;
868
1290
  nodeListForEach($inputs, (function($input) {
869
- var target = $input.getAttribute("data-aria-controls");
870
- if (!target || !document.getElementById(target)) {
1291
+ var targetId = $input.getAttribute("data-aria-controls");
1292
+ if (!targetId || !document.getElementById(targetId)) {
871
1293
  return;
872
1294
  }
873
- $input.setAttribute("aria-controls", target);
1295
+ $input.setAttribute("aria-controls", targetId);
874
1296
  $input.removeAttribute("data-aria-controls");
875
1297
  }));
876
1298
  if ("onpageshow" in window) {
@@ -918,53 +1340,34 @@ Checkboxes.prototype.unCheckExclusiveInputs = function($input) {
918
1340
  };
919
1341
 
920
1342
  Checkboxes.prototype.handleClick = function(event) {
921
- var $target = event.target;
922
- if ($target.type !== "checkbox") {
1343
+ var $clickedInput = event.target;
1344
+ if ($clickedInput.type !== "checkbox") {
923
1345
  return;
924
1346
  }
925
- var hasAriaControls = $target.getAttribute("aria-controls");
1347
+ var hasAriaControls = $clickedInput.getAttribute("aria-controls");
926
1348
  if (hasAriaControls) {
927
- this.syncConditionalRevealWithInputState($target);
1349
+ this.syncConditionalRevealWithInputState($clickedInput);
928
1350
  }
929
- if (!$target.checked) {
1351
+ if (!$clickedInput.checked) {
930
1352
  return;
931
1353
  }
932
- var hasBehaviourExclusive = $target.getAttribute("data-behaviour") === "exclusive";
1354
+ var hasBehaviourExclusive = $clickedInput.getAttribute("data-behaviour") === "exclusive";
933
1355
  if (hasBehaviourExclusive) {
934
- this.unCheckAllInputsExcept($target);
1356
+ this.unCheckAllInputsExcept($clickedInput);
935
1357
  } else {
936
- this.unCheckExclusiveInputs($target);
1358
+ this.unCheckExclusiveInputs($clickedInput);
937
1359
  }
938
1360
  };
939
1361
 
940
- (function(undefined$1) {
941
- var detect = "document" in this && "matches" in document.documentElement;
942
- if (detect) return;
943
- Element.prototype.matches = Element.prototype.webkitMatchesSelector || Element.prototype.oMatchesSelector || Element.prototype.msMatchesSelector || Element.prototype.mozMatchesSelector || function matches(selector) {
944
- var element = this;
945
- var elements = (element.document || element.ownerDocument).querySelectorAll(selector);
946
- var index = 0;
947
- while (elements[index] && elements[index] !== element) {
948
- ++index;
949
- }
950
- return !!elements[index];
951
- };
952
- }).call("object" === typeof window && window || "object" === typeof self && self || "object" === typeof global && global || {});
953
-
954
- (function(undefined$1) {
955
- var detect = "document" in this && "closest" in document.documentElement;
956
- if (detect) return;
957
- Element.prototype.closest = function closest(selector) {
958
- var node = this;
959
- while (node) {
960
- if (node.matches(selector)) return node; else node = "SVGElement" in window && node instanceof SVGElement ? node.parentNode : node.parentElement;
961
- }
962
- return null;
963
- };
964
- }).call("object" === typeof window && window || "object" === typeof self && self || "object" === typeof global && global || {});
965
-
966
- function ErrorSummary($module) {
1362
+ function ErrorSummary($module, config) {
1363
+ if (!$module) {
1364
+ return this;
1365
+ }
967
1366
  this.$module = $module;
1367
+ var defaultConfig = {
1368
+ disableAutoFocus: false
1369
+ };
1370
+ this.config = mergeConfigs(defaultConfig, config || {}, normaliseDataset($module.dataset));
968
1371
  }
969
1372
 
970
1373
  ErrorSummary.prototype.init = function() {
@@ -978,7 +1381,7 @@ ErrorSummary.prototype.init = function() {
978
1381
 
979
1382
  ErrorSummary.prototype.setFocus = function() {
980
1383
  var $module = this.$module;
981
- if ($module.getAttribute("data-disable-auto-focus") === "true") {
1384
+ if (this.config.disableAutoFocus) {
982
1385
  return;
983
1386
  }
984
1387
  $module.setAttribute("tabindex", "-1");
@@ -989,8 +1392,8 @@ ErrorSummary.prototype.setFocus = function() {
989
1392
  };
990
1393
 
991
1394
  ErrorSummary.prototype.handleClick = function(event) {
992
- var target = event.target;
993
- if (this.focusTarget(target)) {
1395
+ var $target = event.target;
1396
+ if (this.focusTarget($target)) {
994
1397
  event.preventDefault();
995
1398
  }
996
1399
  };
@@ -1025,9 +1428,9 @@ ErrorSummary.prototype.getFragmentFromUrl = function(url) {
1025
1428
  ErrorSummary.prototype.getAssociatedLegendOrLabel = function($input) {
1026
1429
  var $fieldset = $input.closest("fieldset");
1027
1430
  if ($fieldset) {
1028
- var legends = $fieldset.getElementsByTagName("legend");
1029
- if (legends.length) {
1030
- var $candidateLegend = legends[0];
1431
+ var $legends = $fieldset.getElementsByTagName("legend");
1432
+ if ($legends.length) {
1433
+ var $candidateLegend = $legends[0];
1031
1434
  if ($input.type === "checkbox" || $input.type === "radio") {
1032
1435
  return $candidateLegend;
1033
1436
  }
@@ -1053,11 +1456,11 @@ Radios.prototype.init = function() {
1053
1456
  var $module = this.$module;
1054
1457
  var $inputs = this.$inputs;
1055
1458
  nodeListForEach($inputs, (function($input) {
1056
- var target = $input.getAttribute("data-aria-controls");
1057
- if (!target || !document.getElementById(target)) {
1459
+ var targetId = $input.getAttribute("data-aria-controls");
1460
+ if (!targetId || !document.getElementById(targetId)) {
1058
1461
  return;
1059
1462
  }
1060
- $input.setAttribute("aria-controls", target);
1463
+ $input.setAttribute("aria-controls", targetId);
1061
1464
  $input.removeAttribute("data-aria-controls");
1062
1465
  }));
1063
1466
  if ("onpageshow" in window) {