katalyst-govuk-formbuilder 1.5.0 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (174) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/builds/katalyst/govuk/formbuilder.css +534 -453
  3. data/app/assets/builds/katalyst/govuk/formbuilder.js +1865 -1379
  4. data/app/assets/builds/katalyst/govuk/formbuilder.min.js +85 -1
  5. data/app/assets/stylesheets/katalyst/govuk/components/richtextarea/_index.scss +5 -5
  6. data/app/assets/stylesheets/katalyst/govuk/components/richtextarea/_richtextarea.scss +1 -1
  7. data/app/assets/stylesheets/katalyst/govuk/formbuilder.scss +18 -18
  8. data/lib/katalyst/govuk/formbuilder/frontend.rb +2 -1
  9. data/lib/katalyst/govuk/formbuilder.rb +0 -1
  10. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/_base.scss +2 -0
  11. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/all.scss +2 -0
  12. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/components/_all.scss +8 -4
  13. data/vendor/assets/stylesheets/govuk-frontend/dist/govuk/components/accordion/_accordion.scss +4 -0
  14. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/components/accordion/_index.scss +19 -45
  15. data/vendor/assets/stylesheets/govuk-frontend/dist/govuk/components/back-link/_back-link.scss +4 -0
  16. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/components/back-link/_index.scss +18 -34
  17. data/vendor/assets/stylesheets/govuk-frontend/dist/govuk/components/breadcrumbs/_breadcrumbs.scss +4 -0
  18. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/components/breadcrumbs/_index.scss +22 -35
  19. data/vendor/assets/stylesheets/govuk-frontend/dist/govuk/components/button/_button.scss +4 -0
  20. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/components/button/_index.scss +56 -91
  21. data/vendor/assets/stylesheets/govuk-frontend/dist/govuk/components/character-count/_character-count.scss +4 -0
  22. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/components/character-count/_index.scss +3 -1
  23. data/vendor/assets/stylesheets/govuk-frontend/dist/govuk/components/checkboxes/_checkboxes.scss +4 -0
  24. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/components/checkboxes/_index.scss +69 -91
  25. data/vendor/assets/stylesheets/govuk-frontend/dist/govuk/components/cookie-banner/_cookie-banner.scss +4 -0
  26. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/components/cookie-banner/_index.scss +3 -2
  27. data/vendor/assets/stylesheets/govuk-frontend/dist/govuk/components/date-input/_date-input.scss +4 -0
  28. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/components/date-input/_index.scss +2 -0
  29. data/vendor/assets/stylesheets/govuk-frontend/dist/govuk/components/details/_details.scss +4 -0
  30. data/vendor/assets/stylesheets/govuk-frontend/dist/govuk/components/details/_index.scss +134 -0
  31. data/vendor/assets/stylesheets/govuk-frontend/dist/govuk/components/error-message/_error-message.scss +4 -0
  32. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/components/error-message/_index.scss +2 -0
  33. data/vendor/assets/stylesheets/govuk-frontend/dist/govuk/components/error-summary/_error-summary.scss +4 -0
  34. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/components/error-summary/_index.scss +5 -3
  35. data/vendor/assets/stylesheets/govuk-frontend/dist/govuk/components/exit-this-page/_exit-this-page.scss +4 -0
  36. data/vendor/assets/stylesheets/govuk-frontend/dist/govuk/components/exit-this-page/_index.scss +92 -0
  37. data/vendor/assets/stylesheets/govuk-frontend/dist/govuk/components/fieldset/_fieldset.scss +4 -0
  38. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/components/fieldset/_index.scss +18 -11
  39. data/vendor/assets/stylesheets/govuk-frontend/dist/govuk/components/file-upload/_file-upload.scss +4 -0
  40. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/components/file-upload/_index.scss +3 -6
  41. data/vendor/assets/stylesheets/govuk-frontend/dist/govuk/components/footer/_footer.scss +4 -0
  42. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/components/footer/_index.scss +16 -57
  43. data/vendor/assets/stylesheets/govuk-frontend/dist/govuk/components/header/_header.scss +4 -0
  44. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/components/header/_index.scss +55 -75
  45. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/components/hint/_hint.scss +2 -0
  46. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/components/hint/_index.scss +3 -0
  47. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/components/input/_index.scss +12 -35
  48. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/components/input/_input.scss +2 -0
  49. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/components/inset-text/_index.scss +2 -0
  50. data/vendor/assets/stylesheets/govuk-frontend/dist/govuk/components/inset-text/_inset-text.scss +4 -0
  51. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/components/label/_index.scss +12 -7
  52. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/components/label/_label.scss +2 -0
  53. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/components/notification-banner/_index.scss +8 -5
  54. data/vendor/assets/stylesheets/govuk-frontend/dist/govuk/components/notification-banner/_notification-banner.scss +4 -0
  55. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/components/pagination/_index.scss +12 -18
  56. data/vendor/assets/stylesheets/govuk-frontend/dist/govuk/components/pagination/_pagination.scss +4 -0
  57. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/components/panel/_index.scss +6 -8
  58. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/components/panel/_panel.scss +2 -0
  59. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/components/phase-banner/_index.scss +11 -0
  60. data/vendor/assets/stylesheets/govuk-frontend/dist/govuk/components/phase-banner/_phase-banner.scss +4 -0
  61. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/components/radios/_index.scss +38 -54
  62. data/vendor/assets/stylesheets/govuk-frontend/dist/govuk/components/radios/_radios.scss +4 -0
  63. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/components/select/_index.scss +4 -11
  64. data/vendor/assets/stylesheets/govuk-frontend/dist/govuk/components/select/_select.scss +4 -0
  65. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/components/skip-link/_index.scss +2 -0
  66. data/vendor/assets/stylesheets/govuk-frontend/dist/govuk/components/skip-link/_skip-link.scss +4 -0
  67. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/components/summary-list/_index.scss +20 -19
  68. data/vendor/assets/stylesheets/govuk-frontend/dist/govuk/components/summary-list/_summary-list.scss +4 -0
  69. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/components/table/_index.scss +10 -16
  70. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/components/table/_table.scss +2 -0
  71. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/components/tabs/_index.scss +13 -9
  72. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/components/tabs/_tabs.scss +2 -0
  73. data/vendor/assets/stylesheets/govuk-frontend/dist/govuk/components/tag/_index.scss +94 -0
  74. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/components/tag/_tag.scss +2 -0
  75. data/vendor/assets/stylesheets/govuk-frontend/dist/govuk/components/task-list/_index.scss +77 -0
  76. data/vendor/assets/stylesheets/govuk-frontend/dist/govuk/components/task-list/_task-list.scss +4 -0
  77. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/components/textarea/_index.scss +3 -7
  78. data/vendor/assets/stylesheets/govuk-frontend/dist/govuk/components/textarea/_textarea.scss +4 -0
  79. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/components/warning-text/_index.scss +7 -8
  80. data/vendor/assets/stylesheets/govuk-frontend/dist/govuk/components/warning-text/_warning-text.scss +4 -0
  81. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/core/_all.scss +2 -0
  82. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/core/_global-styles.scss +2 -1
  83. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/core/_govuk-frontend-version.scss +3 -1
  84. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/core/_links.scss +8 -1
  85. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/core/_lists.scss +2 -1
  86. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/core/_section-break.scss +2 -7
  87. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/core/_typography.scss +5 -4
  88. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/helpers/_all.scss +2 -0
  89. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/helpers/_clearfix.scss +3 -1
  90. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/helpers/_colour.scss +12 -19
  91. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/helpers/_device-pixels.scss +4 -3
  92. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/helpers/_focused.scss +22 -6
  93. data/vendor/assets/stylesheets/govuk-frontend/dist/govuk/helpers/_font-faces.scss +41 -0
  94. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/helpers/_grid.scss +2 -0
  95. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/helpers/_links.scss +36 -74
  96. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/helpers/_media-queries.scss +3 -12
  97. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/helpers/_shape-arrow.scss +2 -0
  98. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/helpers/_spacing.scss +12 -10
  99. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/helpers/_typography.scss +12 -33
  100. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/helpers/_visually-hidden.scss +33 -0
  101. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/objects/_all.scss +2 -0
  102. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/objects/_button-group.scss +7 -12
  103. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/objects/_form-group.scss +2 -0
  104. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/objects/_grid.scss +4 -2
  105. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/objects/_main-wrapper.scss +2 -0
  106. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/objects/_template.scss +22 -1
  107. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/objects/_width-container.scss +2 -9
  108. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/overrides/_all.scss +2 -0
  109. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/overrides/_display.scss +2 -0
  110. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/overrides/_spacing.scss +3 -19
  111. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/overrides/_text-align.scss +2 -0
  112. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/overrides/_typography.scss +2 -0
  113. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/overrides/_width.scss +2 -0
  114. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/settings/_all.scss +2 -3
  115. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/settings/_assets.scss +2 -0
  116. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/settings/_colours-applied.scss +9 -7
  117. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/settings/_colours-organisations.scss +4 -2
  118. data/vendor/assets/stylesheets/govuk-frontend/dist/govuk/settings/_colours-palette.scss +37 -0
  119. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/settings/_global-styles.scss +2 -0
  120. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/settings/_links.scss +3 -20
  121. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/settings/_measurements.scss +17 -5
  122. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/settings/_media-queries.scss +4 -2
  123. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/settings/_spacing.scss +2 -0
  124. data/vendor/assets/stylesheets/govuk-frontend/dist/govuk/settings/_typography-font.scss +52 -0
  125. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/settings/_typography-responsive.scss +2 -39
  126. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/settings/_warnings.scss +25 -5
  127. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/tools/_all.scss +2 -2
  128. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/tools/_exports.scss +2 -0
  129. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/tools/_font-url.scss +3 -0
  130. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/tools/_image-url.scss +3 -0
  131. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/tools/_px-to-em.scss +2 -0
  132. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/tools/_px-to-rem.scss +2 -0
  133. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/utilities/_all.scss +2 -0
  134. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/utilities/_clearfix.scss +2 -0
  135. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/utilities/_visually-hidden.scss +2 -0
  136. data/vendor/assets/stylesheets/govuk-frontend/{govuk → dist/govuk}/vendor/_sass-mq.scss +3 -1
  137. metadata +130 -133
  138. data/lib/katalyst/govuk/formbuilder/version.rb +0 -9
  139. data/vendor/assets/stylesheets/govuk-frontend/govuk/all-ie8.scss +0 -14
  140. data/vendor/assets/stylesheets/govuk-frontend/govuk/components/accordion/_accordion.scss +0 -2
  141. data/vendor/assets/stylesheets/govuk-frontend/govuk/components/back-link/_back-link.scss +0 -2
  142. data/vendor/assets/stylesheets/govuk-frontend/govuk/components/breadcrumbs/_breadcrumbs.scss +0 -2
  143. data/vendor/assets/stylesheets/govuk-frontend/govuk/components/button/_button.scss +0 -2
  144. data/vendor/assets/stylesheets/govuk-frontend/govuk/components/character-count/_character-count.scss +0 -2
  145. data/vendor/assets/stylesheets/govuk-frontend/govuk/components/checkboxes/_checkboxes.scss +0 -2
  146. data/vendor/assets/stylesheets/govuk-frontend/govuk/components/cookie-banner/_cookie-banner.scss +0 -2
  147. data/vendor/assets/stylesheets/govuk-frontend/govuk/components/date-input/_date-input.scss +0 -2
  148. data/vendor/assets/stylesheets/govuk-frontend/govuk/components/details/_details.scss +0 -2
  149. data/vendor/assets/stylesheets/govuk-frontend/govuk/components/details/_index.scss +0 -88
  150. data/vendor/assets/stylesheets/govuk-frontend/govuk/components/error-message/_error-message.scss +0 -2
  151. data/vendor/assets/stylesheets/govuk-frontend/govuk/components/error-summary/_error-summary.scss +0 -2
  152. data/vendor/assets/stylesheets/govuk-frontend/govuk/components/fieldset/_fieldset.scss +0 -2
  153. data/vendor/assets/stylesheets/govuk-frontend/govuk/components/file-upload/_file-upload.scss +0 -2
  154. data/vendor/assets/stylesheets/govuk-frontend/govuk/components/footer/_footer.scss +0 -2
  155. data/vendor/assets/stylesheets/govuk-frontend/govuk/components/header/_header.scss +0 -2
  156. data/vendor/assets/stylesheets/govuk-frontend/govuk/components/inset-text/_inset-text.scss +0 -2
  157. data/vendor/assets/stylesheets/govuk-frontend/govuk/components/notification-banner/_notification-banner.scss +0 -2
  158. data/vendor/assets/stylesheets/govuk-frontend/govuk/components/pagination/_pagination.scss +0 -2
  159. data/vendor/assets/stylesheets/govuk-frontend/govuk/components/phase-banner/_phase-banner.scss +0 -2
  160. data/vendor/assets/stylesheets/govuk-frontend/govuk/components/radios/_radios.scss +0 -2
  161. data/vendor/assets/stylesheets/govuk-frontend/govuk/components/select/_select.scss +0 -2
  162. data/vendor/assets/stylesheets/govuk-frontend/govuk/components/skip-link/_skip-link.scss +0 -2
  163. data/vendor/assets/stylesheets/govuk-frontend/govuk/components/summary-list/_summary-list.scss +0 -2
  164. data/vendor/assets/stylesheets/govuk-frontend/govuk/components/tag/_index.scss +0 -81
  165. data/vendor/assets/stylesheets/govuk-frontend/govuk/components/textarea/_textarea.scss +0 -2
  166. data/vendor/assets/stylesheets/govuk-frontend/govuk/components/warning-text/_warning-text.scss +0 -2
  167. data/vendor/assets/stylesheets/govuk-frontend/govuk/helpers/_font-faces.scss +0 -41
  168. data/vendor/assets/stylesheets/govuk-frontend/govuk/settings/_colours-palette.scss +0 -120
  169. data/vendor/assets/stylesheets/govuk-frontend/govuk/settings/_compatibility.scss +0 -100
  170. data/vendor/assets/stylesheets/govuk-frontend/govuk/settings/_ie8.scss +0 -34
  171. data/vendor/assets/stylesheets/govuk-frontend/govuk/settings/_typography-font-families.scss +0 -32
  172. data/vendor/assets/stylesheets/govuk-frontend/govuk/settings/_typography-font.scss +0 -112
  173. data/vendor/assets/stylesheets/govuk-frontend/govuk/tools/_compatibility.scss +0 -50
  174. data/vendor/assets/stylesheets/govuk-frontend/govuk/tools/_ie8.scss +0 -87
@@ -1,279 +1,82 @@
1
- function nodeListForEach(nodes, callback) {
2
- if (window.NodeList.prototype.forEach) {
3
- return nodes.forEach(callback);
4
- }
5
- for (var i = 0; i < nodes.length; i++) {
6
- callback.call(window, nodes[i], i, nodes);
7
- }
8
- }
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") {
1
+ function mergeConfigs(...configObjects) {
2
+ function flattenObject(configObject) {
3
+ const flattenedObject = {};
4
+ function flattenLoop(obj, prefix) {
5
+ for (const [key, value] of Object.entries(obj)) {
6
+ const prefixedKey = prefix ? `${prefix}.${key}` : key;
7
+ if (value && typeof value === 'object') {
21
8
  flattenLoop(value, prefixedKey);
22
9
  } else {
23
10
  flattenedObject[prefixedKey] = value;
24
11
  }
25
12
  }
26
- };
13
+ }
27
14
  flattenLoop(configObject);
28
15
  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
- }
16
+ }
17
+ const formattedConfigObject = {};
18
+ for (const configObject of configObjects) {
19
+ const obj = flattenObject(configObject);
20
+ for (const [key, value] of Object.entries(obj)) {
21
+ formattedConfigObject[key] = value;
37
22
  }
38
23
  }
39
24
  return formattedConfigObject;
40
25
  }
41
-
42
26
  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) {
27
+ const newObject = {};
28
+ for (const [key, value] of Object.entries(configObject)) {
29
+ const keyParts = key.split('.');
30
+ if (keyParts[0] === namespace) {
53
31
  if (keyParts.length > 1) {
54
32
  keyParts.shift();
55
33
  }
56
- var newKey = keyParts.join(".");
57
- newObject[newKey] = configObject[key];
34
+ const newKey = keyParts.join('.');
35
+ newObject[newKey] = value;
58
36
  }
59
37
  }
60
38
  return newObject;
61
39
  }
62
-
63
- (function(undefined$1) {
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
- }();
75
- if (detect) return;
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);
105
- } else {
106
- object[propertyString] = descriptor.value;
107
- }
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);
126
- }).call("object" === typeof window && window || "object" === typeof self && self || "object" === typeof global && global || {});
127
-
128
- (function(undefined$1) {
129
- var detect = "Document" in this;
130
- if (detect) return;
131
- if (typeof WorkerGlobalScope === "undefined" && typeof importScripts !== "function") {
132
- if (this.HTMLDocument) {
133
- this.Document = this.HTMLDocument;
134
- } else {
135
- this.Document = this.HTMLDocument = document.constructor = new Function("return function Document() {}")();
136
- this.Document.prototype = document;
137
- }
40
+ function getFragmentFromUrl(url) {
41
+ if (!url.includes('#')) {
42
+ return undefined;
138
43
  }
139
- }).call("object" === typeof window && window || "object" === typeof self && self || "object" === typeof global && global || {});
140
-
141
- (function(undefined$1) {
142
- var detect = "Element" in this && "HTMLElement" in this;
143
- if (detect) return;
144
- (function() {
145
- if (window.Element && !window.HTMLElement) {
146
- window.HTMLElement = window.Element;
147
- return;
148
- }
149
- window.Element = window.HTMLElement = new Function("return function Element() {}")();
150
- var vbody = document.appendChild(document.createElement("body"));
151
- var frame = vbody.appendChild(document.createElement("iframe"));
152
- var frameDocument = frame.contentWindow.document;
153
- var prototype = Element.prototype = frameDocument.appendChild(frameDocument.createElement("*"));
154
- var cache = {};
155
- var shiv = function(element, deep) {
156
- var childNodes = element.childNodes || [], index = -1, key, value, childNode;
157
- if (element.nodeType === 1 && element.constructor !== Element) {
158
- element.constructor = Element;
159
- for (key in cache) {
160
- value = cache[key];
161
- element[key] = value;
162
- }
163
- }
164
- while (childNode = deep && childNodes[++index]) {
165
- shiv(childNode, deep);
166
- }
167
- return element;
168
- };
169
- var elements = document.getElementsByTagName("*");
170
- var nativeCreateElement = document.createElement;
171
- var interval;
172
- var loopLimit = 100;
173
- prototype.attachEvent("onpropertychange", (function(event) {
174
- var propertyName = event.propertyName, nonValue = !cache.hasOwnProperty(propertyName), newValue = prototype[propertyName], oldValue = cache[propertyName], index = -1, element;
175
- while (element = elements[++index]) {
176
- if (element.nodeType === 1) {
177
- if (nonValue || element[propertyName] === oldValue) {
178
- element[propertyName] = newValue;
179
- }
180
- }
181
- }
182
- cache[propertyName] = newValue;
183
- }));
184
- prototype.constructor = Element;
185
- if (!prototype.hasAttribute) {
186
- prototype.hasAttribute = function hasAttribute(name) {
187
- return this.getAttribute(name) !== null;
188
- };
189
- }
190
- function bodyCheck() {
191
- if (!loopLimit--) clearTimeout(interval);
192
- if (document.body && !document.body.prototype && /(complete|interactive)/.test(document.readyState)) {
193
- shiv(document, true);
194
- if (interval && document.body.prototype) clearTimeout(interval);
195
- return !!document.body.prototype;
44
+ return url.split('#').pop();
45
+ }
46
+ function isSupported($scope = document.body) {
47
+ if (!$scope) {
48
+ return false;
49
+ }
50
+ return $scope.classList.contains('govuk-frontend-supported');
51
+ }
52
+ function validateConfig(schema, config) {
53
+ const validationErrors = [];
54
+ for (const [name, conditions] of Object.entries(schema)) {
55
+ const errors = [];
56
+ for (const {
57
+ required,
58
+ errorMessage
59
+ } of conditions) {
60
+ if (!required.every(key => !!config[key])) {
61
+ errors.push(errorMessage);
196
62
  }
197
- return false;
198
- }
199
- if (!bodyCheck()) {
200
- document.onreadystatechange = bodyCheck;
201
- interval = setInterval(bodyCheck, 25);
202
- }
203
- document.createElement = function createElement(nodeName) {
204
- var element = nativeCreateElement(String(nodeName).toLowerCase());
205
- return shiv(element);
206
- };
207
- document.removeChild(vbody);
208
- })();
209
- }).call("object" === typeof window && window || "object" === typeof self && self || "object" === typeof global && global || {});
210
-
211
- (function(undefined$1) {
212
- var detect = function() {
213
- if (!document.documentElement.dataset) {
214
- return false;
215
63
  }
216
- var el = document.createElement("div");
217
- el.setAttribute("data-a-b", "c");
218
- return el.dataset && el.dataset.aB == "c";
219
- }();
220
- if (detect) return;
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
- }
253
- }
254
- }
255
- return map;
64
+ if (name === 'anyOf' && !(conditions.length - errors.length >= 1)) {
65
+ validationErrors.push(...errors);
256
66
  }
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 || {});
67
+ }
68
+ return validationErrors;
69
+ }
267
70
 
268
71
  function normaliseString(value) {
269
- if (typeof value !== "string") {
72
+ if (typeof value !== 'string') {
270
73
  return value;
271
74
  }
272
- var trimmedValue = value.trim();
273
- if (trimmedValue === "true") {
75
+ const trimmedValue = value.trim();
76
+ if (trimmedValue === 'true') {
274
77
  return true;
275
78
  }
276
- if (trimmedValue === "false") {
79
+ if (trimmedValue === 'false') {
277
80
  return false;
278
81
  }
279
82
  if (trimmedValue.length > 0 && isFinite(Number(trimmedValue))) {
@@ -281,1280 +84,1963 @@ function normaliseString(value) {
281
84
  }
282
85
  return value;
283
86
  }
284
-
285
87
  function normaliseDataset(dataset) {
286
- var out = {};
287
- for (var key in dataset) {
288
- out[key] = normaliseString(dataset[key]);
88
+ const out = {};
89
+ for (const [key, value] of Object.entries(dataset)) {
90
+ out[key] = normaliseString(value);
289
91
  }
290
92
  return out;
291
93
  }
292
94
 
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;
302
- }
303
- })(this);
95
+ class GOVUKFrontendError extends Error {
96
+ constructor(...args) {
97
+ super(...args);
98
+ this.name = 'GOVUKFrontendError';
304
99
  }
305
- }).call("object" === typeof window && window || "object" === typeof self && self || "object" === typeof global && global || {});
306
-
307
- (function(undefined$1) {
308
- var detect = function(global) {
309
- if (!("Event" in global)) return false;
310
- if (typeof global.Event === "function") return true;
311
- try {
312
- new Event("click");
313
- return true;
314
- } catch (e) {
315
- return false;
316
- }
317
- }(this);
318
- if (detect) return;
319
- (function() {
320
- var unlistenableWindowEvents = {
321
- click: 1,
322
- dblclick: 1,
323
- keyup: 1,
324
- keypress: 1,
325
- keydown: 1,
326
- mousedown: 1,
327
- mouseup: 1,
328
- mousemove: 1,
329
- mouseover: 1,
330
- mouseenter: 1,
331
- mouseleave: 1,
332
- mouseout: 1,
333
- storage: 1,
334
- storagecommit: 1,
335
- textinput: 1
336
- };
337
- if (typeof document === "undefined" || typeof window === "undefined") return;
338
- function indexOf(array, element) {
339
- var index = -1, length = array.length;
340
- while (++index < length) {
341
- if (index in array && array[index] === element) {
342
- return index;
343
- }
344
- }
345
- return -1;
346
- }
347
- var existingProto = window.Event && window.Event.prototype || null;
348
- window.Event = Window.prototype.Event = function Event(type, eventInitDict) {
349
- if (!type) {
350
- throw new Error("Not enough arguments");
351
- }
352
- var event;
353
- if ("createEvent" in document) {
354
- event = document.createEvent("Event");
355
- var bubbles = eventInitDict && eventInitDict.bubbles !== undefined$1 ? eventInitDict.bubbles : false;
356
- var cancelable = eventInitDict && eventInitDict.cancelable !== undefined$1 ? eventInitDict.cancelable : false;
357
- event.initEvent(type, bubbles, cancelable);
358
- return event;
359
- }
360
- event = document.createEventObject();
361
- event.type = type;
362
- event.bubbles = eventInitDict && eventInitDict.bubbles !== undefined$1 ? eventInitDict.bubbles : false;
363
- event.cancelable = eventInitDict && eventInitDict.cancelable !== undefined$1 ? eventInitDict.cancelable : false;
364
- return event;
365
- };
366
- if (existingProto) {
367
- Object.defineProperty(window.Event, "prototype", {
368
- configurable: false,
369
- enumerable: false,
370
- writable: true,
371
- value: existingProto
372
- });
373
- }
374
- if (!("createEvent" in document)) {
375
- window.addEventListener = Window.prototype.addEventListener = Document.prototype.addEventListener = Element.prototype.addEventListener = function addEventListener() {
376
- var element = this, type = arguments[0], listener = arguments[1];
377
- if (element === window && type in unlistenableWindowEvents) {
378
- 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.");
379
- }
380
- if (!element._events) {
381
- element._events = {};
382
- }
383
- if (!element._events[type]) {
384
- element._events[type] = function(event) {
385
- var list = element._events[event.type].list, events = list.slice(), index = -1, length = events.length, eventElement;
386
- event.preventDefault = function preventDefault() {
387
- if (event.cancelable !== false) {
388
- event.returnValue = false;
389
- }
390
- };
391
- event.stopPropagation = function stopPropagation() {
392
- event.cancelBubble = true;
393
- };
394
- event.stopImmediatePropagation = function stopImmediatePropagation() {
395
- event.cancelBubble = true;
396
- event.cancelImmediate = true;
397
- };
398
- event.currentTarget = element;
399
- event.relatedTarget = event.fromElement || null;
400
- event.target = event.target || event.srcElement || element;
401
- event.timeStamp = (new Date).getTime();
402
- if (event.clientX) {
403
- event.pageX = event.clientX + document.documentElement.scrollLeft;
404
- event.pageY = event.clientY + document.documentElement.scrollTop;
405
- }
406
- while (++index < length && !event.cancelImmediate) {
407
- if (index in events) {
408
- eventElement = events[index];
409
- if (indexOf(list, eventElement) !== -1 && typeof eventElement === "function") {
410
- eventElement.call(element, event);
411
- }
412
- }
413
- }
414
- };
415
- element._events[type].list = [];
416
- if (element.attachEvent) {
417
- element.attachEvent("on" + type, element._events[type]);
418
- }
419
- }
420
- element._events[type].list.push(listener);
421
- };
422
- window.removeEventListener = Window.prototype.removeEventListener = Document.prototype.removeEventListener = Element.prototype.removeEventListener = function removeEventListener() {
423
- var element = this, type = arguments[0], listener = arguments[1], index;
424
- if (element._events && element._events[type] && element._events[type].list) {
425
- index = indexOf(element._events[type].list, listener);
426
- if (index !== -1) {
427
- element._events[type].list.splice(index, 1);
428
- if (!element._events[type].list.length) {
429
- if (element.detachEvent) {
430
- element.detachEvent("on" + type, element._events[type]);
431
- }
432
- delete element._events[type];
433
- }
434
- }
435
- }
436
- };
437
- window.dispatchEvent = Window.prototype.dispatchEvent = Document.prototype.dispatchEvent = Element.prototype.dispatchEvent = function dispatchEvent(event) {
438
- if (!arguments.length) {
439
- throw new Error("Not enough arguments");
440
- }
441
- if (!event || typeof event.type !== "string") {
442
- throw new Error("DOM Events Exception 0");
443
- }
444
- var element = this, type = event.type;
445
- try {
446
- if (!event.bubbles) {
447
- event.cancelBubble = true;
448
- var cancelBubbleEvent = function(event) {
449
- event.cancelBubble = true;
450
- (element || window).detachEvent("on" + type, cancelBubbleEvent);
451
- };
452
- this.attachEvent("on" + type, cancelBubbleEvent);
453
- }
454
- this.fireEvent("on" + type, event);
455
- } catch (error) {
456
- event.target = element;
457
- do {
458
- event.currentTarget = element;
459
- if ("_events" in element && typeof element._events[type] === "function") {
460
- element._events[type].call(element, event);
461
- }
462
- if (typeof element["on" + type] === "function") {
463
- element["on" + type].call(element, event);
464
- }
465
- element = element.nodeType === 9 ? element.parentWindow : element.parentNode;
466
- } while (element && !event.cancelBubble);
467
- }
468
- return true;
469
- };
470
- document.attachEvent("onreadystatechange", (function() {
471
- if (document.readyState === "complete") {
472
- document.dispatchEvent(new Event("DOMContentLoaded", {
473
- bubbles: true
474
- }));
475
- }
476
- }));
477
- }
478
- })();
479
- }).call("object" === typeof window && window || "object" === typeof self && self || "object" === typeof global && global || {});
480
-
481
- (function(undefined$1) {
482
- var detect = "bind" in Function.prototype;
483
- if (detect) return;
484
- Object.defineProperty(Function.prototype, "bind", {
485
- value: function bind(that) {
486
- var $Array = Array;
487
- var $Object = Object;
488
- var ObjectPrototype = $Object.prototype;
489
- var ArrayPrototype = $Array.prototype;
490
- var Empty = function Empty() {};
491
- var to_string = ObjectPrototype.toString;
492
- var hasToStringTag = typeof Symbol === "function" && typeof Symbol.toStringTag === "symbol";
493
- var isCallable;
494
- var fnToStr = Function.prototype.toString, tryFunctionObject = function tryFunctionObject(value) {
495
- try {
496
- fnToStr.call(value);
497
- return true;
498
- } catch (e) {
499
- return false;
500
- }
501
- }, fnClass = "[object Function]", genClass = "[object GeneratorFunction]";
502
- isCallable = function isCallable(value) {
503
- if (typeof value !== "function") {
504
- return false;
505
- }
506
- if (hasToStringTag) {
507
- return tryFunctionObject(value);
508
- }
509
- var strClass = to_string.call(value);
510
- return strClass === fnClass || strClass === genClass;
511
- };
512
- var array_slice = ArrayPrototype.slice;
513
- var array_concat = ArrayPrototype.concat;
514
- var array_push = ArrayPrototype.push;
515
- var max = Math.max;
516
- var target = this;
517
- if (!isCallable(target)) {
518
- throw new TypeError("Function.prototype.bind called on incompatible " + target);
519
- }
520
- var args = array_slice.call(arguments, 1);
521
- var bound;
522
- var binder = function() {
523
- if (this instanceof bound) {
524
- var result = target.apply(this, array_concat.call(args, array_slice.call(arguments)));
525
- if ($Object(result) === result) {
526
- return result;
527
- }
528
- return this;
529
- } else {
530
- return target.apply(that, array_concat.call(args, array_slice.call(arguments)));
531
- }
532
- };
533
- var boundLength = max(0, target.length - args.length);
534
- var boundArgs = [];
535
- for (var i = 0; i < boundLength; i++) {
536
- array_push.call(boundArgs, "$" + i);
537
- }
538
- bound = Function("binder", "return function (" + boundArgs.join(",") + "){ return binder.apply(this, arguments); }")(binder);
539
- if (target.prototype) {
540
- Empty.prototype = target.prototype;
541
- bound.prototype = new Empty;
542
- Empty.prototype = null;
543
- }
544
- return bound;
545
- }
546
- });
547
- }).call("object" === typeof window && window || "object" === typeof self && self || "object" === typeof global && global || {});
548
-
549
- var KEY_SPACE = 32;
550
-
551
- var DEBOUNCE_TIMEOUT_IN_SECONDS = 1;
552
-
553
- function Button($module, config) {
554
- if (!($module instanceof HTMLElement)) {
555
- return this;
556
- }
557
- this.$module = $module;
558
- this.debounceFormSubmitTimer = null;
559
- var defaultConfig = {
560
- preventDoubleClick: false
561
- };
562
- this.config = mergeConfigs(defaultConfig, config || {}, normaliseDataset($module.dataset));
563
100
  }
564
-
565
- Button.prototype.init = function() {
566
- if (!this.$module) {
567
- return;
101
+ class SupportError extends GOVUKFrontendError {
102
+ /**
103
+ * Checks if GOV.UK Frontend is supported on this page
104
+ *
105
+ * @param {HTMLElement | null} [$scope] - HTML element `<body>` checked for browser support
106
+ */
107
+ constructor($scope = document.body) {
108
+ const supportMessage = 'noModule' in HTMLScriptElement.prototype ? 'GOV.UK Frontend initialised without `<body class="govuk-frontend-supported">` from template `<script>` snippet' : 'GOV.UK Frontend is not supported in this browser';
109
+ super($scope ? supportMessage : 'GOV.UK Frontend initialised without `<script type="module">`');
110
+ this.name = 'SupportError';
568
111
  }
569
- this.$module.addEventListener("keydown", this.handleKeyDown);
570
- this.$module.addEventListener("click", this.debounce.bind(this));
571
- };
572
-
573
- Button.prototype.handleKeyDown = function(event) {
574
- var $target = event.target;
575
- if (event.keyCode !== KEY_SPACE) {
576
- return;
112
+ }
113
+ class ConfigError extends GOVUKFrontendError {
114
+ constructor(...args) {
115
+ super(...args);
116
+ this.name = 'ConfigError';
577
117
  }
578
- if ($target instanceof HTMLElement && $target.getAttribute("role") === "button") {
579
- event.preventDefault();
580
- $target.click();
118
+ }
119
+ class ElementError extends GOVUKFrontendError {
120
+ constructor(messageOrOptions) {
121
+ let message = typeof messageOrOptions === 'string' ? messageOrOptions : '';
122
+ if (typeof messageOrOptions === 'object') {
123
+ const {
124
+ componentName,
125
+ identifier,
126
+ element,
127
+ expectedType
128
+ } = messageOrOptions;
129
+ message = `${componentName}: ${identifier}`;
130
+ message += element ? ` is not of type ${expectedType != null ? expectedType : 'HTMLElement'}` : ' not found';
131
+ }
132
+ super(message);
133
+ this.name = 'ElementError';
581
134
  }
582
- };
135
+ }
583
136
 
584
- Button.prototype.debounce = function(event) {
585
- if (!this.config.preventDoubleClick) {
586
- return;
137
+ class GOVUKFrontendComponent {
138
+ constructor() {
139
+ this.checkSupport();
587
140
  }
588
- if (this.debounceFormSubmitTimer) {
589
- event.preventDefault();
590
- return false;
141
+ checkSupport() {
142
+ if (!isSupported()) {
143
+ throw new SupportError();
144
+ }
591
145
  }
592
- this.debounceFormSubmitTimer = setTimeout(function() {
593
- this.debounceFormSubmitTimer = null;
594
- }.bind(this), DEBOUNCE_TIMEOUT_IN_SECONDS * 1e3);
595
- };
596
-
597
- (function(undefined$1) {
598
- var detect = "document" in this && "matches" in document.documentElement;
599
- if (detect) return;
600
- Element.prototype.matches = Element.prototype.webkitMatchesSelector || Element.prototype.oMatchesSelector || Element.prototype.msMatchesSelector || Element.prototype.mozMatchesSelector || function matches(selector) {
601
- var element = this;
602
- var elements = (element.document || element.ownerDocument).querySelectorAll(selector);
603
- var index = 0;
604
- while (elements[index] && elements[index] !== element) {
605
- ++index;
606
- }
607
- return !!elements[index];
608
- };
609
- }).call("object" === typeof window && window || "object" === typeof self && self || "object" === typeof global && global || {});
610
-
611
- (function(undefined$1) {
612
- var detect = "document" in this && "closest" in document.documentElement;
613
- if (detect) return;
614
- Element.prototype.closest = function closest(selector) {
615
- var node = this;
616
- while (node) {
617
- if (node.matches(selector)) return node; else node = "SVGElement" in window && node instanceof SVGElement ? node.parentNode : node.parentElement;
618
- }
619
- return null;
620
- };
621
- }).call("object" === typeof window && window || "object" === typeof self && self || "object" === typeof global && global || {});
622
-
623
- function closestAttributeValue($element, attributeName) {
624
- var $closestElementWithAttribute = $element.closest("[" + attributeName + "]");
625
- return $closestElementWithAttribute ? $closestElementWithAttribute.getAttribute(attributeName) : null;
626
146
  }
627
147
 
628
- function I18n(translations, config) {
629
- this.translations = translations || {};
630
- this.locale = config && config.locale || document.documentElement.lang || "en";
631
- }
632
-
633
- I18n.prototype.t = function(lookupKey, options) {
634
- if (!lookupKey) {
635
- throw new Error("i18n: lookup key missing");
636
- }
637
- if (options && typeof options.count === "number") {
638
- lookupKey = lookupKey + "." + this.getPluralSuffix(lookupKey, options.count);
639
- }
640
- var translationString = this.translations[lookupKey];
641
- if (typeof translationString === "string") {
642
- if (translationString.match(/%{(.\S+)}/)) {
643
- if (!options) {
644
- throw new Error("i18n: cannot replace placeholders in string if no option data provided");
148
+ class I18n {
149
+ constructor(translations = {}, config = {}) {
150
+ var _config$locale;
151
+ this.translations = void 0;
152
+ this.locale = void 0;
153
+ this.translations = translations;
154
+ this.locale = (_config$locale = config.locale) != null ? _config$locale : document.documentElement.lang || 'en';
155
+ }
156
+ t(lookupKey, options) {
157
+ if (!lookupKey) {
158
+ throw new Error('i18n: lookup key missing');
159
+ }
160
+ if (typeof (options == null ? void 0 : options.count) === 'number') {
161
+ lookupKey = `${lookupKey}.${this.getPluralSuffix(lookupKey, options.count)}`;
162
+ }
163
+ const translationString = this.translations[lookupKey];
164
+ if (typeof translationString === 'string') {
165
+ if (translationString.match(/%{(.\S+)}/)) {
166
+ if (!options) {
167
+ throw new Error('i18n: cannot replace placeholders in string if no option data provided');
168
+ }
169
+ return this.replacePlaceholders(translationString, options);
645
170
  }
646
- return this.replacePlaceholders(translationString, options);
647
- } else {
648
171
  return translationString;
649
172
  }
650
- } else {
651
173
  return lookupKey;
652
174
  }
653
- };
654
-
655
- I18n.prototype.replacePlaceholders = function(translationString, options) {
656
- var formatter;
657
- if (this.hasIntlNumberFormatSupport()) {
658
- formatter = new Intl.NumberFormat(this.locale);
659
- }
660
- return translationString.replace(/%{(.\S+)}/g, (function(placeholderWithBraces, placeholderKey) {
661
- if (Object.prototype.hasOwnProperty.call(options, placeholderKey)) {
662
- var placeholderValue = options[placeholderKey];
663
- if (placeholderValue === false || typeof placeholderValue !== "number" && typeof placeholderValue !== "string") {
664
- return "";
665
- }
666
- if (typeof placeholderValue === "number") {
667
- return formatter ? formatter.format(placeholderValue) : placeholderValue.toString();
175
+ replacePlaceholders(translationString, options) {
176
+ const formatter = Intl.NumberFormat.supportedLocalesOf(this.locale).length ? new Intl.NumberFormat(this.locale) : undefined;
177
+ return translationString.replace(/%{(.\S+)}/g, function (placeholderWithBraces, placeholderKey) {
178
+ if (Object.prototype.hasOwnProperty.call(options, placeholderKey)) {
179
+ const placeholderValue = options[placeholderKey];
180
+ if (placeholderValue === false || typeof placeholderValue !== 'number' && typeof placeholderValue !== 'string') {
181
+ return '';
182
+ }
183
+ if (typeof placeholderValue === 'number') {
184
+ return formatter ? formatter.format(placeholderValue) : `${placeholderValue}`;
185
+ }
186
+ return placeholderValue;
668
187
  }
669
- return placeholderValue;
670
- } else {
671
- throw new Error("i18n: no data found to replace " + placeholderWithBraces + " placeholder in string");
672
- }
673
- }));
674
- };
675
-
676
- I18n.prototype.hasIntlPluralRulesSupport = function() {
677
- return Boolean(window.Intl && ("PluralRules" in window.Intl && Intl.PluralRules.supportedLocalesOf(this.locale).length));
678
- };
679
-
680
- I18n.prototype.hasIntlNumberFormatSupport = function() {
681
- return Boolean(window.Intl && ("NumberFormat" in window.Intl && Intl.NumberFormat.supportedLocalesOf(this.locale).length));
682
- };
683
-
684
- I18n.prototype.getPluralSuffix = function(lookupKey, count) {
685
- count = Number(count);
686
- if (!isFinite(count)) {
687
- return "other";
688
- }
689
- var preferredForm;
690
- if (this.hasIntlPluralRulesSupport()) {
691
- preferredForm = new Intl.PluralRules(this.locale).select(count);
692
- } else {
693
- preferredForm = this.selectPluralFormUsingFallbackRules(count);
694
- }
695
- if (lookupKey + "." + preferredForm in this.translations) {
696
- return preferredForm;
697
- } else if (lookupKey + ".other" in this.translations) {
698
- if (console && "warn" in console) {
699
- console.warn('i18n: Missing plural form ".' + preferredForm + '" for "' + this.locale + '" locale. Falling back to ".other".');
700
- }
701
- return "other";
702
- } else {
703
- throw new Error('i18n: Plural form ".other" is required for "' + this.locale + '" locale');
188
+ throw new Error(`i18n: no data found to replace ${placeholderWithBraces} placeholder in string`);
189
+ });
704
190
  }
705
- };
706
-
707
- I18n.prototype.selectPluralFormUsingFallbackRules = function(count) {
708
- count = Math.abs(Math.floor(count));
709
- var ruleset = this.getPluralRulesForLocale();
710
- if (ruleset) {
711
- return I18n.pluralRules[ruleset](count);
191
+ hasIntlPluralRulesSupport() {
192
+ return Boolean('PluralRules' in window.Intl && Intl.PluralRules.supportedLocalesOf(this.locale).length);
712
193
  }
713
- return "other";
714
- };
715
-
716
- I18n.prototype.getPluralRulesForLocale = function() {
717
- var locale = this.locale;
718
- var localeShort = locale.split("-")[0];
719
- for (var pluralRule in I18n.pluralRulesMap) {
720
- if (Object.prototype.hasOwnProperty.call(I18n.pluralRulesMap, pluralRule)) {
721
- var languages = I18n.pluralRulesMap[pluralRule];
722
- for (var i = 0; i < languages.length; i++) {
723
- if (languages[i] === locale || languages[i] === localeShort) {
724
- return pluralRule;
725
- }
194
+ getPluralSuffix(lookupKey, count) {
195
+ count = Number(count);
196
+ if (!isFinite(count)) {
197
+ return 'other';
198
+ }
199
+ const preferredForm = this.hasIntlPluralRulesSupport() ? new Intl.PluralRules(this.locale).select(count) : this.selectPluralFormUsingFallbackRules(count);
200
+ if (`${lookupKey}.${preferredForm}` in this.translations) {
201
+ return preferredForm;
202
+ } else if (`${lookupKey}.other` in this.translations) {
203
+ console.warn(`i18n: Missing plural form ".${preferredForm}" for "${this.locale}" locale. Falling back to ".other".`);
204
+ return 'other';
205
+ }
206
+ throw new Error(`i18n: Plural form ".other" is required for "${this.locale}" locale`);
207
+ }
208
+ selectPluralFormUsingFallbackRules(count) {
209
+ count = Math.abs(Math.floor(count));
210
+ const ruleset = this.getPluralRulesForLocale();
211
+ if (ruleset) {
212
+ return I18n.pluralRules[ruleset](count);
213
+ }
214
+ return 'other';
215
+ }
216
+ getPluralRulesForLocale() {
217
+ const localeShort = this.locale.split('-')[0];
218
+ for (const pluralRule in I18n.pluralRulesMap) {
219
+ const languages = I18n.pluralRulesMap[pluralRule];
220
+ if (languages.includes(this.locale) || languages.includes(localeShort)) {
221
+ return pluralRule;
726
222
  }
727
223
  }
728
224
  }
729
- };
730
-
225
+ }
731
226
  I18n.pluralRulesMap = {
732
- arabic: [ "ar" ],
733
- chinese: [ "my", "zh", "id", "ja", "jv", "ko", "ms", "th", "vi" ],
734
- french: [ "hy", "bn", "fr", "gu", "hi", "fa", "pa", "zu" ],
735
- 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" ],
736
- irish: [ "ga" ],
737
- russian: [ "ru", "uk" ],
738
- scottish: [ "gd" ],
739
- spanish: [ "pt-PT", "it", "es" ],
740
- welsh: [ "cy" ]
227
+ arabic: ['ar'],
228
+ chinese: ['my', 'zh', 'id', 'ja', 'jv', 'ko', 'ms', 'th', 'vi'],
229
+ french: ['hy', 'bn', 'fr', 'gu', 'hi', 'fa', 'pa', 'zu'],
230
+ 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'],
231
+ irish: ['ga'],
232
+ russian: ['ru', 'uk'],
233
+ scottish: ['gd'],
234
+ spanish: ['pt-PT', 'it', 'es'],
235
+ welsh: ['cy']
741
236
  };
742
-
743
237
  I18n.pluralRules = {
744
- arabic: function(n) {
238
+ arabic(n) {
745
239
  if (n === 0) {
746
- return "zero";
240
+ return 'zero';
747
241
  }
748
242
  if (n === 1) {
749
- return "one";
243
+ return 'one';
750
244
  }
751
245
  if (n === 2) {
752
- return "two";
246
+ return 'two';
753
247
  }
754
248
  if (n % 100 >= 3 && n % 100 <= 10) {
755
- return "few";
249
+ return 'few';
756
250
  }
757
251
  if (n % 100 >= 11 && n % 100 <= 99) {
758
- return "many";
252
+ return 'many';
759
253
  }
760
- return "other";
254
+ return 'other';
761
255
  },
762
- chinese: function() {
763
- return "other";
256
+ chinese() {
257
+ return 'other';
764
258
  },
765
- french: function(n) {
766
- return n === 0 || n === 1 ? "one" : "other";
259
+ french(n) {
260
+ return n === 0 || n === 1 ? 'one' : 'other';
767
261
  },
768
- german: function(n) {
769
- return n === 1 ? "one" : "other";
262
+ german(n) {
263
+ return n === 1 ? 'one' : 'other';
770
264
  },
771
- irish: function(n) {
265
+ irish(n) {
772
266
  if (n === 1) {
773
- return "one";
267
+ return 'one';
774
268
  }
775
269
  if (n === 2) {
776
- return "two";
270
+ return 'two';
777
271
  }
778
272
  if (n >= 3 && n <= 6) {
779
- return "few";
273
+ return 'few';
780
274
  }
781
275
  if (n >= 7 && n <= 10) {
782
- return "many";
276
+ return 'many';
783
277
  }
784
- return "other";
278
+ return 'other';
785
279
  },
786
- russian: function(n) {
787
- var lastTwo = n % 100;
788
- var last = lastTwo % 10;
280
+ russian(n) {
281
+ const lastTwo = n % 100;
282
+ const last = lastTwo % 10;
789
283
  if (last === 1 && lastTwo !== 11) {
790
- return "one";
284
+ return 'one';
791
285
  }
792
286
  if (last >= 2 && last <= 4 && !(lastTwo >= 12 && lastTwo <= 14)) {
793
- return "few";
287
+ return 'few';
794
288
  }
795
289
  if (last === 0 || last >= 5 && last <= 9 || lastTwo >= 11 && lastTwo <= 14) {
796
- return "many";
290
+ return 'many';
797
291
  }
798
- return "other";
292
+ return 'other';
799
293
  },
800
- scottish: function(n) {
294
+ scottish(n) {
801
295
  if (n === 1 || n === 11) {
802
- return "one";
296
+ return 'one';
803
297
  }
804
298
  if (n === 2 || n === 12) {
805
- return "two";
299
+ return 'two';
806
300
  }
807
301
  if (n >= 3 && n <= 10 || n >= 13 && n <= 19) {
808
- return "few";
302
+ return 'few';
809
303
  }
810
- return "other";
304
+ return 'other';
811
305
  },
812
- spanish: function(n) {
306
+ spanish(n) {
813
307
  if (n === 1) {
814
- return "one";
308
+ return 'one';
815
309
  }
816
- if (n % 1e6 === 0 && n !== 0) {
817
- return "many";
310
+ if (n % 1000000 === 0 && n !== 0) {
311
+ return 'many';
818
312
  }
819
- return "other";
313
+ return 'other';
820
314
  },
821
- welsh: function(n) {
315
+ welsh(n) {
822
316
  if (n === 0) {
823
- return "zero";
317
+ return 'zero';
824
318
  }
825
319
  if (n === 1) {
826
- return "one";
320
+ return 'one';
827
321
  }
828
322
  if (n === 2) {
829
- return "two";
323
+ return 'two';
830
324
  }
831
325
  if (n === 3) {
832
- return "few";
326
+ return 'few';
833
327
  }
834
328
  if (n === 6) {
835
- return "many";
329
+ return 'many';
836
330
  }
837
- return "other";
331
+ return 'other';
838
332
  }
839
333
  };
840
334
 
841
- (function(undefined$1) {
842
- var detect = "Date" in self && "now" in self.Date && "getTime" in self.Date.prototype;
843
- if (detect) return;
844
- Date.now = function() {
845
- return (new Date).getTime();
846
- };
847
- }).call("object" === typeof window && window || "object" === typeof self && self || "object" === typeof global && global || {});
848
-
849
- (function(undefined$1) {
850
- var detect = "DOMTokenList" in this && function(x) {
851
- return "classList" in x ? !x.classList.toggle("x", false) && !x.className : true;
852
- }(document.createElement("x"));
853
- if (detect) return;
854
- (function(global) {
855
- var nativeImpl = "DOMTokenList" in global && global.DOMTokenList;
856
- if (!nativeImpl || !!document.createElementNS && !!document.createElementNS("http://www.w3.org/2000/svg", "svg") && !(document.createElementNS("http://www.w3.org/2000/svg", "svg").classList instanceof DOMTokenList)) {
857
- global.DOMTokenList = function() {
858
- var dpSupport = true;
859
- var defineGetter = function(object, name, fn, configurable) {
860
- if (Object.defineProperty) Object.defineProperty(object, name, {
861
- configurable: false === dpSupport ? true : !!configurable,
862
- get: fn
863
- }); else object.__defineGetter__(name, fn);
864
- };
865
- try {
866
- defineGetter({}, "support");
867
- } catch (e) {
868
- dpSupport = false;
869
- }
870
- var _DOMTokenList = function(el, prop) {
871
- var that = this;
872
- var tokens = [];
873
- var tokenMap = {};
874
- var length = 0;
875
- var maxLength = 0;
876
- var addIndexGetter = function(i) {
877
- defineGetter(that, i, (function() {
878
- preop();
879
- return tokens[i];
880
- }), false);
881
- };
882
- var reindex = function() {
883
- if (length >= maxLength) for (;maxLength < length; ++maxLength) {
884
- addIndexGetter(maxLength);
885
- }
886
- };
887
- var preop = function() {
888
- var error;
889
- var i;
890
- var args = arguments;
891
- var rSpace = /\s+/;
892
- if (args.length) for (i = 0; i < args.length; ++i) if (rSpace.test(args[i])) {
893
- error = new SyntaxError('String "' + args[i] + '" ' + "contains" + " an invalid character");
894
- error.code = 5;
895
- error.name = "InvalidCharacterError";
896
- throw error;
897
- }
898
- if (typeof el[prop] === "object") {
899
- tokens = ("" + el[prop].baseVal).replace(/^\s+|\s+$/g, "").split(rSpace);
900
- } else {
901
- tokens = ("" + el[prop]).replace(/^\s+|\s+$/g, "").split(rSpace);
902
- }
903
- if ("" === tokens[0]) tokens = [];
904
- tokenMap = {};
905
- for (i = 0; i < tokens.length; ++i) tokenMap[tokens[i]] = true;
906
- length = tokens.length;
907
- reindex();
908
- };
909
- preop();
910
- defineGetter(that, "length", (function() {
911
- preop();
912
- return length;
913
- }));
914
- that.toLocaleString = that.toString = function() {
915
- preop();
916
- return tokens.join(" ");
917
- };
918
- that.item = function(idx) {
919
- preop();
920
- return tokens[idx];
921
- };
922
- that.contains = function(token) {
923
- preop();
924
- return !!tokenMap[token];
925
- };
926
- that.add = function() {
927
- preop.apply(that, args = arguments);
928
- for (var args, token, i = 0, l = args.length; i < l; ++i) {
929
- token = args[i];
930
- if (!tokenMap[token]) {
931
- tokens.push(token);
932
- tokenMap[token] = true;
933
- }
934
- }
935
- if (length !== tokens.length) {
936
- length = tokens.length >>> 0;
937
- if (typeof el[prop] === "object") {
938
- el[prop].baseVal = tokens.join(" ");
939
- } else {
940
- el[prop] = tokens.join(" ");
941
- }
942
- reindex();
943
- }
944
- };
945
- that.remove = function() {
946
- preop.apply(that, args = arguments);
947
- for (var args, ignore = {}, i = 0, t = []; i < args.length; ++i) {
948
- ignore[args[i]] = true;
949
- delete tokenMap[args[i]];
950
- }
951
- for (i = 0; i < tokens.length; ++i) if (!ignore[tokens[i]]) t.push(tokens[i]);
952
- tokens = t;
953
- length = t.length >>> 0;
954
- if (typeof el[prop] === "object") {
955
- el[prop].baseVal = tokens.join(" ");
956
- } else {
957
- el[prop] = tokens.join(" ");
958
- }
959
- reindex();
960
- };
961
- that.toggle = function(token, force) {
962
- preop.apply(that, [ token ]);
963
- if (undefined$1 !== force) {
964
- if (force) {
965
- that.add(token);
966
- return true;
967
- } else {
968
- that.remove(token);
969
- return false;
970
- }
971
- }
972
- if (tokenMap[token]) {
973
- that.remove(token);
974
- return false;
975
- }
976
- that.add(token);
977
- return true;
978
- };
979
- return that;
980
- };
981
- return _DOMTokenList;
982
- }();
983
- }
984
- (function() {
985
- var e = document.createElement("span");
986
- if (!("classList" in e)) return;
987
- e.classList.toggle("x", false);
988
- if (!e.classList.contains("x")) return;
989
- e.classList.constructor.prototype.toggle = function toggle(token) {
990
- var force = arguments[1];
991
- if (force === undefined$1) {
992
- var add = !this.contains(token);
993
- this[add ? "add" : "remove"](token);
994
- return add;
995
- }
996
- force = !!force;
997
- this[force ? "add" : "remove"](token);
998
- return force;
999
- };
1000
- })();
1001
- (function() {
1002
- var e = document.createElement("span");
1003
- if (!("classList" in e)) return;
1004
- e.classList.add("a", "b");
1005
- if (e.classList.contains("b")) return;
1006
- var native = e.classList.constructor.prototype.add;
1007
- e.classList.constructor.prototype.add = function() {
1008
- var args = arguments;
1009
- var l = arguments.length;
1010
- for (var i = 0; i < l; i++) {
1011
- native.call(this, args[i]);
335
+ /**
336
+ * Accordion component
337
+ *
338
+ * This allows a collection of sections to be collapsed by default, showing only
339
+ * their headers. Sections can be expanded or collapsed individually by clicking
340
+ * their headers. A "Show all sections" button is also added to the top of the
341
+ * accordion, which switches to "Hide all sections" when all the sections are
342
+ * expanded.
343
+ *
344
+ * The state of each section is saved to the DOM via the `aria-expanded`
345
+ * attribute, which also provides accessibility.
346
+ *
347
+ * @preserve
348
+ */
349
+ class Accordion extends GOVUKFrontendComponent {
350
+ /**
351
+ * @param {Element | null} $module - HTML element to use for accordion
352
+ * @param {AccordionConfig} [config] - Accordion config
353
+ */
354
+ constructor($module, config = {}) {
355
+ super();
356
+ this.$module = void 0;
357
+ this.config = void 0;
358
+ this.i18n = void 0;
359
+ this.controlsClass = 'govuk-accordion__controls';
360
+ this.showAllClass = 'govuk-accordion__show-all';
361
+ this.showAllTextClass = 'govuk-accordion__show-all-text';
362
+ this.sectionClass = 'govuk-accordion__section';
363
+ this.sectionExpandedClass = 'govuk-accordion__section--expanded';
364
+ this.sectionButtonClass = 'govuk-accordion__section-button';
365
+ this.sectionHeaderClass = 'govuk-accordion__section-header';
366
+ this.sectionHeadingClass = 'govuk-accordion__section-heading';
367
+ this.sectionHeadingDividerClass = 'govuk-accordion__section-heading-divider';
368
+ this.sectionHeadingTextClass = 'govuk-accordion__section-heading-text';
369
+ this.sectionHeadingTextFocusClass = 'govuk-accordion__section-heading-text-focus';
370
+ this.sectionShowHideToggleClass = 'govuk-accordion__section-toggle';
371
+ this.sectionShowHideToggleFocusClass = 'govuk-accordion__section-toggle-focus';
372
+ this.sectionShowHideTextClass = 'govuk-accordion__section-toggle-text';
373
+ this.upChevronIconClass = 'govuk-accordion-nav__chevron';
374
+ this.downChevronIconClass = 'govuk-accordion-nav__chevron--down';
375
+ this.sectionSummaryClass = 'govuk-accordion__section-summary';
376
+ this.sectionSummaryFocusClass = 'govuk-accordion__section-summary-focus';
377
+ this.sectionContentClass = 'govuk-accordion__section-content';
378
+ this.$sections = void 0;
379
+ this.browserSupportsSessionStorage = false;
380
+ this.$showAllButton = null;
381
+ this.$showAllIcon = null;
382
+ this.$showAllText = null;
383
+ if (!($module instanceof HTMLElement)) {
384
+ throw new ElementError({
385
+ componentName: 'Accordion',
386
+ element: $module,
387
+ identifier: 'Root element (`$module`)'
388
+ });
389
+ }
390
+ this.$module = $module;
391
+ this.config = mergeConfigs(Accordion.defaults, config, normaliseDataset($module.dataset));
392
+ this.i18n = new I18n(extractConfigByNamespace(this.config, 'i18n'));
393
+ const $sections = this.$module.querySelectorAll(`.${this.sectionClass}`);
394
+ if (!$sections.length) {
395
+ throw new ElementError({
396
+ componentName: 'Accordion',
397
+ identifier: `Sections (\`<div class="${this.sectionClass}">\`)`
398
+ });
399
+ }
400
+ this.$sections = $sections;
401
+ this.browserSupportsSessionStorage = helper.checkForSessionStorage();
402
+ this.initControls();
403
+ this.initSectionHeaders();
404
+ const areAllSectionsOpen = this.checkIfAllSectionsOpen();
405
+ this.updateShowAllButton(areAllSectionsOpen);
406
+ }
407
+ initControls() {
408
+ this.$showAllButton = document.createElement('button');
409
+ this.$showAllButton.setAttribute('type', 'button');
410
+ this.$showAllButton.setAttribute('class', this.showAllClass);
411
+ this.$showAllButton.setAttribute('aria-expanded', 'false');
412
+ this.$showAllIcon = document.createElement('span');
413
+ this.$showAllIcon.classList.add(this.upChevronIconClass);
414
+ this.$showAllButton.appendChild(this.$showAllIcon);
415
+ const $accordionControls = document.createElement('div');
416
+ $accordionControls.setAttribute('class', this.controlsClass);
417
+ $accordionControls.appendChild(this.$showAllButton);
418
+ this.$module.insertBefore($accordionControls, this.$module.firstChild);
419
+ this.$showAllText = document.createElement('span');
420
+ this.$showAllText.classList.add(this.showAllTextClass);
421
+ this.$showAllButton.appendChild(this.$showAllText);
422
+ this.$showAllButton.addEventListener('click', () => this.onShowOrHideAllToggle());
423
+ if ('onbeforematch' in document) {
424
+ document.addEventListener('beforematch', event => this.onBeforeMatch(event));
425
+ }
426
+ }
427
+ initSectionHeaders() {
428
+ this.$sections.forEach(($section, i) => {
429
+ const $header = $section.querySelector(`.${this.sectionHeaderClass}`);
430
+ if (!$header) {
431
+ throw new ElementError({
432
+ componentName: 'Accordion',
433
+ identifier: `Section headers (\`<div class="${this.sectionHeaderClass}">\`)`
434
+ });
435
+ }
436
+ this.constructHeaderMarkup($header, i);
437
+ this.setExpanded(this.isExpanded($section), $section);
438
+ $header.addEventListener('click', () => this.onSectionToggle($section));
439
+ this.setInitialState($section);
440
+ });
441
+ }
442
+ constructHeaderMarkup($header, index) {
443
+ const $span = $header.querySelector(`.${this.sectionButtonClass}`);
444
+ const $heading = $header.querySelector(`.${this.sectionHeadingClass}`);
445
+ const $summary = $header.querySelector(`.${this.sectionSummaryClass}`);
446
+ if (!$heading) {
447
+ throw new ElementError({
448
+ componentName: 'Accordion',
449
+ identifier: `Section heading (\`.${this.sectionHeadingClass}\`)`
450
+ });
451
+ }
452
+ if (!$span) {
453
+ throw new ElementError({
454
+ componentName: 'Accordion',
455
+ identifier: `Section button placeholder (\`<span class="${this.sectionButtonClass}">\`)`
456
+ });
457
+ }
458
+ const $button = document.createElement('button');
459
+ $button.setAttribute('type', 'button');
460
+ $button.setAttribute('aria-controls', `${this.$module.id}-content-${index + 1}`);
461
+ for (const attr of Array.from($span.attributes)) {
462
+ if (attr.nodeName !== 'id') {
463
+ $button.setAttribute(attr.nodeName, `${attr.nodeValue}`);
464
+ }
465
+ }
466
+ const $headingText = document.createElement('span');
467
+ $headingText.classList.add(this.sectionHeadingTextClass);
468
+ $headingText.id = $span.id;
469
+ const $headingTextFocus = document.createElement('span');
470
+ $headingTextFocus.classList.add(this.sectionHeadingTextFocusClass);
471
+ $headingText.appendChild($headingTextFocus);
472
+ $headingTextFocus.innerHTML = $span.innerHTML;
473
+ const $showHideToggle = document.createElement('span');
474
+ $showHideToggle.classList.add(this.sectionShowHideToggleClass);
475
+ $showHideToggle.setAttribute('data-nosnippet', '');
476
+ const $showHideToggleFocus = document.createElement('span');
477
+ $showHideToggleFocus.classList.add(this.sectionShowHideToggleFocusClass);
478
+ $showHideToggle.appendChild($showHideToggleFocus);
479
+ const $showHideText = document.createElement('span');
480
+ const $showHideIcon = document.createElement('span');
481
+ $showHideIcon.classList.add(this.upChevronIconClass);
482
+ $showHideToggleFocus.appendChild($showHideIcon);
483
+ $showHideText.classList.add(this.sectionShowHideTextClass);
484
+ $showHideToggleFocus.appendChild($showHideText);
485
+ $button.appendChild($headingText);
486
+ $button.appendChild(this.getButtonPunctuationEl());
487
+ if ($summary != null && $summary.parentNode) {
488
+ const $summarySpan = document.createElement('span');
489
+ const $summarySpanFocus = document.createElement('span');
490
+ $summarySpanFocus.classList.add(this.sectionSummaryFocusClass);
491
+ $summarySpan.appendChild($summarySpanFocus);
492
+ for (const attr of Array.from($summary.attributes)) {
493
+ $summarySpan.setAttribute(attr.nodeName, `${attr.nodeValue}`);
494
+ }
495
+ $summarySpanFocus.innerHTML = $summary.innerHTML;
496
+ $summary.parentNode.replaceChild($summarySpan, $summary);
497
+ $button.appendChild($summarySpan);
498
+ $button.appendChild(this.getButtonPunctuationEl());
499
+ }
500
+ $button.appendChild($showHideToggle);
501
+ $heading.removeChild($span);
502
+ $heading.appendChild($button);
503
+ }
504
+ onBeforeMatch(event) {
505
+ const $fragment = event.target;
506
+ if (!($fragment instanceof Element)) {
507
+ return;
508
+ }
509
+ const $section = $fragment.closest(`.${this.sectionClass}`);
510
+ if ($section) {
511
+ this.setExpanded(true, $section);
512
+ }
513
+ }
514
+ onSectionToggle($section) {
515
+ const expanded = this.isExpanded($section);
516
+ this.setExpanded(!expanded, $section);
517
+ this.storeState($section);
518
+ }
519
+ onShowOrHideAllToggle() {
520
+ const nowExpanded = !this.checkIfAllSectionsOpen();
521
+ this.$sections.forEach($section => {
522
+ this.setExpanded(nowExpanded, $section);
523
+ this.storeState($section);
524
+ });
525
+ this.updateShowAllButton(nowExpanded);
526
+ }
527
+ setExpanded(expanded, $section) {
528
+ const $showHideIcon = $section.querySelector(`.${this.upChevronIconClass}`);
529
+ const $showHideText = $section.querySelector(`.${this.sectionShowHideTextClass}`);
530
+ const $button = $section.querySelector(`.${this.sectionButtonClass}`);
531
+ const $content = $section.querySelector(`.${this.sectionContentClass}`);
532
+ if (!$content) {
533
+ throw new ElementError({
534
+ componentName: 'Accordion',
535
+ identifier: `Section content (\`<div class="${this.sectionContentClass}">\`)`
536
+ });
537
+ }
538
+ if (!$showHideIcon || !$showHideText || !$button) {
539
+ return;
540
+ }
541
+ const newButtonText = expanded ? this.i18n.t('hideSection') : this.i18n.t('showSection');
542
+ $showHideText.textContent = newButtonText;
543
+ $button.setAttribute('aria-expanded', `${expanded}`);
544
+ const ariaLabelParts = [];
545
+ const $headingText = $section.querySelector(`.${this.sectionHeadingTextClass}`);
546
+ if ($headingText) {
547
+ ariaLabelParts.push(`${$headingText.textContent}`.trim());
548
+ }
549
+ const $summary = $section.querySelector(`.${this.sectionSummaryClass}`);
550
+ if ($summary) {
551
+ ariaLabelParts.push(`${$summary.textContent}`.trim());
552
+ }
553
+ const ariaLabelMessage = expanded ? this.i18n.t('hideSectionAriaLabel') : this.i18n.t('showSectionAriaLabel');
554
+ ariaLabelParts.push(ariaLabelMessage);
555
+ $button.setAttribute('aria-label', ariaLabelParts.join(' , '));
556
+ if (expanded) {
557
+ $content.removeAttribute('hidden');
558
+ $section.classList.add(this.sectionExpandedClass);
559
+ $showHideIcon.classList.remove(this.downChevronIconClass);
560
+ } else {
561
+ $content.setAttribute('hidden', 'until-found');
562
+ $section.classList.remove(this.sectionExpandedClass);
563
+ $showHideIcon.classList.add(this.downChevronIconClass);
564
+ }
565
+ const areAllSectionsOpen = this.checkIfAllSectionsOpen();
566
+ this.updateShowAllButton(areAllSectionsOpen);
567
+ }
568
+ isExpanded($section) {
569
+ return $section.classList.contains(this.sectionExpandedClass);
570
+ }
571
+ checkIfAllSectionsOpen() {
572
+ const sectionsCount = this.$sections.length;
573
+ const expandedSectionCount = this.$module.querySelectorAll(`.${this.sectionExpandedClass}`).length;
574
+ const areAllSectionsOpen = sectionsCount === expandedSectionCount;
575
+ return areAllSectionsOpen;
576
+ }
577
+ updateShowAllButton(expanded) {
578
+ if (!this.$showAllButton || !this.$showAllText || !this.$showAllIcon) {
579
+ return;
580
+ }
581
+ this.$showAllButton.setAttribute('aria-expanded', expanded.toString());
582
+ this.$showAllText.textContent = expanded ? this.i18n.t('hideAllSections') : this.i18n.t('showAllSections');
583
+ this.$showAllIcon.classList.toggle(this.downChevronIconClass, !expanded);
584
+ }
585
+ storeState($section) {
586
+ if (this.browserSupportsSessionStorage && this.config.rememberExpanded) {
587
+ const $button = $section.querySelector(`.${this.sectionButtonClass}`);
588
+ if ($button) {
589
+ const contentId = $button.getAttribute('aria-controls');
590
+ const contentState = $button.getAttribute('aria-expanded');
591
+ if (contentId && contentState) {
592
+ window.sessionStorage.setItem(contentId, contentState);
1012
593
  }
1013
- };
1014
- })();
1015
- (function() {
1016
- var e = document.createElement("span");
1017
- if (!("classList" in e)) return;
1018
- e.classList.add("a");
1019
- e.classList.add("b");
1020
- e.classList.remove("a", "b");
1021
- if (!e.classList.contains("b")) return;
1022
- var native = e.classList.constructor.prototype.remove;
1023
- e.classList.constructor.prototype.remove = function() {
1024
- var args = arguments;
1025
- var l = arguments.length;
1026
- for (var i = 0; i < l; i++) {
1027
- native.call(this, args[i]);
594
+ }
595
+ }
596
+ }
597
+ setInitialState($section) {
598
+ if (this.browserSupportsSessionStorage && this.config.rememberExpanded) {
599
+ const $button = $section.querySelector(`.${this.sectionButtonClass}`);
600
+ if ($button) {
601
+ const contentId = $button.getAttribute('aria-controls');
602
+ const contentState = contentId ? window.sessionStorage.getItem(contentId) : null;
603
+ if (contentState !== null) {
604
+ this.setExpanded(contentState === 'true', $section);
1028
605
  }
1029
- };
1030
- })();
1031
- })(this);
1032
- }).call("object" === typeof window && window || "object" === typeof self && self || "object" === typeof global && global || {});
1033
-
1034
- (function(undefined$1) {
1035
- var detect = "document" in this && "classList" in document.documentElement && "Element" in this && "classList" in Element.prototype && function() {
1036
- var e = document.createElement("span");
1037
- e.classList.add("a", "b");
1038
- return e.classList.contains("b");
1039
- }();
1040
- if (detect) return;
1041
- (function(global) {
1042
- var dpSupport = true;
1043
- var defineGetter = function(object, name, fn, configurable) {
1044
- if (Object.defineProperty) Object.defineProperty(object, name, {
1045
- configurable: false === dpSupport ? true : !!configurable,
1046
- get: fn
1047
- }); else object.__defineGetter__(name, fn);
1048
- };
1049
- try {
1050
- defineGetter({}, "support");
1051
- } catch (e) {
1052
- dpSupport = false;
1053
- }
1054
- var addProp = function(o, name, attr) {
1055
- defineGetter(o.prototype, name, (function() {
1056
- var tokenList;
1057
- var THIS = this, gibberishProperty = "__defineGetter__" + "DEFINE_PROPERTY" + name;
1058
- if (THIS[gibberishProperty]) return tokenList;
1059
- THIS[gibberishProperty] = true;
1060
- if (false === dpSupport) {
1061
- var visage;
1062
- var mirror = addProp.mirror || document.createElement("div");
1063
- var reflections = mirror.childNodes;
1064
- var l = reflections.length;
1065
- for (var i = 0; i < l; ++i) if (reflections[i]._R === THIS) {
1066
- visage = reflections[i];
1067
- break;
1068
- }
1069
- visage || (visage = mirror.appendChild(document.createElement("div")));
1070
- tokenList = DOMTokenList.call(visage, THIS, attr);
1071
- } else tokenList = new DOMTokenList(THIS, attr);
1072
- defineGetter(THIS, name, (function() {
1073
- return tokenList;
1074
- }));
1075
- delete THIS[gibberishProperty];
1076
- return tokenList;
1077
- }), true);
1078
- };
1079
- addProp(global.Element, "classList", "className");
1080
- addProp(global.HTMLElement, "classList", "className");
1081
- addProp(global.HTMLLinkElement, "relList", "rel");
1082
- addProp(global.HTMLAnchorElement, "relList", "rel");
1083
- addProp(global.HTMLAreaElement, "relList", "rel");
1084
- })(this);
1085
- }).call("object" === typeof window && window || "object" === typeof self && self || "object" === typeof global && global || {});
1086
-
1087
- var CHARACTER_COUNT_TRANSLATIONS = {
1088
- charactersUnderLimit: {
1089
- one: "You have %{count} character remaining",
1090
- other: "You have %{count} characters remaining"
1091
- },
1092
- charactersAtLimit: "You have 0 characters remaining",
1093
- charactersOverLimit: {
1094
- one: "You have %{count} character too many",
1095
- other: "You have %{count} characters too many"
1096
- },
1097
- wordsUnderLimit: {
1098
- one: "You have %{count} word remaining",
1099
- other: "You have %{count} words remaining"
1100
- },
1101
- wordsAtLimit: "You have 0 words remaining",
1102
- wordsOverLimit: {
1103
- one: "You have %{count} word too many",
1104
- other: "You have %{count} words too many"
606
+ }
607
+ }
608
+ }
609
+ getButtonPunctuationEl() {
610
+ const $punctuationEl = document.createElement('span');
611
+ $punctuationEl.classList.add('govuk-visually-hidden', this.sectionHeadingDividerClass);
612
+ $punctuationEl.innerHTML = ', ';
613
+ return $punctuationEl;
614
+ }
615
+ }
616
+ Accordion.moduleName = 'govuk-accordion';
617
+ Accordion.defaults = Object.freeze({
618
+ i18n: {
619
+ hideAllSections: 'Hide all sections',
620
+ hideSection: 'Hide',
621
+ hideSectionAriaLabel: 'Hide this section',
622
+ showAllSections: 'Show all sections',
623
+ showSection: 'Show',
624
+ showSectionAriaLabel: 'Show this section'
1105
625
  },
1106
- textareaDescription: {
1107
- other: ""
626
+ rememberExpanded: true
627
+ });
628
+ const helper = {
629
+ /**
630
+ * Check for `window.sessionStorage`, and that it actually works.
631
+ *
632
+ * @returns {boolean} True if session storage is available
633
+ */
634
+ checkForSessionStorage: function () {
635
+ const testString = 'this is the test string';
636
+ let result;
637
+ try {
638
+ window.sessionStorage.setItem(testString, testString);
639
+ result = window.sessionStorage.getItem(testString) === testString.toString();
640
+ window.sessionStorage.removeItem(testString);
641
+ return result;
642
+ } catch (exception) {
643
+ return false;
644
+ }
1108
645
  }
1109
646
  };
1110
647
 
1111
- function CharacterCount($module, config) {
1112
- if (!($module instanceof HTMLElement)) {
1113
- return this;
1114
- }
1115
- var $textarea = $module.querySelector(".govuk-js-character-count");
1116
- if (!($textarea instanceof HTMLTextAreaElement || $textarea instanceof HTMLInputElement)) {
1117
- return this;
1118
- }
1119
- var defaultConfig = {
1120
- threshold: 0,
1121
- i18n: CHARACTER_COUNT_TRANSLATIONS
1122
- };
1123
- var datasetConfig = normaliseDataset($module.dataset);
1124
- var configOverrides = {};
1125
- if ("maxwords" in datasetConfig || "maxlength" in datasetConfig) {
1126
- configOverrides = {
1127
- maxlength: false,
1128
- maxwords: false
1129
- };
648
+ const KEY_SPACE = 32;
649
+ const DEBOUNCE_TIMEOUT_IN_SECONDS = 1;
650
+
651
+ /**
652
+ * JavaScript enhancements for the Button component
653
+ *
654
+ * @preserve
655
+ */
656
+ class Button extends GOVUKFrontendComponent {
657
+ /**
658
+ * @param {Element | null} $module - HTML element to use for button
659
+ * @param {ButtonConfig} [config] - Button config
660
+ */
661
+ constructor($module, config = {}) {
662
+ super();
663
+ this.$module = void 0;
664
+ this.config = void 0;
665
+ this.debounceFormSubmitTimer = null;
666
+ if (!($module instanceof HTMLElement)) {
667
+ throw new ElementError({
668
+ componentName: 'Button',
669
+ element: $module,
670
+ identifier: 'Root element (`$module`)'
671
+ });
672
+ }
673
+ this.$module = $module;
674
+ this.config = mergeConfigs(Button.defaults, config, normaliseDataset($module.dataset));
675
+ this.$module.addEventListener('keydown', event => this.handleKeyDown(event));
676
+ this.$module.addEventListener('click', event => this.debounce(event));
677
+ }
678
+ handleKeyDown(event) {
679
+ const $target = event.target;
680
+ if (event.keyCode !== KEY_SPACE) {
681
+ return;
682
+ }
683
+ if ($target instanceof HTMLElement && $target.getAttribute('role') === 'button') {
684
+ event.preventDefault();
685
+ $target.click();
686
+ }
1130
687
  }
1131
- this.config = mergeConfigs(defaultConfig, config || {}, configOverrides, datasetConfig);
1132
- this.i18n = new I18n(extractConfigByNamespace(this.config, "i18n"), {
1133
- locale: closestAttributeValue($module, "lang")
1134
- });
1135
- this.maxLength = Infinity;
1136
- if ("maxwords" in this.config && this.config.maxwords) {
1137
- this.maxLength = this.config.maxwords;
1138
- } else if ("maxlength" in this.config && this.config.maxlength) {
1139
- this.maxLength = this.config.maxlength;
1140
- } else {
1141
- return;
688
+ debounce(event) {
689
+ if (!this.config.preventDoubleClick) {
690
+ return;
691
+ }
692
+ if (this.debounceFormSubmitTimer) {
693
+ event.preventDefault();
694
+ return false;
695
+ }
696
+ this.debounceFormSubmitTimer = window.setTimeout(() => {
697
+ this.debounceFormSubmitTimer = null;
698
+ }, DEBOUNCE_TIMEOUT_IN_SECONDS * 1000);
1142
699
  }
1143
- this.$module = $module;
1144
- this.$textarea = $textarea;
1145
- this.$visibleCountMessage = null;
1146
- this.$screenReaderCountMessage = null;
1147
- this.lastInputTimestamp = null;
1148
- this.lastInputValue = "";
1149
- this.valueChecker = null;
1150
700
  }
1151
701
 
1152
- CharacterCount.prototype.init = function() {
1153
- if (!this.$module || !this.$textarea) {
1154
- return;
1155
- }
1156
- var $textarea = this.$textarea;
1157
- var $textareaDescription = document.getElementById($textarea.id + "-info");
1158
- if (!$textareaDescription) {
1159
- return;
1160
- }
1161
- if ($textareaDescription.innerText.match(/^\s*$/)) {
1162
- $textareaDescription.innerText = this.i18n.t("textareaDescription", {
1163
- count: this.maxLength
1164
- });
1165
- }
1166
- $textarea.insertAdjacentElement("afterend", $textareaDescription);
1167
- var $screenReaderCountMessage = document.createElement("div");
1168
- $screenReaderCountMessage.className = "govuk-character-count__sr-status govuk-visually-hidden";
1169
- $screenReaderCountMessage.setAttribute("aria-live", "polite");
1170
- this.$screenReaderCountMessage = $screenReaderCountMessage;
1171
- $textareaDescription.insertAdjacentElement("afterend", $screenReaderCountMessage);
1172
- var $visibleCountMessage = document.createElement("div");
1173
- $visibleCountMessage.className = $textareaDescription.className;
1174
- $visibleCountMessage.classList.add("govuk-character-count__status");
1175
- $visibleCountMessage.setAttribute("aria-hidden", "true");
1176
- this.$visibleCountMessage = $visibleCountMessage;
1177
- $textareaDescription.insertAdjacentElement("afterend", $visibleCountMessage);
1178
- $textareaDescription.classList.add("govuk-visually-hidden");
1179
- $textarea.removeAttribute("maxlength");
1180
- this.bindChangeEvents();
1181
- window.addEventListener("onpageshow" in window ? "pageshow" : "DOMContentLoaded", this.updateCountMessage.bind(this));
1182
- this.updateCountMessage();
1183
- };
702
+ /**
703
+ * Button config
704
+ *
705
+ * @typedef {object} ButtonConfig
706
+ * @property {boolean} [preventDoubleClick=false] - Prevent accidental double
707
+ * clicks on submit buttons from submitting forms multiple times.
708
+ */
709
+ Button.moduleName = 'govuk-button';
710
+ Button.defaults = Object.freeze({
711
+ preventDoubleClick: false
712
+ });
1184
713
 
1185
- CharacterCount.prototype.bindChangeEvents = function() {
1186
- var $textarea = this.$textarea;
1187
- $textarea.addEventListener("keyup", this.handleKeyUp.bind(this));
1188
- $textarea.addEventListener("focus", this.handleFocus.bind(this));
1189
- $textarea.addEventListener("blur", this.handleBlur.bind(this));
1190
- };
714
+ function closestAttributeValue($element, attributeName) {
715
+ const $closestElementWithAttribute = $element.closest(`[${attributeName}]`);
716
+ return $closestElementWithAttribute ? $closestElementWithAttribute.getAttribute(attributeName) : null;
717
+ }
1191
718
 
1192
- CharacterCount.prototype.handleKeyUp = function() {
1193
- this.updateVisibleCountMessage();
1194
- this.lastInputTimestamp = Date.now();
1195
- };
1196
-
1197
- CharacterCount.prototype.handleFocus = function() {
1198
- this.valueChecker = setInterval(function() {
1199
- if (!this.lastInputTimestamp || Date.now() - 500 >= this.lastInputTimestamp) {
1200
- this.updateIfValueChanged();
719
+ /**
720
+ * Character count component
721
+ *
722
+ * Tracks the number of characters or words in the `.govuk-js-character-count`
723
+ * `<textarea>` inside the element. Displays a message with the remaining number
724
+ * of characters/words available, or the number of characters/words in excess.
725
+ *
726
+ * You can configure the message to only appear after a certain percentage
727
+ * of the available characters/words has been entered.
728
+ *
729
+ * @preserve
730
+ */
731
+ class CharacterCount extends GOVUKFrontendComponent {
732
+ /**
733
+ * @param {Element | null} $module - HTML element to use for character count
734
+ * @param {CharacterCountConfig} [config] - Character count config
735
+ */
736
+ constructor($module, config = {}) {
737
+ var _ref, _this$config$maxwords;
738
+ super();
739
+ this.$module = void 0;
740
+ this.$textarea = void 0;
741
+ this.$visibleCountMessage = void 0;
742
+ this.$screenReaderCountMessage = void 0;
743
+ this.lastInputTimestamp = null;
744
+ this.lastInputValue = '';
745
+ this.valueChecker = null;
746
+ this.config = void 0;
747
+ this.i18n = void 0;
748
+ this.maxLength = void 0;
749
+ if (!($module instanceof HTMLElement)) {
750
+ throw new ElementError({
751
+ componentName: 'Character count',
752
+ element: $module,
753
+ identifier: 'Root element (`$module`)'
754
+ });
1201
755
  }
1202
- }.bind(this), 1e3);
1203
- };
1204
-
1205
- CharacterCount.prototype.handleBlur = function() {
1206
- clearInterval(this.valueChecker);
1207
- };
1208
-
1209
- CharacterCount.prototype.updateIfValueChanged = function() {
1210
- if (this.$textarea.value !== this.lastInputValue) {
1211
- this.lastInputValue = this.$textarea.value;
756
+ const $textarea = $module.querySelector('.govuk-js-character-count');
757
+ if (!($textarea instanceof HTMLTextAreaElement || $textarea instanceof HTMLInputElement)) {
758
+ throw new ElementError({
759
+ componentName: 'Character count',
760
+ element: $textarea,
761
+ expectedType: 'HTMLTextareaElement or HTMLInputElement',
762
+ identifier: 'Form field (`.govuk-js-character-count`)'
763
+ });
764
+ }
765
+ const datasetConfig = normaliseDataset($module.dataset);
766
+ let configOverrides = {};
767
+ if ('maxwords' in datasetConfig || 'maxlength' in datasetConfig) {
768
+ configOverrides = {
769
+ maxlength: undefined,
770
+ maxwords: undefined
771
+ };
772
+ }
773
+ this.config = mergeConfigs(CharacterCount.defaults, config, configOverrides, datasetConfig);
774
+ const errors = validateConfig(CharacterCount.schema, this.config);
775
+ if (errors[0]) {
776
+ throw new ConfigError(`Character count: ${errors[0]}`);
777
+ }
778
+ this.i18n = new I18n(extractConfigByNamespace(this.config, 'i18n'), {
779
+ locale: closestAttributeValue($module, 'lang')
780
+ });
781
+ this.maxLength = (_ref = (_this$config$maxwords = this.config.maxwords) != null ? _this$config$maxwords : this.config.maxlength) != null ? _ref : Infinity;
782
+ this.$module = $module;
783
+ this.$textarea = $textarea;
784
+ const textareaDescriptionId = `${this.$textarea.id}-info`;
785
+ const $textareaDescription = document.getElementById(textareaDescriptionId);
786
+ if (!$textareaDescription) {
787
+ throw new ElementError({
788
+ componentName: 'Character count',
789
+ element: $textareaDescription,
790
+ identifier: `Count message (\`id="${textareaDescriptionId}"\`)`
791
+ });
792
+ }
793
+ if (`${$textareaDescription.textContent}`.match(/^\s*$/)) {
794
+ $textareaDescription.textContent = this.i18n.t('textareaDescription', {
795
+ count: this.maxLength
796
+ });
797
+ }
798
+ this.$textarea.insertAdjacentElement('afterend', $textareaDescription);
799
+ const $screenReaderCountMessage = document.createElement('div');
800
+ $screenReaderCountMessage.className = 'govuk-character-count__sr-status govuk-visually-hidden';
801
+ $screenReaderCountMessage.setAttribute('aria-live', 'polite');
802
+ this.$screenReaderCountMessage = $screenReaderCountMessage;
803
+ $textareaDescription.insertAdjacentElement('afterend', $screenReaderCountMessage);
804
+ const $visibleCountMessage = document.createElement('div');
805
+ $visibleCountMessage.className = $textareaDescription.className;
806
+ $visibleCountMessage.classList.add('govuk-character-count__status');
807
+ $visibleCountMessage.setAttribute('aria-hidden', 'true');
808
+ this.$visibleCountMessage = $visibleCountMessage;
809
+ $textareaDescription.insertAdjacentElement('afterend', $visibleCountMessage);
810
+ $textareaDescription.classList.add('govuk-visually-hidden');
811
+ this.$textarea.removeAttribute('maxlength');
812
+ this.bindChangeEvents();
813
+ window.addEventListener('pageshow', () => this.updateCountMessage());
1212
814
  this.updateCountMessage();
1213
815
  }
1214
- };
1215
-
1216
- CharacterCount.prototype.updateCountMessage = function() {
1217
- this.updateVisibleCountMessage();
1218
- this.updateScreenReaderCountMessage();
1219
- };
1220
-
1221
- CharacterCount.prototype.updateVisibleCountMessage = function() {
1222
- var $textarea = this.$textarea;
1223
- var $visibleCountMessage = this.$visibleCountMessage;
1224
- var remainingNumber = this.maxLength - this.count($textarea.value);
1225
- if (this.isOverThreshold()) {
1226
- $visibleCountMessage.classList.remove("govuk-character-count__message--disabled");
1227
- } else {
1228
- $visibleCountMessage.classList.add("govuk-character-count__message--disabled");
1229
- }
1230
- if (remainingNumber < 0) {
1231
- $textarea.classList.add("govuk-textarea--error");
1232
- $visibleCountMessage.classList.remove("govuk-hint");
1233
- $visibleCountMessage.classList.add("govuk-error-message");
1234
- } else {
1235
- $textarea.classList.remove("govuk-textarea--error");
1236
- $visibleCountMessage.classList.remove("govuk-error-message");
1237
- $visibleCountMessage.classList.add("govuk-hint");
1238
- }
1239
- $visibleCountMessage.innerText = this.getCountMessage();
1240
- };
1241
-
1242
- CharacterCount.prototype.updateScreenReaderCountMessage = function() {
1243
- var $screenReaderCountMessage = this.$screenReaderCountMessage;
1244
- if (this.isOverThreshold()) {
1245
- $screenReaderCountMessage.removeAttribute("aria-hidden");
1246
- } else {
1247
- $screenReaderCountMessage.setAttribute("aria-hidden", "true");
1248
- }
1249
- $screenReaderCountMessage.innerText = this.getCountMessage();
1250
- };
1251
-
1252
- CharacterCount.prototype.count = function(text) {
1253
- if ("maxwords" in this.config && this.config.maxwords) {
1254
- var tokens = text.match(/\S+/g) || [];
1255
- return tokens.length;
1256
- } else {
1257
- return text.length;
816
+ bindChangeEvents() {
817
+ this.$textarea.addEventListener('keyup', () => this.handleKeyUp());
818
+ this.$textarea.addEventListener('focus', () => this.handleFocus());
819
+ this.$textarea.addEventListener('blur', () => this.handleBlur());
1258
820
  }
1259
- };
1260
-
1261
- CharacterCount.prototype.getCountMessage = function() {
1262
- var remainingNumber = this.maxLength - this.count(this.$textarea.value);
1263
- var countType = "maxwords" in this.config && this.config.maxwords ? "words" : "characters";
1264
- return this.formatCountMessage(remainingNumber, countType);
1265
- };
1266
-
1267
- CharacterCount.prototype.formatCountMessage = function(remainingNumber, countType) {
1268
- if (remainingNumber === 0) {
1269
- return this.i18n.t(countType + "AtLimit");
821
+ handleKeyUp() {
822
+ this.updateVisibleCountMessage();
823
+ this.lastInputTimestamp = Date.now();
1270
824
  }
1271
- var translationKeySuffix = remainingNumber < 0 ? "OverLimit" : "UnderLimit";
1272
- return this.i18n.t(countType + translationKeySuffix, {
1273
- count: Math.abs(remainingNumber)
1274
- });
1275
- };
1276
-
1277
- CharacterCount.prototype.isOverThreshold = function() {
1278
- if (!this.config.threshold) {
1279
- return true;
825
+ handleFocus() {
826
+ this.valueChecker = window.setInterval(() => {
827
+ if (!this.lastInputTimestamp || Date.now() - 500 >= this.lastInputTimestamp) {
828
+ this.updateIfValueChanged();
829
+ }
830
+ }, 1000);
1280
831
  }
1281
- var $textarea = this.$textarea;
1282
- var currentLength = this.count($textarea.value);
1283
- var maxLength = this.maxLength;
1284
- var thresholdValue = maxLength * this.config.threshold / 100;
1285
- return thresholdValue <= currentLength;
1286
- };
1287
-
1288
- function Checkboxes($module) {
1289
- if (!($module instanceof HTMLElement)) {
1290
- return this;
832
+ handleBlur() {
833
+ if (this.valueChecker) {
834
+ window.clearInterval(this.valueChecker);
835
+ }
1291
836
  }
1292
- var $inputs = $module.querySelectorAll('input[type="checkbox"]');
1293
- if (!$inputs.length) {
1294
- return this;
837
+ updateIfValueChanged() {
838
+ if (this.$textarea.value !== this.lastInputValue) {
839
+ this.lastInputValue = this.$textarea.value;
840
+ this.updateCountMessage();
841
+ }
842
+ }
843
+ updateCountMessage() {
844
+ this.updateVisibleCountMessage();
845
+ this.updateScreenReaderCountMessage();
846
+ }
847
+ updateVisibleCountMessage() {
848
+ const remainingNumber = this.maxLength - this.count(this.$textarea.value);
849
+ const isError = remainingNumber < 0;
850
+ this.$visibleCountMessage.classList.toggle('govuk-character-count__message--disabled', !this.isOverThreshold());
851
+ this.$textarea.classList.toggle('govuk-textarea--error', isError);
852
+ this.$visibleCountMessage.classList.toggle('govuk-error-message', isError);
853
+ this.$visibleCountMessage.classList.toggle('govuk-hint', !isError);
854
+ this.$visibleCountMessage.textContent = this.getCountMessage();
855
+ }
856
+ updateScreenReaderCountMessage() {
857
+ if (this.isOverThreshold()) {
858
+ this.$screenReaderCountMessage.removeAttribute('aria-hidden');
859
+ } else {
860
+ this.$screenReaderCountMessage.setAttribute('aria-hidden', 'true');
861
+ }
862
+ this.$screenReaderCountMessage.textContent = this.getCountMessage();
863
+ }
864
+ count(text) {
865
+ if (this.config.maxwords) {
866
+ var _text$match;
867
+ const tokens = (_text$match = text.match(/\S+/g)) != null ? _text$match : [];
868
+ return tokens.length;
869
+ }
870
+ return text.length;
871
+ }
872
+ getCountMessage() {
873
+ const remainingNumber = this.maxLength - this.count(this.$textarea.value);
874
+ const countType = this.config.maxwords ? 'words' : 'characters';
875
+ return this.formatCountMessage(remainingNumber, countType);
876
+ }
877
+ formatCountMessage(remainingNumber, countType) {
878
+ if (remainingNumber === 0) {
879
+ return this.i18n.t(`${countType}AtLimit`);
880
+ }
881
+ const translationKeySuffix = remainingNumber < 0 ? 'OverLimit' : 'UnderLimit';
882
+ return this.i18n.t(`${countType}${translationKeySuffix}`, {
883
+ count: Math.abs(remainingNumber)
884
+ });
885
+ }
886
+ isOverThreshold() {
887
+ if (!this.config.threshold) {
888
+ return true;
889
+ }
890
+ const currentLength = this.count(this.$textarea.value);
891
+ const maxLength = this.maxLength;
892
+ const thresholdValue = maxLength * this.config.threshold / 100;
893
+ return thresholdValue <= currentLength;
1295
894
  }
1296
- this.$module = $module;
1297
- this.$inputs = $inputs;
1298
895
  }
1299
896
 
1300
- Checkboxes.prototype.init = function() {
1301
- if (!this.$module || !this.$inputs) {
1302
- return;
897
+ /**
898
+ * Character count config
899
+ *
900
+ * @see {@link CharacterCount.defaults}
901
+ * @typedef {object} CharacterCountConfig
902
+ * @property {number} [maxlength] - The maximum number of characters.
903
+ * If maxwords is provided, the maxlength option will be ignored.
904
+ * @property {number} [maxwords] - The maximum number of words. If maxwords is
905
+ * provided, the maxlength option will be ignored.
906
+ * @property {number} [threshold=0] - The percentage value of the limit at
907
+ * which point the count message is displayed. If this attribute is set, the
908
+ * count message will be hidden by default.
909
+ * @property {CharacterCountTranslations} [i18n=CharacterCount.defaults.i18n] - Character count translations
910
+ */
911
+
912
+ /**
913
+ * Character count translations
914
+ *
915
+ * @see {@link CharacterCount.defaults.i18n}
916
+ * @typedef {object} CharacterCountTranslations
917
+ *
918
+ * Messages shown to users as they type. It provides feedback on how many words
919
+ * or characters they have remaining or if they are over the limit. This also
920
+ * includes a message used as an accessible description for the textarea.
921
+ * @property {TranslationPluralForms} [charactersUnderLimit] - Message displayed
922
+ * when the number of characters is under the configured maximum, `maxlength`.
923
+ * This message is displayed visually and through assistive technologies. The
924
+ * component will replace the `%{count}` placeholder with the number of
925
+ * remaining characters. This is a [pluralised list of
926
+ * messages](https://frontend.design-system.service.gov.uk/localise-govuk-frontend).
927
+ * @property {string} [charactersAtLimit] - Message displayed when the number of
928
+ * characters reaches the configured maximum, `maxlength`. This message is
929
+ * displayed visually and through assistive technologies.
930
+ * @property {TranslationPluralForms} [charactersOverLimit] - Message displayed
931
+ * when the number of characters is over the configured maximum, `maxlength`.
932
+ * This message is displayed visually and through assistive technologies. The
933
+ * component will replace the `%{count}` placeholder with the number of
934
+ * remaining characters. This is a [pluralised list of
935
+ * messages](https://frontend.design-system.service.gov.uk/localise-govuk-frontend).
936
+ * @property {TranslationPluralForms} [wordsUnderLimit] - Message displayed when
937
+ * the number of words is under the configured maximum, `maxlength`. This
938
+ * message is displayed visually and through assistive technologies. The
939
+ * component will replace the `%{count}` placeholder with the number of
940
+ * remaining words. This is a [pluralised list of
941
+ * messages](https://frontend.design-system.service.gov.uk/localise-govuk-frontend).
942
+ * @property {string} [wordsAtLimit] - Message displayed when the number of
943
+ * words reaches the configured maximum, `maxlength`. This message is
944
+ * displayed visually and through assistive technologies.
945
+ * @property {TranslationPluralForms} [wordsOverLimit] - Message displayed when
946
+ * the number of words is over the configured maximum, `maxlength`. This
947
+ * message is displayed visually and through assistive technologies. The
948
+ * component will replace the `%{count}` placeholder with the number of
949
+ * remaining words. This is a [pluralised list of
950
+ * messages](https://frontend.design-system.service.gov.uk/localise-govuk-frontend).
951
+ * @property {TranslationPluralForms} [textareaDescription] - Message made
952
+ * available to assistive technologies, if none is already present in the
953
+ * HTML, to describe that the component accepts only a limited amount of
954
+ * content. It is visible on the page when JavaScript is unavailable. The
955
+ * component will replace the `%{count}` placeholder with the value of the
956
+ * `maxlength` or `maxwords` parameter.
957
+ */
958
+
959
+ /**
960
+ * @typedef {import('../../common/index.mjs').Schema} Schema
961
+ * @typedef {import('../../i18n.mjs').TranslationPluralForms} TranslationPluralForms
962
+ */
963
+ CharacterCount.moduleName = 'govuk-character-count';
964
+ CharacterCount.defaults = Object.freeze({
965
+ threshold: 0,
966
+ i18n: {
967
+ charactersUnderLimit: {
968
+ one: 'You have %{count} character remaining',
969
+ other: 'You have %{count} characters remaining'
970
+ },
971
+ charactersAtLimit: 'You have 0 characters remaining',
972
+ charactersOverLimit: {
973
+ one: 'You have %{count} character too many',
974
+ other: 'You have %{count} characters too many'
975
+ },
976
+ wordsUnderLimit: {
977
+ one: 'You have %{count} word remaining',
978
+ other: 'You have %{count} words remaining'
979
+ },
980
+ wordsAtLimit: 'You have 0 words remaining',
981
+ wordsOverLimit: {
982
+ one: 'You have %{count} word too many',
983
+ other: 'You have %{count} words too many'
984
+ },
985
+ textareaDescription: {
986
+ other: ''
987
+ }
988
+ }
989
+ });
990
+ CharacterCount.schema = Object.freeze({
991
+ anyOf: [{
992
+ required: ['maxwords'],
993
+ errorMessage: 'Either "maxlength" or "maxwords" must be provided'
994
+ }, {
995
+ required: ['maxlength'],
996
+ errorMessage: 'Either "maxlength" or "maxwords" must be provided'
997
+ }]
998
+ });
999
+
1000
+ /**
1001
+ * Checkboxes component
1002
+ *
1003
+ * @preserve
1004
+ */
1005
+ class Checkboxes extends GOVUKFrontendComponent {
1006
+ /**
1007
+ * Checkboxes can be associated with a 'conditionally revealed' content block
1008
+ * – for example, a checkbox for 'Phone' could reveal an additional form field
1009
+ * for the user to enter their phone number.
1010
+ *
1011
+ * These associations are made using a `data-aria-controls` attribute, which
1012
+ * is promoted to an aria-controls attribute during initialisation.
1013
+ *
1014
+ * We also need to restore the state of any conditional reveals on the page
1015
+ * (for example if the user has navigated back), and set up event handlers to
1016
+ * keep the reveal in sync with the checkbox state.
1017
+ *
1018
+ * @param {Element | null} $module - HTML element to use for checkboxes
1019
+ */
1020
+ constructor($module) {
1021
+ super();
1022
+ this.$module = void 0;
1023
+ this.$inputs = void 0;
1024
+ if (!($module instanceof HTMLElement)) {
1025
+ throw new ElementError({
1026
+ componentName: 'Checkboxes',
1027
+ element: $module,
1028
+ identifier: 'Root element (`$module`)'
1029
+ });
1030
+ }
1031
+ const $inputs = $module.querySelectorAll('input[type="checkbox"]');
1032
+ if (!$inputs.length) {
1033
+ throw new ElementError({
1034
+ componentName: 'Checkboxes',
1035
+ identifier: 'Form inputs (`<input type="checkbox">`)'
1036
+ });
1037
+ }
1038
+ this.$module = $module;
1039
+ this.$inputs = $inputs;
1040
+ this.$inputs.forEach($input => {
1041
+ const targetId = $input.getAttribute('data-aria-controls');
1042
+ if (!targetId) {
1043
+ return;
1044
+ }
1045
+ if (!document.getElementById(targetId)) {
1046
+ throw new ElementError({
1047
+ componentName: 'Checkboxes',
1048
+ identifier: `Conditional reveal (\`id="${targetId}"\`)`
1049
+ });
1050
+ }
1051
+ $input.setAttribute('aria-controls', targetId);
1052
+ $input.removeAttribute('data-aria-controls');
1053
+ });
1054
+ window.addEventListener('pageshow', () => this.syncAllConditionalReveals());
1055
+ this.syncAllConditionalReveals();
1056
+ this.$module.addEventListener('click', event => this.handleClick(event));
1057
+ }
1058
+ syncAllConditionalReveals() {
1059
+ this.$inputs.forEach($input => this.syncConditionalRevealWithInputState($input));
1303
1060
  }
1304
- var $module = this.$module;
1305
- var $inputs = this.$inputs;
1306
- nodeListForEach($inputs, (function($input) {
1307
- var targetId = $input.getAttribute("data-aria-controls");
1308
- if (!targetId || !document.getElementById(targetId)) {
1061
+ syncConditionalRevealWithInputState($input) {
1062
+ const targetId = $input.getAttribute('aria-controls');
1063
+ if (!targetId) {
1309
1064
  return;
1310
1065
  }
1311
- $input.setAttribute("aria-controls", targetId);
1312
- $input.removeAttribute("data-aria-controls");
1313
- }));
1314
- window.addEventListener("onpageshow" in window ? "pageshow" : "DOMContentLoaded", this.syncAllConditionalReveals.bind(this));
1315
- this.syncAllConditionalReveals();
1316
- $module.addEventListener("click", this.handleClick.bind(this));
1317
- };
1318
-
1319
- Checkboxes.prototype.syncAllConditionalReveals = function() {
1320
- nodeListForEach(this.$inputs, this.syncConditionalRevealWithInputState.bind(this));
1321
- };
1322
-
1323
- Checkboxes.prototype.syncConditionalRevealWithInputState = function($input) {
1324
- var targetId = $input.getAttribute("aria-controls");
1325
- if (!targetId) {
1326
- return;
1066
+ const $target = document.getElementById(targetId);
1067
+ if ($target && $target.classList.contains('govuk-checkboxes__conditional')) {
1068
+ const inputIsChecked = $input.checked;
1069
+ $input.setAttribute('aria-expanded', inputIsChecked.toString());
1070
+ $target.classList.toggle('govuk-checkboxes__conditional--hidden', !inputIsChecked);
1071
+ }
1327
1072
  }
1328
- var $target = document.getElementById(targetId);
1329
- if ($target && $target.classList.contains("govuk-checkboxes__conditional")) {
1330
- var inputIsChecked = $input.checked;
1331
- $input.setAttribute("aria-expanded", inputIsChecked.toString());
1332
- $target.classList.toggle("govuk-checkboxes__conditional--hidden", !inputIsChecked);
1073
+ unCheckAllInputsExcept($input) {
1074
+ const allInputsWithSameName = document.querySelectorAll(`input[type="checkbox"][name="${$input.name}"]`);
1075
+ allInputsWithSameName.forEach($inputWithSameName => {
1076
+ const hasSameFormOwner = $input.form === $inputWithSameName.form;
1077
+ if (hasSameFormOwner && $inputWithSameName !== $input) {
1078
+ $inputWithSameName.checked = false;
1079
+ this.syncConditionalRevealWithInputState($inputWithSameName);
1080
+ }
1081
+ });
1333
1082
  }
1334
- };
1335
-
1336
- Checkboxes.prototype.unCheckAllInputsExcept = function($input) {
1337
- var $component = this;
1338
- var allInputsWithSameName = document.querySelectorAll('input[type="checkbox"][name="' + $input.name + '"]');
1339
- nodeListForEach(allInputsWithSameName, (function($inputWithSameName) {
1340
- var hasSameFormOwner = $input.form === $inputWithSameName.form;
1341
- if (hasSameFormOwner && $inputWithSameName !== $input) {
1342
- $inputWithSameName.checked = false;
1343
- $component.syncConditionalRevealWithInputState($inputWithSameName);
1344
- }
1345
- }));
1346
- };
1347
-
1348
- Checkboxes.prototype.unCheckExclusiveInputs = function($input) {
1349
- var $component = this;
1350
- var allInputsWithSameNameAndExclusiveBehaviour = document.querySelectorAll('input[data-behaviour="exclusive"][type="checkbox"][name="' + $input.name + '"]');
1351
- nodeListForEach(allInputsWithSameNameAndExclusiveBehaviour, (function($exclusiveInput) {
1352
- var hasSameFormOwner = $input.form === $exclusiveInput.form;
1353
- if (hasSameFormOwner) {
1354
- $exclusiveInput.checked = false;
1355
- $component.syncConditionalRevealWithInputState($exclusiveInput);
1356
- }
1357
- }));
1358
- };
1359
-
1360
- Checkboxes.prototype.handleClick = function(event) {
1361
- var $clickedInput = event.target;
1362
- if (!($clickedInput instanceof HTMLInputElement) || $clickedInput.type !== "checkbox") {
1363
- return;
1083
+ unCheckExclusiveInputs($input) {
1084
+ const allInputsWithSameNameAndExclusiveBehaviour = document.querySelectorAll(`input[data-behaviour="exclusive"][type="checkbox"][name="${$input.name}"]`);
1085
+ allInputsWithSameNameAndExclusiveBehaviour.forEach($exclusiveInput => {
1086
+ const hasSameFormOwner = $input.form === $exclusiveInput.form;
1087
+ if (hasSameFormOwner) {
1088
+ $exclusiveInput.checked = false;
1089
+ this.syncConditionalRevealWithInputState($exclusiveInput);
1090
+ }
1091
+ });
1364
1092
  }
1365
- var hasAriaControls = $clickedInput.getAttribute("aria-controls");
1366
- if (hasAriaControls) {
1367
- this.syncConditionalRevealWithInputState($clickedInput);
1093
+ handleClick(event) {
1094
+ const $clickedInput = event.target;
1095
+ if (!($clickedInput instanceof HTMLInputElement) || $clickedInput.type !== 'checkbox') {
1096
+ return;
1097
+ }
1098
+ const hasAriaControls = $clickedInput.getAttribute('aria-controls');
1099
+ if (hasAriaControls) {
1100
+ this.syncConditionalRevealWithInputState($clickedInput);
1101
+ }
1102
+ if (!$clickedInput.checked) {
1103
+ return;
1104
+ }
1105
+ const hasBehaviourExclusive = $clickedInput.getAttribute('data-behaviour') === 'exclusive';
1106
+ if (hasBehaviourExclusive) {
1107
+ this.unCheckAllInputsExcept($clickedInput);
1108
+ } else {
1109
+ this.unCheckExclusiveInputs($clickedInput);
1110
+ }
1368
1111
  }
1369
- if (!$clickedInput.checked) {
1370
- return;
1112
+ }
1113
+ Checkboxes.moduleName = 'govuk-checkboxes';
1114
+
1115
+ /**
1116
+ * Error summary component
1117
+ *
1118
+ * Takes focus on initialisation for accessible announcement, unless disabled in
1119
+ * configuration.
1120
+ *
1121
+ * @preserve
1122
+ */
1123
+ class ErrorSummary extends GOVUKFrontendComponent {
1124
+ /**
1125
+ * @param {Element | null} $module - HTML element to use for error summary
1126
+ * @param {ErrorSummaryConfig} [config] - Error summary config
1127
+ */
1128
+ constructor($module, config = {}) {
1129
+ super();
1130
+ this.$module = void 0;
1131
+ this.config = void 0;
1132
+ if (!($module instanceof HTMLElement)) {
1133
+ throw new ElementError({
1134
+ componentName: 'Error summary',
1135
+ element: $module,
1136
+ identifier: 'Root element (`$module`)'
1137
+ });
1138
+ }
1139
+ this.$module = $module;
1140
+ this.config = mergeConfigs(ErrorSummary.defaults, config, normaliseDataset($module.dataset));
1141
+ this.setFocus();
1142
+ this.$module.addEventListener('click', event => this.handleClick(event));
1371
1143
  }
1372
- var hasBehaviourExclusive = $clickedInput.getAttribute("data-behaviour") === "exclusive";
1373
- if (hasBehaviourExclusive) {
1374
- this.unCheckAllInputsExcept($clickedInput);
1375
- } else {
1376
- this.unCheckExclusiveInputs($clickedInput);
1144
+ setFocus() {
1145
+ if (this.config.disableAutoFocus) {
1146
+ return;
1147
+ }
1148
+ this.$module.setAttribute('tabindex', '-1');
1149
+ this.$module.addEventListener('blur', () => {
1150
+ this.$module.removeAttribute('tabindex');
1151
+ });
1152
+ this.$module.focus();
1153
+ }
1154
+ handleClick(event) {
1155
+ const $target = event.target;
1156
+ if ($target && this.focusTarget($target)) {
1157
+ event.preventDefault();
1158
+ }
1159
+ }
1160
+ focusTarget($target) {
1161
+ if (!($target instanceof HTMLAnchorElement)) {
1162
+ return false;
1163
+ }
1164
+ const inputId = getFragmentFromUrl($target.href);
1165
+ if (!inputId) {
1166
+ return false;
1167
+ }
1168
+ const $input = document.getElementById(inputId);
1169
+ if (!$input) {
1170
+ return false;
1171
+ }
1172
+ const $legendOrLabel = this.getAssociatedLegendOrLabel($input);
1173
+ if (!$legendOrLabel) {
1174
+ return false;
1175
+ }
1176
+ $legendOrLabel.scrollIntoView();
1177
+ $input.focus({
1178
+ preventScroll: true
1179
+ });
1180
+ return true;
1181
+ }
1182
+ getAssociatedLegendOrLabel($input) {
1183
+ var _document$querySelect;
1184
+ const $fieldset = $input.closest('fieldset');
1185
+ if ($fieldset) {
1186
+ const $legends = $fieldset.getElementsByTagName('legend');
1187
+ if ($legends.length) {
1188
+ const $candidateLegend = $legends[0];
1189
+ if ($input instanceof HTMLInputElement && ($input.type === 'checkbox' || $input.type === 'radio')) {
1190
+ return $candidateLegend;
1191
+ }
1192
+ const legendTop = $candidateLegend.getBoundingClientRect().top;
1193
+ const inputRect = $input.getBoundingClientRect();
1194
+ if (inputRect.height && window.innerHeight) {
1195
+ const inputBottom = inputRect.top + inputRect.height;
1196
+ if (inputBottom - legendTop < window.innerHeight / 2) {
1197
+ return $candidateLegend;
1198
+ }
1199
+ }
1200
+ }
1201
+ }
1202
+ return (_document$querySelect = document.querySelector(`label[for='${$input.getAttribute('id')}']`)) != null ? _document$querySelect : $input.closest('label');
1377
1203
  }
1378
- };
1379
-
1380
- function ErrorSummary($module, config) {
1381
- if (!($module instanceof HTMLElement)) {
1382
- return this;
1383
- }
1384
- this.$module = $module;
1385
- var defaultConfig = {
1386
- disableAutoFocus: false
1387
- };
1388
- this.config = mergeConfigs(defaultConfig, config || {}, normaliseDataset($module.dataset));
1389
1204
  }
1390
1205
 
1391
- ErrorSummary.prototype.init = function() {
1392
- if (!this.$module) {
1393
- return;
1206
+ /**
1207
+ * Error summary config
1208
+ *
1209
+ * @typedef {object} ErrorSummaryConfig
1210
+ * @property {boolean} [disableAutoFocus=false] - If set to `true` the error
1211
+ * summary will not be focussed when the page loads.
1212
+ */
1213
+ ErrorSummary.moduleName = 'govuk-error-summary';
1214
+ ErrorSummary.defaults = Object.freeze({
1215
+ disableAutoFocus: false
1216
+ });
1217
+
1218
+ /**
1219
+ * Exit this page component
1220
+ *
1221
+ * @preserve
1222
+ */
1223
+ class ExitThisPage extends GOVUKFrontendComponent {
1224
+ /**
1225
+ * @param {Element | null} $module - HTML element that wraps the Exit This Page button
1226
+ * @param {ExitThisPageConfig} [config] - Exit This Page config
1227
+ */
1228
+ constructor($module, config = {}) {
1229
+ super();
1230
+ this.$module = void 0;
1231
+ this.config = void 0;
1232
+ this.i18n = void 0;
1233
+ this.$button = void 0;
1234
+ this.$skiplinkButton = null;
1235
+ this.$updateSpan = null;
1236
+ this.$indicatorContainer = null;
1237
+ this.$overlay = null;
1238
+ this.keypressCounter = 0;
1239
+ this.lastKeyWasModified = false;
1240
+ this.timeoutTime = 5000;
1241
+ this.keypressTimeoutId = null;
1242
+ this.timeoutMessageId = null;
1243
+ if (!($module instanceof HTMLElement)) {
1244
+ throw new ElementError({
1245
+ componentName: 'Exit this page',
1246
+ element: $module,
1247
+ identifier: 'Root element (`$module`)'
1248
+ });
1249
+ }
1250
+ const $button = $module.querySelector('.govuk-exit-this-page__button');
1251
+ if (!($button instanceof HTMLAnchorElement)) {
1252
+ throw new ElementError({
1253
+ componentName: 'Exit this page',
1254
+ element: $button,
1255
+ expectedType: 'HTMLAnchorElement',
1256
+ identifier: 'Button (`.govuk-exit-this-page__button`)'
1257
+ });
1258
+ }
1259
+ this.config = mergeConfigs(ExitThisPage.defaults, config, normaliseDataset($module.dataset));
1260
+ this.i18n = new I18n(extractConfigByNamespace(this.config, 'i18n'));
1261
+ this.$module = $module;
1262
+ this.$button = $button;
1263
+ const $skiplinkButton = document.querySelector('.govuk-js-exit-this-page-skiplink');
1264
+ if ($skiplinkButton instanceof HTMLAnchorElement) {
1265
+ this.$skiplinkButton = $skiplinkButton;
1266
+ }
1267
+ this.buildIndicator();
1268
+ this.initUpdateSpan();
1269
+ this.initButtonClickHandler();
1270
+ if (!('govukFrontendExitThisPageKeypress' in document.body.dataset)) {
1271
+ document.addEventListener('keyup', this.handleKeypress.bind(this), true);
1272
+ document.body.dataset.govukFrontendExitThisPageKeypress = 'true';
1273
+ }
1274
+ window.addEventListener('pageshow', this.resetPage.bind(this));
1275
+ }
1276
+ initUpdateSpan() {
1277
+ this.$updateSpan = document.createElement('span');
1278
+ this.$updateSpan.setAttribute('role', 'status');
1279
+ this.$updateSpan.className = 'govuk-visually-hidden';
1280
+ this.$module.appendChild(this.$updateSpan);
1281
+ }
1282
+ initButtonClickHandler() {
1283
+ this.$button.addEventListener('click', this.handleClick.bind(this));
1284
+ if (this.$skiplinkButton) {
1285
+ this.$skiplinkButton.addEventListener('click', this.handleClick.bind(this));
1286
+ }
1394
1287
  }
1395
- var $module = this.$module;
1396
- this.setFocus();
1397
- $module.addEventListener("click", this.handleClick.bind(this));
1398
- };
1399
-
1400
- ErrorSummary.prototype.setFocus = function() {
1401
- var $module = this.$module;
1402
- if (this.config.disableAutoFocus) {
1403
- return;
1288
+ buildIndicator() {
1289
+ this.$indicatorContainer = document.createElement('div');
1290
+ this.$indicatorContainer.className = 'govuk-exit-this-page__indicator';
1291
+ this.$indicatorContainer.setAttribute('aria-hidden', 'true');
1292
+ for (let i = 0; i < 3; i++) {
1293
+ const $indicator = document.createElement('div');
1294
+ $indicator.className = 'govuk-exit-this-page__indicator-light';
1295
+ this.$indicatorContainer.appendChild($indicator);
1296
+ }
1297
+ this.$button.appendChild(this.$indicatorContainer);
1404
1298
  }
1405
- $module.setAttribute("tabindex", "-1");
1406
- $module.addEventListener("blur", (function() {
1407
- $module.removeAttribute("tabindex");
1408
- }));
1409
- $module.focus();
1410
- };
1411
-
1412
- ErrorSummary.prototype.handleClick = function(event) {
1413
- var $target = event.target;
1414
- if (this.focusTarget($target)) {
1299
+ updateIndicator() {
1300
+ if (!this.$indicatorContainer) {
1301
+ return;
1302
+ }
1303
+ this.$indicatorContainer.classList.toggle('govuk-exit-this-page__indicator--visible', this.keypressCounter > 0);
1304
+ const $indicators = this.$indicatorContainer.querySelectorAll('.govuk-exit-this-page__indicator-light');
1305
+ $indicators.forEach(($indicator, index) => {
1306
+ $indicator.classList.toggle('govuk-exit-this-page__indicator-light--on', index < this.keypressCounter);
1307
+ });
1308
+ }
1309
+ exitPage() {
1310
+ if (!this.$updateSpan) {
1311
+ return;
1312
+ }
1313
+ this.$updateSpan.textContent = '';
1314
+ document.body.classList.add('govuk-exit-this-page-hide-content');
1315
+ this.$overlay = document.createElement('div');
1316
+ this.$overlay.className = 'govuk-exit-this-page-overlay';
1317
+ this.$overlay.setAttribute('role', 'alert');
1318
+ document.body.appendChild(this.$overlay);
1319
+ this.$overlay.textContent = this.i18n.t('activated');
1320
+ window.location.href = this.$button.href;
1321
+ }
1322
+ handleClick(event) {
1415
1323
  event.preventDefault();
1324
+ this.exitPage();
1416
1325
  }
1417
- };
1326
+ handleKeypress(event) {
1327
+ if (!this.$updateSpan) {
1328
+ return;
1329
+ }
1330
+ if ((event.key === 'Shift' || event.keyCode === 16 || event.which === 16) && !this.lastKeyWasModified) {
1331
+ this.keypressCounter += 1;
1332
+ this.updateIndicator();
1333
+ if (this.timeoutMessageId) {
1334
+ window.clearTimeout(this.timeoutMessageId);
1335
+ this.timeoutMessageId = null;
1336
+ }
1337
+ if (this.keypressCounter >= 3) {
1338
+ this.keypressCounter = 0;
1339
+ if (this.keypressTimeoutId) {
1340
+ window.clearTimeout(this.keypressTimeoutId);
1341
+ this.keypressTimeoutId = null;
1342
+ }
1343
+ this.exitPage();
1344
+ } else {
1345
+ if (this.keypressCounter === 1) {
1346
+ this.$updateSpan.textContent = this.i18n.t('pressTwoMoreTimes');
1347
+ } else {
1348
+ this.$updateSpan.textContent = this.i18n.t('pressOneMoreTime');
1349
+ }
1350
+ }
1351
+ this.setKeypressTimer();
1352
+ } else if (this.keypressTimeoutId) {
1353
+ this.resetKeypressTimer();
1354
+ }
1355
+ this.lastKeyWasModified = event.shiftKey;
1356
+ }
1357
+ setKeypressTimer() {
1358
+ if (this.keypressTimeoutId) {
1359
+ window.clearTimeout(this.keypressTimeoutId);
1360
+ }
1361
+ this.keypressTimeoutId = window.setTimeout(this.resetKeypressTimer.bind(this), this.timeoutTime);
1362
+ }
1363
+ resetKeypressTimer() {
1364
+ if (!this.$updateSpan) {
1365
+ return;
1366
+ }
1367
+ if (this.keypressTimeoutId) {
1368
+ window.clearTimeout(this.keypressTimeoutId);
1369
+ this.keypressTimeoutId = null;
1370
+ }
1371
+ const $updateSpan = this.$updateSpan;
1372
+ this.keypressCounter = 0;
1373
+ $updateSpan.textContent = this.i18n.t('timedOut');
1374
+ this.timeoutMessageId = window.setTimeout(() => {
1375
+ $updateSpan.textContent = '';
1376
+ }, this.timeoutTime);
1377
+ this.updateIndicator();
1378
+ }
1379
+ resetPage() {
1380
+ document.body.classList.remove('govuk-exit-this-page-hide-content');
1381
+ if (this.$overlay) {
1382
+ this.$overlay.remove();
1383
+ this.$overlay = null;
1384
+ }
1385
+ if (this.$updateSpan) {
1386
+ this.$updateSpan.setAttribute('role', 'status');
1387
+ this.$updateSpan.textContent = '';
1388
+ }
1389
+ this.updateIndicator();
1390
+ if (this.keypressTimeoutId) {
1391
+ window.clearTimeout(this.keypressTimeoutId);
1392
+ }
1393
+ if (this.timeoutMessageId) {
1394
+ window.clearTimeout(this.timeoutMessageId);
1395
+ }
1396
+ }
1397
+ }
1418
1398
 
1419
- ErrorSummary.prototype.focusTarget = function($target) {
1420
- if (!($target instanceof HTMLAnchorElement)) {
1421
- return false;
1399
+ /**
1400
+ * Exit this Page config
1401
+ *
1402
+ * @see {@link ExitThisPage.defaults}
1403
+ * @typedef {object} ExitThisPageConfig
1404
+ * @property {ExitThisPageTranslations} [i18n=ExitThisPage.defaults.i18n] - Exit this page translations
1405
+ */
1406
+
1407
+ /**
1408
+ * Exit this Page translations
1409
+ *
1410
+ * @see {@link ExitThisPage.defaults.i18n}
1411
+ * @typedef {object} ExitThisPageTranslations
1412
+ *
1413
+ * Messages used by the component programatically inserted text, including
1414
+ * overlay text and screen reader announcements.
1415
+ * @property {string} [activated] - Screen reader announcement for when EtP
1416
+ * keypress functionality has been successfully activated.
1417
+ * @property {string} [timedOut] - Screen reader announcement for when the EtP
1418
+ * keypress functionality has timed out.
1419
+ * @property {string} [pressTwoMoreTimes] - Screen reader announcement informing
1420
+ * the user they must press the activation key two more times.
1421
+ * @property {string} [pressOneMoreTime] - Screen reader announcement informing
1422
+ * the user they must press the activation key one more time.
1423
+ */
1424
+ ExitThisPage.moduleName = 'govuk-exit-this-page';
1425
+ ExitThisPage.defaults = Object.freeze({
1426
+ i18n: {
1427
+ activated: 'Loading.',
1428
+ timedOut: 'Exit this page expired.',
1429
+ pressTwoMoreTimes: 'Shift, press 2 more times to exit.',
1430
+ pressOneMoreTime: 'Shift, press 1 more time to exit.'
1431
+ }
1432
+ });
1433
+
1434
+ /**
1435
+ * Header component
1436
+ *
1437
+ * @preserve
1438
+ */
1439
+ class Header extends GOVUKFrontendComponent {
1440
+ /**
1441
+ * Apply a matchMedia for desktop which will trigger a state sync if the
1442
+ * browser viewport moves between states.
1443
+ *
1444
+ * @param {Element | null} $module - HTML element to use for header
1445
+ */
1446
+ constructor($module) {
1447
+ super();
1448
+ this.$module = void 0;
1449
+ this.$menuButton = void 0;
1450
+ this.$menu = void 0;
1451
+ this.menuIsOpen = false;
1452
+ this.mql = null;
1453
+ if (!$module) {
1454
+ throw new ElementError({
1455
+ componentName: 'Header',
1456
+ element: $module,
1457
+ identifier: 'Root element (`$module`)'
1458
+ });
1459
+ }
1460
+ this.$module = $module;
1461
+ const $menuButton = $module.querySelector('.govuk-js-header-toggle');
1462
+ if (!$menuButton) {
1463
+ return this;
1464
+ }
1465
+ const menuId = $menuButton.getAttribute('aria-controls');
1466
+ if (!menuId) {
1467
+ throw new ElementError({
1468
+ componentName: 'Header',
1469
+ identifier: 'Navigation button (`<button class="govuk-js-header-toggle">`) attribute (`aria-controls`)'
1470
+ });
1471
+ }
1472
+ const $menu = document.getElementById(menuId);
1473
+ if (!$menu) {
1474
+ throw new ElementError({
1475
+ componentName: 'Header',
1476
+ element: $menu,
1477
+ identifier: `Navigation (\`<ul id="${menuId}">\`)`
1478
+ });
1479
+ }
1480
+ this.$menu = $menu;
1481
+ this.$menuButton = $menuButton;
1482
+ this.mql = window.matchMedia('(min-width: 48.0625em)');
1483
+ if ('addEventListener' in this.mql) {
1484
+ this.mql.addEventListener('change', () => this.syncState());
1485
+ } else {
1486
+ this.mql.addListener(() => this.syncState());
1487
+ }
1488
+ this.syncState();
1489
+ this.$menuButton.addEventListener('click', () => this.handleMenuButtonClick());
1422
1490
  }
1423
- var inputId = this.getFragmentFromUrl($target.href);
1424
- if (!inputId) {
1425
- return false;
1491
+ syncState() {
1492
+ if (!this.mql || !this.$menu || !this.$menuButton) {
1493
+ return;
1494
+ }
1495
+ if (this.mql.matches) {
1496
+ this.$menu.removeAttribute('hidden');
1497
+ this.$menuButton.setAttribute('hidden', '');
1498
+ } else {
1499
+ this.$menuButton.removeAttribute('hidden');
1500
+ this.$menuButton.setAttribute('aria-expanded', this.menuIsOpen.toString());
1501
+ if (this.menuIsOpen) {
1502
+ this.$menu.removeAttribute('hidden');
1503
+ } else {
1504
+ this.$menu.setAttribute('hidden', '');
1505
+ }
1506
+ }
1426
1507
  }
1427
- var $input = document.getElementById(inputId);
1428
- if (!$input) {
1429
- return false;
1508
+ handleMenuButtonClick() {
1509
+ this.menuIsOpen = !this.menuIsOpen;
1510
+ this.syncState();
1430
1511
  }
1431
- var $legendOrLabel = this.getAssociatedLegendOrLabel($input);
1432
- if (!$legendOrLabel) {
1433
- return false;
1512
+ }
1513
+ Header.moduleName = 'govuk-header';
1514
+
1515
+ /**
1516
+ * Notification Banner component
1517
+ *
1518
+ * @preserve
1519
+ */
1520
+ class NotificationBanner extends GOVUKFrontendComponent {
1521
+ /**
1522
+ * @param {Element | null} $module - HTML element to use for notification banner
1523
+ * @param {NotificationBannerConfig} [config] - Notification banner config
1524
+ */
1525
+ constructor($module, config = {}) {
1526
+ super();
1527
+ this.$module = void 0;
1528
+ this.config = void 0;
1529
+ if (!($module instanceof HTMLElement)) {
1530
+ throw new ElementError({
1531
+ componentName: 'Notification banner',
1532
+ element: $module,
1533
+ identifier: 'Root element (`$module`)'
1534
+ });
1535
+ }
1536
+ this.$module = $module;
1537
+ this.config = mergeConfigs(NotificationBanner.defaults, config, normaliseDataset($module.dataset));
1538
+ this.setFocus();
1434
1539
  }
1435
- $legendOrLabel.scrollIntoView();
1436
- $input.focus({
1437
- preventScroll: true
1438
- });
1439
- return true;
1440
- };
1441
-
1442
- ErrorSummary.prototype.getFragmentFromUrl = function(url) {
1443
- if (url.indexOf("#") === -1) {
1444
- return undefined;
1540
+ setFocus() {
1541
+ if (this.config.disableAutoFocus) {
1542
+ return;
1543
+ }
1544
+ if (this.$module.getAttribute('role') !== 'alert') {
1545
+ return;
1546
+ }
1547
+ if (!this.$module.getAttribute('tabindex')) {
1548
+ this.$module.setAttribute('tabindex', '-1');
1549
+ this.$module.addEventListener('blur', () => {
1550
+ this.$module.removeAttribute('tabindex');
1551
+ });
1552
+ }
1553
+ this.$module.focus();
1445
1554
  }
1446
- return url.split("#").pop();
1447
- };
1555
+ }
1448
1556
 
1449
- ErrorSummary.prototype.getAssociatedLegendOrLabel = function($input) {
1450
- var $fieldset = $input.closest("fieldset");
1451
- if ($fieldset) {
1452
- var $legends = $fieldset.getElementsByTagName("legend");
1453
- if ($legends.length) {
1454
- var $candidateLegend = $legends[0];
1455
- if ($input instanceof HTMLInputElement && ($input.type === "checkbox" || $input.type === "radio")) {
1456
- return $candidateLegend;
1557
+ /**
1558
+ * Notification banner config
1559
+ *
1560
+ * @typedef {object} NotificationBannerConfig
1561
+ * @property {boolean} [disableAutoFocus=false] - If set to `true` the
1562
+ * notification banner will not be focussed when the page loads. This only
1563
+ * applies if the component has a `role` of `alert` in other cases the
1564
+ * component will not be focused on page load, regardless of this option.
1565
+ */
1566
+ NotificationBanner.moduleName = 'govuk-notification-banner';
1567
+ NotificationBanner.defaults = Object.freeze({
1568
+ disableAutoFocus: false
1569
+ });
1570
+
1571
+ /**
1572
+ * Radios component
1573
+ *
1574
+ * @preserve
1575
+ */
1576
+ class Radios extends GOVUKFrontendComponent {
1577
+ /**
1578
+ * Radios can be associated with a 'conditionally revealed' content block –
1579
+ * for example, a radio for 'Phone' could reveal an additional form field for
1580
+ * the user to enter their phone number.
1581
+ *
1582
+ * These associations are made using a `data-aria-controls` attribute, which
1583
+ * is promoted to an aria-controls attribute during initialisation.
1584
+ *
1585
+ * We also need to restore the state of any conditional reveals on the page
1586
+ * (for example if the user has navigated back), and set up event handlers to
1587
+ * keep the reveal in sync with the radio state.
1588
+ *
1589
+ * @param {Element | null} $module - HTML element to use for radios
1590
+ */
1591
+ constructor($module) {
1592
+ super();
1593
+ this.$module = void 0;
1594
+ this.$inputs = void 0;
1595
+ if (!($module instanceof HTMLElement)) {
1596
+ throw new ElementError({
1597
+ componentName: 'Radios',
1598
+ element: $module,
1599
+ identifier: 'Root element (`$module`)'
1600
+ });
1601
+ }
1602
+ const $inputs = $module.querySelectorAll('input[type="radio"]');
1603
+ if (!$inputs.length) {
1604
+ throw new ElementError({
1605
+ componentName: 'Radios',
1606
+ identifier: 'Form inputs (`<input type="radio">`)'
1607
+ });
1608
+ }
1609
+ this.$module = $module;
1610
+ this.$inputs = $inputs;
1611
+ this.$inputs.forEach($input => {
1612
+ const targetId = $input.getAttribute('data-aria-controls');
1613
+ if (!targetId) {
1614
+ return;
1457
1615
  }
1458
- var legendTop = $candidateLegend.getBoundingClientRect().top;
1459
- var inputRect = $input.getBoundingClientRect();
1460
- if (inputRect.height && window.innerHeight) {
1461
- var inputBottom = inputRect.top + inputRect.height;
1462
- if (inputBottom - legendTop < window.innerHeight / 2) {
1463
- return $candidateLegend;
1464
- }
1616
+ if (!document.getElementById(targetId)) {
1617
+ throw new ElementError({
1618
+ componentName: 'Radios',
1619
+ identifier: `Conditional reveal (\`id="${targetId}"\`)`
1620
+ });
1465
1621
  }
1622
+ $input.setAttribute('aria-controls', targetId);
1623
+ $input.removeAttribute('data-aria-controls');
1624
+ });
1625
+ window.addEventListener('pageshow', () => this.syncAllConditionalReveals());
1626
+ this.syncAllConditionalReveals();
1627
+ this.$module.addEventListener('click', event => this.handleClick(event));
1628
+ }
1629
+ syncAllConditionalReveals() {
1630
+ this.$inputs.forEach($input => this.syncConditionalRevealWithInputState($input));
1631
+ }
1632
+ syncConditionalRevealWithInputState($input) {
1633
+ const targetId = $input.getAttribute('aria-controls');
1634
+ if (!targetId) {
1635
+ return;
1636
+ }
1637
+ const $target = document.getElementById(targetId);
1638
+ if ($target != null && $target.classList.contains('govuk-radios__conditional')) {
1639
+ const inputIsChecked = $input.checked;
1640
+ $input.setAttribute('aria-expanded', inputIsChecked.toString());
1641
+ $target.classList.toggle('govuk-radios__conditional--hidden', !inputIsChecked);
1466
1642
  }
1467
1643
  }
1468
- return document.querySelector("label[for='" + $input.getAttribute("id") + "']") || $input.closest("label");
1469
- };
1470
-
1471
- function Radios($module) {
1472
- if (!($module instanceof HTMLElement)) {
1473
- return this;
1644
+ handleClick(event) {
1645
+ const $clickedInput = event.target;
1646
+ if (!($clickedInput instanceof HTMLInputElement) || $clickedInput.type !== 'radio') {
1647
+ return;
1648
+ }
1649
+ const $allInputs = document.querySelectorAll('input[type="radio"][aria-controls]');
1650
+ const $clickedInputForm = $clickedInput.form;
1651
+ const $clickedInputName = $clickedInput.name;
1652
+ $allInputs.forEach($input => {
1653
+ const hasSameFormOwner = $input.form === $clickedInputForm;
1654
+ const hasSameName = $input.name === $clickedInputName;
1655
+ if (hasSameName && hasSameFormOwner) {
1656
+ this.syncConditionalRevealWithInputState($input);
1657
+ }
1658
+ });
1659
+ }
1660
+ }
1661
+ Radios.moduleName = 'govuk-radios';
1662
+
1663
+ /**
1664
+ * Skip link component
1665
+ *
1666
+ * @preserve
1667
+ */
1668
+ class SkipLink extends GOVUKFrontendComponent {
1669
+ /**
1670
+ * @param {Element | null} $module - HTML element to use for skip link
1671
+ * @throws {ElementError} when $module is not set or the wrong type
1672
+ * @throws {ElementError} when $module.hash does not contain a hash
1673
+ * @throws {ElementError} when the linked element is missing or the wrong type
1674
+ */
1675
+ constructor($module) {
1676
+ var _this$$module$getAttr;
1677
+ super();
1678
+ this.$module = void 0;
1679
+ this.$linkedElement = void 0;
1680
+ this.linkedElementListener = false;
1681
+ if (!($module instanceof HTMLAnchorElement)) {
1682
+ throw new ElementError({
1683
+ componentName: 'Skip link',
1684
+ element: $module,
1685
+ expectedType: 'HTMLAnchorElement',
1686
+ identifier: 'Root element (`$module`)'
1687
+ });
1688
+ }
1689
+ this.$module = $module;
1690
+ const hash = this.$module.hash;
1691
+ const href = (_this$$module$getAttr = this.$module.getAttribute('href')) != null ? _this$$module$getAttr : '';
1692
+ let url;
1693
+ try {
1694
+ url = new window.URL(this.$module.href);
1695
+ } catch (error) {
1696
+ throw new ElementError(`Skip link: Target link (\`href="${href}"\`) is invalid`);
1697
+ }
1698
+ if (url.origin !== window.location.origin || url.pathname !== window.location.pathname) {
1699
+ return;
1700
+ }
1701
+ const linkedElementId = getFragmentFromUrl(hash);
1702
+ if (!linkedElementId) {
1703
+ throw new ElementError(`Skip link: Target link (\`href="${href}"\`) has no hash fragment`);
1704
+ }
1705
+ const $linkedElement = document.getElementById(linkedElementId);
1706
+ if (!$linkedElement) {
1707
+ throw new ElementError({
1708
+ componentName: 'Skip link',
1709
+ element: $linkedElement,
1710
+ identifier: `Target content (\`id="${linkedElementId}"\`)`
1711
+ });
1712
+ }
1713
+ this.$linkedElement = $linkedElement;
1714
+ this.$module.addEventListener('click', () => this.focusLinkedElement());
1474
1715
  }
1475
- var $inputs = $module.querySelectorAll('input[type="radio"]');
1476
- if (!$inputs.length) {
1477
- return this;
1716
+ focusLinkedElement() {
1717
+ if (!this.$linkedElement) {
1718
+ return;
1719
+ }
1720
+ if (!this.$linkedElement.getAttribute('tabindex')) {
1721
+ this.$linkedElement.setAttribute('tabindex', '-1');
1722
+ this.$linkedElement.classList.add('govuk-skip-link-focused-element');
1723
+ if (!this.linkedElementListener) {
1724
+ this.$linkedElement.addEventListener('blur', () => this.removeFocusProperties());
1725
+ this.linkedElementListener = true;
1726
+ }
1727
+ }
1728
+ this.$linkedElement.focus();
1729
+ }
1730
+ removeFocusProperties() {
1731
+ if (!this.$linkedElement) {
1732
+ return;
1733
+ }
1734
+ this.$linkedElement.removeAttribute('tabindex');
1735
+ this.$linkedElement.classList.remove('govuk-skip-link-focused-element');
1478
1736
  }
1479
- this.$module = $module;
1480
- this.$inputs = $inputs;
1481
1737
  }
1482
-
1483
- Radios.prototype.init = function() {
1484
- if (!this.$module || !this.$inputs) {
1485
- return;
1738
+ SkipLink.moduleName = 'govuk-skip-link';
1739
+
1740
+ /**
1741
+ * Tabs component
1742
+ *
1743
+ * @preserve
1744
+ */
1745
+ class Tabs extends GOVUKFrontendComponent {
1746
+ /**
1747
+ * @param {Element | null} $module - HTML element to use for tabs
1748
+ */
1749
+ constructor($module) {
1750
+ super();
1751
+ this.$module = void 0;
1752
+ this.$tabs = void 0;
1753
+ this.$tabList = void 0;
1754
+ this.$tabListItems = void 0;
1755
+ this.keys = {
1756
+ left: 37,
1757
+ right: 39,
1758
+ up: 38,
1759
+ down: 40
1760
+ };
1761
+ this.jsHiddenClass = 'govuk-tabs__panel--hidden';
1762
+ this.changingHash = false;
1763
+ this.boundTabClick = void 0;
1764
+ this.boundTabKeydown = void 0;
1765
+ this.boundOnHashChange = void 0;
1766
+ this.mql = null;
1767
+ if (!$module) {
1768
+ throw new ElementError({
1769
+ componentName: 'Tabs',
1770
+ element: $module,
1771
+ identifier: 'Root element (`$module`)'
1772
+ });
1773
+ }
1774
+ const $tabs = $module.querySelectorAll('a.govuk-tabs__tab');
1775
+ if (!$tabs.length) {
1776
+ throw new ElementError({
1777
+ componentName: 'Tabs',
1778
+ identifier: 'Links (`<a class="govuk-tabs__tab">`)'
1779
+ });
1780
+ }
1781
+ this.$module = $module;
1782
+ this.$tabs = $tabs;
1783
+ this.boundTabClick = this.onTabClick.bind(this);
1784
+ this.boundTabKeydown = this.onTabKeydown.bind(this);
1785
+ this.boundOnHashChange = this.onHashChange.bind(this);
1786
+ const $tabList = this.$module.querySelector('.govuk-tabs__list');
1787
+ const $tabListItems = this.$module.querySelectorAll('li.govuk-tabs__list-item');
1788
+ if (!$tabList) {
1789
+ throw new ElementError({
1790
+ componentName: 'Tabs',
1791
+ identifier: 'List (`<ul class="govuk-tabs__list">`)'
1792
+ });
1793
+ }
1794
+ if (!$tabListItems.length) {
1795
+ throw new ElementError({
1796
+ componentName: 'Tabs',
1797
+ identifier: 'List items (`<li class="govuk-tabs__list-item">`)'
1798
+ });
1799
+ }
1800
+ this.$tabList = $tabList;
1801
+ this.$tabListItems = $tabListItems;
1802
+ this.setupResponsiveChecks();
1803
+ }
1804
+ setupResponsiveChecks() {
1805
+ this.mql = window.matchMedia('(min-width: 40.0625em)');
1806
+ if ('addEventListener' in this.mql) {
1807
+ this.mql.addEventListener('change', () => this.checkMode());
1808
+ } else {
1809
+ this.mql.addListener(() => this.checkMode());
1810
+ }
1811
+ this.checkMode();
1812
+ }
1813
+ checkMode() {
1814
+ var _this$mql;
1815
+ if ((_this$mql = this.mql) != null && _this$mql.matches) {
1816
+ this.setup();
1817
+ } else {
1818
+ this.teardown();
1819
+ }
1820
+ }
1821
+ setup() {
1822
+ var _this$getTab;
1823
+ this.$tabList.setAttribute('role', 'tablist');
1824
+ this.$tabListItems.forEach($item => {
1825
+ $item.setAttribute('role', 'presentation');
1826
+ });
1827
+ this.$tabs.forEach($tab => {
1828
+ this.setAttributes($tab);
1829
+ $tab.addEventListener('click', this.boundTabClick, true);
1830
+ $tab.addEventListener('keydown', this.boundTabKeydown, true);
1831
+ this.hideTab($tab);
1832
+ });
1833
+ const $activeTab = (_this$getTab = this.getTab(window.location.hash)) != null ? _this$getTab : this.$tabs[0];
1834
+ this.showTab($activeTab);
1835
+ window.addEventListener('hashchange', this.boundOnHashChange, true);
1836
+ }
1837
+ teardown() {
1838
+ this.$tabList.removeAttribute('role');
1839
+ this.$tabListItems.forEach($item => {
1840
+ $item.removeAttribute('role');
1841
+ });
1842
+ this.$tabs.forEach($tab => {
1843
+ $tab.removeEventListener('click', this.boundTabClick, true);
1844
+ $tab.removeEventListener('keydown', this.boundTabKeydown, true);
1845
+ this.unsetAttributes($tab);
1846
+ });
1847
+ window.removeEventListener('hashchange', this.boundOnHashChange, true);
1486
1848
  }
1487
- var $module = this.$module;
1488
- var $inputs = this.$inputs;
1489
- nodeListForEach($inputs, (function($input) {
1490
- var targetId = $input.getAttribute("data-aria-controls");
1491
- if (!targetId || !document.getElementById(targetId)) {
1849
+ onHashChange() {
1850
+ const hash = window.location.hash;
1851
+ const $tabWithHash = this.getTab(hash);
1852
+ if (!$tabWithHash) {
1492
1853
  return;
1493
1854
  }
1494
- $input.setAttribute("aria-controls", targetId);
1495
- $input.removeAttribute("data-aria-controls");
1496
- }));
1497
- window.addEventListener("onpageshow" in window ? "pageshow" : "DOMContentLoaded", this.syncAllConditionalReveals.bind(this));
1498
- this.syncAllConditionalReveals();
1499
- $module.addEventListener("click", this.handleClick.bind(this));
1500
- };
1501
-
1502
- Radios.prototype.syncAllConditionalReveals = function() {
1503
- nodeListForEach(this.$inputs, this.syncConditionalRevealWithInputState.bind(this));
1504
- };
1505
-
1506
- Radios.prototype.syncConditionalRevealWithInputState = function($input) {
1507
- var targetId = $input.getAttribute("aria-controls");
1508
- if (!targetId) {
1509
- return;
1855
+ if (this.changingHash) {
1856
+ this.changingHash = false;
1857
+ return;
1858
+ }
1859
+ const $previousTab = this.getCurrentTab();
1860
+ if (!$previousTab) {
1861
+ return;
1862
+ }
1863
+ this.hideTab($previousTab);
1864
+ this.showTab($tabWithHash);
1865
+ $tabWithHash.focus();
1510
1866
  }
1511
- var $target = document.getElementById(targetId);
1512
- if ($target && $target.classList.contains("govuk-radios__conditional")) {
1513
- var inputIsChecked = $input.checked;
1514
- $input.setAttribute("aria-expanded", inputIsChecked.toString());
1515
- $target.classList.toggle("govuk-radios__conditional--hidden", !inputIsChecked);
1867
+ hideTab($tab) {
1868
+ this.unhighlightTab($tab);
1869
+ this.hidePanel($tab);
1516
1870
  }
1517
- };
1518
-
1519
- Radios.prototype.handleClick = function(event) {
1520
- var $component = this;
1521
- var $clickedInput = event.target;
1522
- if (!($clickedInput instanceof HTMLInputElement) || $clickedInput.type !== "radio") {
1523
- return;
1871
+ showTab($tab) {
1872
+ this.highlightTab($tab);
1873
+ this.showPanel($tab);
1874
+ }
1875
+ getTab(hash) {
1876
+ return this.$module.querySelector(`a.govuk-tabs__tab[href="${hash}"]`);
1524
1877
  }
1525
- var $allInputs = document.querySelectorAll('input[type="radio"][aria-controls]');
1526
- var $clickedInputForm = $clickedInput.form;
1527
- var $clickedInputName = $clickedInput.name;
1528
- nodeListForEach($allInputs, (function($input) {
1529
- var hasSameFormOwner = $input.form === $clickedInputForm;
1530
- var hasSameName = $input.name === $clickedInputName;
1531
- if (hasSameName && hasSameFormOwner) {
1532
- $component.syncConditionalRevealWithInputState($input);
1878
+ setAttributes($tab) {
1879
+ const panelId = getFragmentFromUrl($tab.href);
1880
+ if (!panelId) {
1881
+ return;
1533
1882
  }
1534
- }));
1535
- };
1883
+ $tab.setAttribute('id', `tab_${panelId}`);
1884
+ $tab.setAttribute('role', 'tab');
1885
+ $tab.setAttribute('aria-controls', panelId);
1886
+ $tab.setAttribute('aria-selected', 'false');
1887
+ $tab.setAttribute('tabindex', '-1');
1888
+ const $panel = this.getPanel($tab);
1889
+ if (!$panel) {
1890
+ return;
1891
+ }
1892
+ $panel.setAttribute('role', 'tabpanel');
1893
+ $panel.setAttribute('aria-labelledby', $tab.id);
1894
+ $panel.classList.add(this.jsHiddenClass);
1895
+ }
1896
+ unsetAttributes($tab) {
1897
+ $tab.removeAttribute('id');
1898
+ $tab.removeAttribute('role');
1899
+ $tab.removeAttribute('aria-controls');
1900
+ $tab.removeAttribute('aria-selected');
1901
+ $tab.removeAttribute('tabindex');
1902
+ const $panel = this.getPanel($tab);
1903
+ if (!$panel) {
1904
+ return;
1905
+ }
1906
+ $panel.removeAttribute('role');
1907
+ $panel.removeAttribute('aria-labelledby');
1908
+ $panel.classList.remove(this.jsHiddenClass);
1909
+ }
1910
+ onTabClick(event) {
1911
+ const $currentTab = this.getCurrentTab();
1912
+ const $nextTab = event.currentTarget;
1913
+ if (!$currentTab || !($nextTab instanceof HTMLAnchorElement)) {
1914
+ return;
1915
+ }
1916
+ event.preventDefault();
1917
+ this.hideTab($currentTab);
1918
+ this.showTab($nextTab);
1919
+ this.createHistoryEntry($nextTab);
1920
+ }
1921
+ createHistoryEntry($tab) {
1922
+ const $panel = this.getPanel($tab);
1923
+ if (!$panel) {
1924
+ return;
1925
+ }
1926
+ const panelId = $panel.id;
1927
+ $panel.id = '';
1928
+ this.changingHash = true;
1929
+ window.location.hash = panelId;
1930
+ $panel.id = panelId;
1931
+ }
1932
+ onTabKeydown(event) {
1933
+ switch (event.keyCode) {
1934
+ case this.keys.left:
1935
+ case this.keys.up:
1936
+ this.activatePreviousTab();
1937
+ event.preventDefault();
1938
+ break;
1939
+ case this.keys.right:
1940
+ case this.keys.down:
1941
+ this.activateNextTab();
1942
+ event.preventDefault();
1943
+ break;
1944
+ }
1945
+ }
1946
+ activateNextTab() {
1947
+ const $currentTab = this.getCurrentTab();
1948
+ if (!($currentTab != null && $currentTab.parentElement)) {
1949
+ return;
1950
+ }
1951
+ const $nextTabListItem = $currentTab.parentElement.nextElementSibling;
1952
+ if (!$nextTabListItem) {
1953
+ return;
1954
+ }
1955
+ const $nextTab = $nextTabListItem.querySelector('a.govuk-tabs__tab');
1956
+ if (!$nextTab) {
1957
+ return;
1958
+ }
1959
+ this.hideTab($currentTab);
1960
+ this.showTab($nextTab);
1961
+ $nextTab.focus();
1962
+ this.createHistoryEntry($nextTab);
1963
+ }
1964
+ activatePreviousTab() {
1965
+ const $currentTab = this.getCurrentTab();
1966
+ if (!($currentTab != null && $currentTab.parentElement)) {
1967
+ return;
1968
+ }
1969
+ const $previousTabListItem = $currentTab.parentElement.previousElementSibling;
1970
+ if (!$previousTabListItem) {
1971
+ return;
1972
+ }
1973
+ const $previousTab = $previousTabListItem.querySelector('a.govuk-tabs__tab');
1974
+ if (!$previousTab) {
1975
+ return;
1976
+ }
1977
+ this.hideTab($currentTab);
1978
+ this.showTab($previousTab);
1979
+ $previousTab.focus();
1980
+ this.createHistoryEntry($previousTab);
1981
+ }
1982
+ getPanel($tab) {
1983
+ const panelId = getFragmentFromUrl($tab.href);
1984
+ if (!panelId) {
1985
+ return null;
1986
+ }
1987
+ return this.$module.querySelector(`#${panelId}`);
1988
+ }
1989
+ showPanel($tab) {
1990
+ const $panel = this.getPanel($tab);
1991
+ if (!$panel) {
1992
+ return;
1993
+ }
1994
+ $panel.classList.remove(this.jsHiddenClass);
1995
+ }
1996
+ hidePanel($tab) {
1997
+ const $panel = this.getPanel($tab);
1998
+ if (!$panel) {
1999
+ return;
2000
+ }
2001
+ $panel.classList.add(this.jsHiddenClass);
2002
+ }
2003
+ unhighlightTab($tab) {
2004
+ if (!$tab.parentElement) {
2005
+ return;
2006
+ }
2007
+ $tab.setAttribute('aria-selected', 'false');
2008
+ $tab.parentElement.classList.remove('govuk-tabs__list-item--selected');
2009
+ $tab.setAttribute('tabindex', '-1');
2010
+ }
2011
+ highlightTab($tab) {
2012
+ if (!$tab.parentElement) {
2013
+ return;
2014
+ }
2015
+ $tab.setAttribute('aria-selected', 'true');
2016
+ $tab.parentElement.classList.add('govuk-tabs__list-item--selected');
2017
+ $tab.setAttribute('tabindex', '0');
2018
+ }
2019
+ getCurrentTab() {
2020
+ return this.$module.querySelector('.govuk-tabs__list-item--selected a.govuk-tabs__tab');
2021
+ }
2022
+ }
2023
+ Tabs.moduleName = 'govuk-tabs';
1536
2024
 
1537
- function initAll(options) {
1538
- options = typeof options !== "undefined" ? options : {};
1539
- const scope = typeof options.scope !== "undefined" ? options.scope : document;
1540
- const $buttons = scope.querySelectorAll('[data-module="govuk-button"]');
1541
- nodeListForEach($buttons, (function($button) {
1542
- new Button($button).init();
1543
- }));
1544
- const $characterCounts = scope.querySelectorAll('[data-module="govuk-character-count"]');
1545
- nodeListForEach($characterCounts, (function($characterCount) {
1546
- new CharacterCount($characterCount).init();
1547
- }));
1548
- const $checkboxes = scope.querySelectorAll('[data-module="govuk-checkboxes"]');
1549
- nodeListForEach($checkboxes, (function($checkbox) {
1550
- new Checkboxes($checkbox).init();
1551
- }));
1552
- const $errorSummary = scope.querySelector('[data-module="govuk-error-summary"]');
1553
- new ErrorSummary($errorSummary).init();
1554
- const $radios = scope.querySelectorAll('[data-module="govuk-radios"]');
1555
- nodeListForEach($radios, (function($radio) {
1556
- new Radios($radio).init();
1557
- }));
2025
+ function initAll(config) {
2026
+ let _config$scope;
2027
+ config = typeof config !== 'undefined' ? config : {};
2028
+ if (!isSupported()) {
2029
+ console.log(new SupportError());
2030
+ return;
2031
+ }
2032
+ const components = [[Button, config.button], [CharacterCount, config.characterCount], [Checkboxes], [ErrorSummary, config.errorSummary], [Radios]];
2033
+ const $scope = (_config$scope = config.scope) != null ? _config$scope : document;
2034
+ components.forEach(([Component, config]) => {
2035
+ const $elements = $scope.querySelectorAll(`[data-module="${Component.moduleName}"]`);
2036
+ $elements.forEach($element => {
2037
+ try {
2038
+ 'defaults' in Component ? new Component($element, config) : new Component($element);
2039
+ } catch (error) {
2040
+ console.log(error);
2041
+ }
2042
+ });
2043
+ });
1558
2044
  }
1559
2045
 
1560
2046
  export { Button, CharacterCount, Checkboxes, ErrorSummary, Radios, initAll };