govuk_publishing_components 32.0.0 → 33.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (206) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/component_guide/accessibility-test.js +0 -1
  3. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-core.js +175 -0
  4. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-ecommerce-tracker.js +1 -1
  5. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-event-tracker.js +5 -13
  6. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-link-tracker.js +80 -309
  7. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-page-views.js +2 -2
  8. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-specialist-link-tracker.js +140 -0
  9. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/init-ga4.js +3 -0
  10. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4.js +1 -0
  11. data/app/assets/javascripts/govuk_publishing_components/components/accordion.js +12 -1
  12. data/app/assets/javascripts/govuk_publishing_components/components/layout-super-navigation-header.js +13 -4
  13. data/app/assets/javascripts/govuk_publishing_components/components/single-page-notification-button.js +24 -8
  14. data/app/assets/javascripts/govuk_publishing_components/vendor/lux/lux-reporter.js +83 -86
  15. data/app/assets/stylesheets/govuk_publishing_components/components/_big-number.scss +2 -5
  16. data/app/assets/stylesheets/govuk_publishing_components/components/_image-card.scss +1 -5
  17. data/app/assets/stylesheets/govuk_publishing_components/components/_input.scss +3 -5
  18. data/app/assets/stylesheets/govuk_publishing_components/components/_layout-super-navigation-header.scss +10 -30
  19. data/app/assets/stylesheets/govuk_publishing_components/components/_search.scss +0 -7
  20. data/app/assets/stylesheets/govuk_publishing_components/components/_share-links.scss +0 -6
  21. data/app/views/govuk_publishing_components/components/_accordion.html.erb +14 -1
  22. data/app/views/govuk_publishing_components/components/_error_summary.html.erb +27 -26
  23. data/app/views/govuk_publishing_components/components/_layout_super_navigation_header.html.erb +2 -2
  24. data/app/views/govuk_publishing_components/components/_phase_banner.html.erb +1 -1
  25. data/app/views/govuk_publishing_components/components/_share_links.html.erb +18 -15
  26. data/app/views/govuk_publishing_components/components/_single_page_notification_button.html.erb +1 -1
  27. data/app/views/govuk_publishing_components/components/docs/accordion.yml +15 -3
  28. data/app/views/govuk_publishing_components/components/docs/button.yml +10 -0
  29. data/app/views/govuk_publishing_components/components/docs/share_links.yml +59 -30
  30. data/app/views/govuk_publishing_components/components/docs/single_page_notification_button.yml +10 -1
  31. data/app/views/govuk_publishing_components/components/feedback/_yes_no_banner.html.erb +3 -3
  32. data/config/locales/ar.yml +4 -1
  33. data/config/locales/az.yml +4 -1
  34. data/config/locales/be.yml +4 -1
  35. data/config/locales/bg.yml +4 -1
  36. data/config/locales/bn.yml +4 -1
  37. data/config/locales/cs.yml +4 -1
  38. data/config/locales/cy.yml +4 -1
  39. data/config/locales/da.yml +4 -1
  40. data/config/locales/de.yml +4 -1
  41. data/config/locales/dr.yml +4 -1
  42. data/config/locales/el.yml +4 -1
  43. data/config/locales/en.yml +20 -17
  44. data/config/locales/es-419.yml +4 -1
  45. data/config/locales/es.yml +4 -1
  46. data/config/locales/et.yml +4 -1
  47. data/config/locales/fa.yml +4 -1
  48. data/config/locales/fi.yml +4 -1
  49. data/config/locales/fr.yml +4 -1
  50. data/config/locales/gd.yml +4 -1
  51. data/config/locales/gu.yml +4 -1
  52. data/config/locales/he.yml +4 -1
  53. data/config/locales/hi.yml +4 -1
  54. data/config/locales/hr.yml +4 -1
  55. data/config/locales/hu.yml +4 -1
  56. data/config/locales/hy.yml +4 -1
  57. data/config/locales/id.yml +4 -1
  58. data/config/locales/is.yml +4 -1
  59. data/config/locales/it.yml +4 -1
  60. data/config/locales/ja.yml +4 -1
  61. data/config/locales/ka.yml +4 -1
  62. data/config/locales/kk.yml +4 -1
  63. data/config/locales/ko.yml +4 -1
  64. data/config/locales/lt.yml +4 -1
  65. data/config/locales/lv.yml +4 -1
  66. data/config/locales/ms.yml +4 -1
  67. data/config/locales/mt.yml +4 -1
  68. data/config/locales/nl.yml +4 -1
  69. data/config/locales/no.yml +4 -1
  70. data/config/locales/pa-pk.yml +4 -1
  71. data/config/locales/pa.yml +4 -1
  72. data/config/locales/pl.yml +4 -1
  73. data/config/locales/ps.yml +4 -1
  74. data/config/locales/pt.yml +4 -1
  75. data/config/locales/ro.yml +4 -1
  76. data/config/locales/ru.yml +4 -1
  77. data/config/locales/si.yml +4 -1
  78. data/config/locales/sk.yml +4 -1
  79. data/config/locales/sl.yml +4 -1
  80. data/config/locales/so.yml +4 -1
  81. data/config/locales/sq.yml +4 -1
  82. data/config/locales/sr.yml +4 -1
  83. data/config/locales/sv.yml +4 -1
  84. data/config/locales/sw.yml +4 -1
  85. data/config/locales/ta.yml +4 -1
  86. data/config/locales/th.yml +4 -1
  87. data/config/locales/tk.yml +4 -1
  88. data/config/locales/tr.yml +4 -1
  89. data/config/locales/uk.yml +4 -1
  90. data/config/locales/ur.yml +4 -1
  91. data/config/locales/uz.yml +4 -1
  92. data/config/locales/vi.yml +4 -1
  93. data/config/locales/zh-hk.yml +4 -1
  94. data/config/locales/zh-tw.yml +4 -1
  95. data/config/locales/zh.yml +4 -1
  96. data/lib/govuk_publishing_components/presenters/button_helper.rb +7 -1
  97. data/lib/govuk_publishing_components/presenters/single_page_notification_button_helper.rb +25 -1
  98. data/lib/govuk_publishing_components/version.rb +1 -1
  99. data/node_modules/axe-core/axe.js +4567 -4678
  100. data/node_modules/axe-core/axe.min.js +2 -2
  101. data/node_modules/axe-core/package.json +2 -2
  102. data/node_modules/axe-core/sri-history.json +8 -0
  103. data/node_modules/govuk-frontend/README.md +1 -2
  104. data/node_modules/govuk-frontend/govuk/all.js +1398 -273
  105. data/node_modules/govuk-frontend/govuk/common/closest-attribute-value.js +70 -0
  106. data/node_modules/govuk-frontend/govuk/common/index.js +172 -0
  107. data/node_modules/govuk-frontend/govuk/common/normalise-dataset.js +373 -0
  108. data/node_modules/govuk-frontend/govuk/common.js +138 -3
  109. data/node_modules/govuk-frontend/govuk/components/accordion/accordion.js +753 -25
  110. data/node_modules/govuk-frontend/govuk/components/accordion/fixtures.json +54 -22
  111. data/node_modules/govuk-frontend/govuk/components/accordion/macro-options.json +36 -0
  112. data/node_modules/govuk-frontend/govuk/components/accordion/template.njk +7 -1
  113. data/node_modules/govuk-frontend/govuk/components/back-link/fixtures.json +12 -12
  114. data/node_modules/govuk-frontend/govuk/components/breadcrumbs/fixtures.json +22 -22
  115. data/node_modules/govuk-frontend/govuk/components/button/_index.scss +23 -5
  116. data/node_modules/govuk-frontend/govuk/components/button/button.js +365 -107
  117. data/node_modules/govuk-frontend/govuk/components/button/fixtures.json +85 -66
  118. data/node_modules/govuk-frontend/govuk/components/button/template.njk +1 -1
  119. data/node_modules/govuk-frontend/govuk/components/character-count/_index.scss +9 -0
  120. data/node_modules/govuk-frontend/govuk/components/character-count/character-count.js +1033 -121
  121. data/node_modules/govuk-frontend/govuk/components/character-count/fixtures.json +112 -36
  122. data/node_modules/govuk-frontend/govuk/components/character-count/macro-options.json +42 -0
  123. data/node_modules/govuk-frontend/govuk/components/character-count/template.njk +27 -3
  124. data/node_modules/govuk-frontend/govuk/components/checkboxes/checkboxes.js +30 -2
  125. data/node_modules/govuk-frontend/govuk/components/checkboxes/fixtures.json +96 -93
  126. data/node_modules/govuk-frontend/govuk/components/cookie-banner/fixtures.json +46 -46
  127. data/node_modules/govuk-frontend/govuk/components/date-input/fixtures.json +50 -50
  128. data/node_modules/govuk-frontend/govuk/components/details/details.js +43 -13
  129. data/node_modules/govuk-frontend/govuk/components/details/fixtures.json +20 -20
  130. data/node_modules/govuk-frontend/govuk/components/error-message/fixtures.json +20 -20
  131. data/node_modules/govuk-frontend/govuk/components/error-summary/error-summary.js +268 -6
  132. data/node_modules/govuk-frontend/govuk/components/error-summary/fixtures.json +44 -35
  133. data/node_modules/govuk-frontend/govuk/components/error-summary/template.njk +25 -21
  134. data/node_modules/govuk-frontend/govuk/components/fieldset/fixtures.json +51 -39
  135. data/node_modules/govuk-frontend/govuk/components/file-upload/fixtures.json +26 -26
  136. data/node_modules/govuk-frontend/govuk/components/footer/_index.scss +1 -1
  137. data/node_modules/govuk-frontend/govuk/components/footer/fixtures.json +46 -46
  138. data/node_modules/govuk-frontend/govuk/components/footer/macro-options.json +2 -2
  139. data/node_modules/govuk-frontend/govuk/components/header/fixtures.json +93 -38
  140. data/node_modules/govuk-frontend/govuk/components/header/header.js +6 -0
  141. data/node_modules/govuk-frontend/govuk/components/header/macro-options.json +8 -2
  142. data/node_modules/govuk-frontend/govuk/components/header/template.njk +4 -2
  143. data/node_modules/govuk-frontend/govuk/components/hint/fixtures.json +12 -12
  144. data/node_modules/govuk-frontend/govuk/components/input/fixtures.json +80 -80
  145. data/node_modules/govuk-frontend/govuk/components/inset-text/fixtures.json +12 -12
  146. data/node_modules/govuk-frontend/govuk/components/label/fixtures.json +34 -34
  147. data/node_modules/govuk-frontend/govuk/components/notification-banner/fixtures.json +56 -46
  148. data/node_modules/govuk-frontend/govuk/components/notification-banner/notification-banner.js +252 -2
  149. data/node_modules/govuk-frontend/govuk/components/notification-banner/template.njk +1 -1
  150. data/node_modules/govuk-frontend/govuk/components/pagination/_index.scss +10 -7
  151. data/node_modules/govuk-frontend/govuk/components/pagination/fixtures.json +33 -26
  152. data/node_modules/govuk-frontend/govuk/components/panel/fixtures.json +18 -18
  153. data/node_modules/govuk-frontend/govuk/components/phase-banner/fixtures.json +14 -14
  154. data/node_modules/govuk-frontend/govuk/components/radios/fixtures.json +94 -91
  155. data/node_modules/govuk-frontend/govuk/components/radios/radios.js +30 -2
  156. data/node_modules/govuk-frontend/govuk/components/select/fixtures.json +32 -32
  157. data/node_modules/govuk-frontend/govuk/components/skip-link/fixtures.json +22 -20
  158. data/node_modules/govuk-frontend/govuk/components/skip-link/skip-link.js +10 -4
  159. data/node_modules/govuk-frontend/govuk/components/summary-list/fixtures.json +50 -50
  160. data/node_modules/govuk-frontend/govuk/components/table/_index.scss +1 -1
  161. data/node_modules/govuk-frontend/govuk/components/table/fixtures.json +40 -40
  162. data/node_modules/govuk-frontend/govuk/components/tabs/fixtures.json +29 -29
  163. data/node_modules/govuk-frontend/govuk/components/tabs/tabs.js +28 -0
  164. data/node_modules/govuk-frontend/govuk/components/tag/fixtures.json +28 -28
  165. data/node_modules/govuk-frontend/govuk/components/textarea/fixtures.json +34 -34
  166. data/node_modules/govuk-frontend/govuk/components/warning-text/fixtures.json +14 -14
  167. data/node_modules/govuk-frontend/govuk/core/_section-break.scss +1 -1
  168. data/node_modules/govuk-frontend/govuk/helpers/_colour.scss +2 -2
  169. data/node_modules/govuk-frontend/govuk/helpers/_links.scss +6 -6
  170. data/node_modules/govuk-frontend/govuk/i18n.js +390 -0
  171. data/node_modules/govuk-frontend/govuk/macros/i18n.njk +15 -0
  172. data/node_modules/govuk-frontend/govuk/settings/_all.scss +1 -0
  173. data/node_modules/govuk-frontend/govuk/settings/_colours-palette.scss +12 -0
  174. data/node_modules/govuk-frontend/govuk/settings/_compatibility.scss +26 -0
  175. data/node_modules/govuk-frontend/govuk/settings/_typography-font.scss +23 -0
  176. data/node_modules/govuk-frontend/govuk/settings/_typography-responsive.scss +12 -0
  177. data/node_modules/govuk-frontend/govuk/settings/_warnings.scss +53 -0
  178. data/node_modules/govuk-frontend/govuk/tools/_compatibility.scss +20 -6
  179. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Date/now.js +21 -0
  180. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Element/prototype/dataset.js +300 -0
  181. data/node_modules/govuk-frontend/govuk/vendor/polyfills/String/prototype/trim.js +21 -0
  182. data/node_modules/govuk-frontend/govuk-esm/all.mjs +50 -27
  183. data/node_modules/govuk-frontend/govuk-esm/common/closest-attribute-value.mjs +15 -0
  184. data/node_modules/govuk-frontend/govuk-esm/common/index.mjs +159 -0
  185. data/node_modules/govuk-frontend/govuk-esm/common/normalise-dataset.mjs +58 -0
  186. data/node_modules/govuk-frontend/govuk-esm/common.mjs +6 -28
  187. data/node_modules/govuk-frontend/govuk-esm/components/accordion/accordion.mjs +113 -43
  188. data/node_modules/govuk-frontend/govuk-esm/components/button/button.mjs +67 -30
  189. data/node_modules/govuk-frontend/govuk-esm/components/character-count/character-count.mjs +325 -123
  190. data/node_modules/govuk-frontend/govuk-esm/components/checkboxes/checkboxes.mjs +9 -3
  191. data/node_modules/govuk-frontend/govuk-esm/components/details/details.mjs +22 -8
  192. data/node_modules/govuk-frontend/govuk-esm/components/error-summary/error-summary.mjs +48 -6
  193. data/node_modules/govuk-frontend/govuk-esm/components/header/header.mjs +6 -0
  194. data/node_modules/govuk-frontend/govuk-esm/components/notification-banner/notification-banner.mjs +32 -2
  195. data/node_modules/govuk-frontend/govuk-esm/components/radios/radios.mjs +9 -3
  196. data/node_modules/govuk-frontend/govuk-esm/components/skip-link/skip-link.mjs +10 -4
  197. data/node_modules/govuk-frontend/govuk-esm/components/tabs/tabs.mjs +8 -2
  198. data/node_modules/govuk-frontend/govuk-esm/i18n.mjs +380 -0
  199. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Date/now.mjs +13 -0
  200. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Element/prototype/dataset.mjs +68 -0
  201. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/String/prototype/trim.mjs +13 -0
  202. data/node_modules/govuk-frontend/govuk-prototype-kit/init.js +7 -0
  203. data/node_modules/govuk-frontend/govuk-prototype-kit/init.scss +12 -0
  204. data/node_modules/govuk-frontend/govuk-prototype-kit.config.json +138 -7
  205. data/node_modules/govuk-frontend/package.json +1 -1
  206. metadata +22 -3
@@ -4,10 +4,24 @@
4
4
  (global.GOVUKFrontend = global.GOVUKFrontend || {}, global.GOVUKFrontend.Accordion = factory());
5
5
  }(this, (function () { 'use strict';
6
6
 
7
+ /**
8
+ * Common helpers which do not require polyfill.
9
+ *
10
+ * IMPORTANT: If a helper require a polyfill, please isolate it in its own module
11
+ * so that the polyfill can be properly tree-shaken and does not burden
12
+ * the components that do not need that helper
13
+ *
14
+ * @module common/index
15
+ */
16
+
7
17
  /**
8
18
  * TODO: Ideally this would be a NodeList.prototype.forEach polyfill
9
19
  * This seems to fail in IE8, requires more investigation.
10
20
  * See: https://github.com/imagitama/nodelist-foreach-polyfill
21
+ *
22
+ * @param {NodeListOf<Element>} nodes - NodeList from querySelectorAll()
23
+ * @param {nodeListIterator} callback - Callback function to run for each node
24
+ * @returns {undefined}
11
25
  */
12
26
  function nodeListForEach (nodes, callback) {
13
27
  if (window.NodeList.prototype.forEach) {
@@ -18,6 +32,500 @@ function nodeListForEach (nodes, callback) {
18
32
  }
19
33
  }
20
34
 
35
+ /**
36
+ * Config flattening function
37
+ *
38
+ * Takes any number of objects, flattens them into namespaced key-value pairs,
39
+ * (e.g. {'i18n.showSection': 'Show section'}) and combines them together, with
40
+ * greatest priority on the LAST item passed in.
41
+ *
42
+ * @returns {object} A flattened object of key-value pairs.
43
+ */
44
+ function mergeConfigs (/* configObject1, configObject2, ...configObjects */) {
45
+ /**
46
+ * Function to take nested objects and flatten them to a dot-separated keyed
47
+ * object. Doing this means we don't need to do any deep/recursive merging of
48
+ * each of our objects, nor transform our dataset from a flat list into a
49
+ * nested object.
50
+ *
51
+ * @param {object} configObject - Deeply nested object
52
+ * @returns {object} Flattened object with dot-separated keys
53
+ */
54
+ var flattenObject = function (configObject) {
55
+ // Prepare an empty return object
56
+ var flattenedObject = {};
57
+
58
+ // Our flattening function, this is called recursively for each level of
59
+ // depth in the object. At each level we prepend the previous level names to
60
+ // the key using `prefix`.
61
+ var flattenLoop = function (obj, prefix) {
62
+ // Loop through keys...
63
+ for (var key in obj) {
64
+ // Check to see if this is a prototypical key/value,
65
+ // if it is, skip it.
66
+ if (!Object.prototype.hasOwnProperty.call(obj, key)) {
67
+ continue
68
+ }
69
+ var value = obj[key];
70
+ var prefixedKey = prefix ? prefix + '.' + key : key;
71
+ if (typeof value === 'object') {
72
+ // If the value is a nested object, recurse over that too
73
+ flattenLoop(value, prefixedKey);
74
+ } else {
75
+ // Otherwise, add this value to our return object
76
+ flattenedObject[prefixedKey] = value;
77
+ }
78
+ }
79
+ };
80
+
81
+ // Kick off the recursive loop
82
+ flattenLoop(configObject);
83
+ return flattenedObject
84
+ };
85
+
86
+ // Start with an empty object as our base
87
+ var formattedConfigObject = {};
88
+
89
+ // Loop through each of the remaining passed objects and push their keys
90
+ // one-by-one into configObject. Any duplicate keys will override the existing
91
+ // key with the new value.
92
+ for (var i = 0; i < arguments.length; i++) {
93
+ var obj = flattenObject(arguments[i]);
94
+ for (var key in obj) {
95
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
96
+ formattedConfigObject[key] = obj[key];
97
+ }
98
+ }
99
+ }
100
+
101
+ return formattedConfigObject
102
+ }
103
+
104
+ /**
105
+ * Extracts keys starting with a particular namespace from a flattened config
106
+ * object, removing the namespace in the process.
107
+ *
108
+ * @param {object} configObject - The object to extract key-value pairs from.
109
+ * @param {string} namespace - The namespace to filter keys with.
110
+ * @returns {object} Flattened object with dot-separated key namespace removed
111
+ */
112
+ function extractConfigByNamespace (configObject, namespace) {
113
+ // Check we have what we need
114
+ if (!configObject || typeof configObject !== 'object') {
115
+ throw new Error('Provide a `configObject` of type "object".')
116
+ }
117
+ if (!namespace || typeof namespace !== 'string') {
118
+ throw new Error('Provide a `namespace` of type "string" to filter the `configObject` by.')
119
+ }
120
+ var newObject = {};
121
+ for (var key in configObject) {
122
+ // Split the key into parts, using . as our namespace separator
123
+ var keyParts = key.split('.');
124
+ // Check if the first namespace matches the configured namespace
125
+ if (Object.prototype.hasOwnProperty.call(configObject, key) && keyParts[0] === namespace) {
126
+ // Remove the first item (the namespace) from the parts array,
127
+ // but only if there is more than one part (we don't want blank keys!)
128
+ if (keyParts.length > 1) {
129
+ keyParts.shift();
130
+ }
131
+ // Join the remaining parts back together
132
+ var newKey = keyParts.join('.');
133
+ // Add them to our new object
134
+ newObject[newKey] = configObject[key];
135
+ }
136
+ }
137
+ return newObject
138
+ }
139
+
140
+ /**
141
+ * @callback nodeListIterator
142
+ * @param {Element} value - The current node being iterated on
143
+ * @param {number} index - The current index in the iteration
144
+ * @param {NodeListOf<Element>} nodes - NodeList from querySelectorAll()
145
+ * @returns {undefined}
146
+ */
147
+
148
+ /**
149
+ * Internal support for selecting messages to render, with placeholder
150
+ * interpolation and locale-aware number formatting and pluralisation
151
+ *
152
+ * @class
153
+ * @private
154
+ * @param {TranslationsFlattened} translations - Key-value pairs of the translation strings to use.
155
+ * @param {object} [config] - Configuration options for the function.
156
+ * @param {string} config.locale - An overriding locale for the PluralRules functionality.
157
+ */
158
+ function I18n (translations, config) {
159
+ // Make list of translations available throughout function
160
+ this.translations = translations || {};
161
+
162
+ // The locale to use for PluralRules and NumberFormat
163
+ this.locale = (config && config.locale) || document.documentElement.lang || 'en';
164
+ }
165
+
166
+ /**
167
+ * The most used function - takes the key for a given piece of UI text and
168
+ * returns the appropriate string.
169
+ *
170
+ * @param {string} lookupKey - The lookup key of the string to use.
171
+ * @param {object} options - Any options passed with the translation string, e.g: for string interpolation.
172
+ * @returns {string} The appropriate translation string.
173
+ */
174
+ I18n.prototype.t = function (lookupKey, options) {
175
+ if (!lookupKey) {
176
+ // Print a console error if no lookup key has been provided
177
+ throw new Error('i18n: lookup key missing')
178
+ }
179
+
180
+ // If the `count` option is set, determine which plural suffix is needed and
181
+ // change the lookupKey to match. We check to see if it's undefined instead of
182
+ // falsy, as this could legitimately be 0.
183
+ if (options && typeof options.count !== 'undefined') {
184
+ // Get the plural suffix
185
+ lookupKey = lookupKey + '.' + this.getPluralSuffix(lookupKey, options.count);
186
+ }
187
+
188
+ if (lookupKey in this.translations) {
189
+ // Fetch the translation string for that lookup key
190
+ var translationString = this.translations[lookupKey];
191
+
192
+ // Check for ${} placeholders in the translation string
193
+ if (translationString.match(/%{(.\S+)}/)) {
194
+ if (!options) {
195
+ throw new Error('i18n: cannot replace placeholders in string if no option data provided')
196
+ }
197
+
198
+ return this.replacePlaceholders(translationString, options)
199
+ } else {
200
+ return translationString
201
+ }
202
+ } else {
203
+ // If the key wasn't found in our translations object,
204
+ // return the lookup key itself as the fallback
205
+ return lookupKey
206
+ }
207
+ };
208
+
209
+ /**
210
+ * Takes a translation string with placeholders, and replaces the placeholders
211
+ * with the provided data
212
+ *
213
+ * @param {string} translationString - The translation string
214
+ * @param {object} options - Any options passed with the translation string, e.g: for string interpolation.
215
+ * @returns {string} The translation string to output, with ${} placeholders replaced
216
+ */
217
+ I18n.prototype.replacePlaceholders = function (translationString, options) {
218
+ var formatter;
219
+
220
+ if (this.hasIntlNumberFormatSupport()) {
221
+ formatter = new Intl.NumberFormat(this.locale);
222
+ }
223
+
224
+ return translationString.replace(/%{(.\S+)}/g, function (placeholderWithBraces, placeholderKey) {
225
+ if (Object.prototype.hasOwnProperty.call(options, placeholderKey)) {
226
+ var placeholderValue = options[placeholderKey];
227
+
228
+ // If a user has passed `false` as the value for the placeholder
229
+ // treat it as though the value should not be displayed
230
+ if (placeholderValue === false) {
231
+ return ''
232
+ }
233
+
234
+ // If the placeholder's value is a number, localise the number formatting
235
+ if (typeof placeholderValue === 'number' && formatter) {
236
+ return formatter.format(placeholderValue)
237
+ }
238
+
239
+ return placeholderValue
240
+ } else {
241
+ throw new Error('i18n: no data found to replace ' + placeholderWithBraces + ' placeholder in string')
242
+ }
243
+ })
244
+ };
245
+
246
+ /**
247
+ * Check to see if the browser supports Intl and Intl.PluralRules.
248
+ *
249
+ * It requires all conditions to be met in order to be supported:
250
+ * - The browser supports the Intl class (true in IE11)
251
+ * - The implementation of Intl supports PluralRules (NOT true in IE11)
252
+ * - The browser/OS has plural rules for the current locale (browser dependent)
253
+ *
254
+ * @returns {boolean} Returns true if all conditions are met. Returns false otherwise.
255
+ */
256
+ I18n.prototype.hasIntlPluralRulesSupport = function () {
257
+ return Boolean(window.Intl && ('PluralRules' in window.Intl && Intl.PluralRules.supportedLocalesOf(this.locale).length))
258
+ };
259
+
260
+ /**
261
+ * Check to see if the browser supports Intl and Intl.NumberFormat.
262
+ *
263
+ * It requires all conditions to be met in order to be supported:
264
+ * - The browser supports the Intl class (true in IE11)
265
+ * - The implementation of Intl supports NumberFormat (also true in IE11)
266
+ * - The browser/OS has number formatting rules for the current locale (browser dependent)
267
+ *
268
+ * @returns {boolean} Returns true if all conditions are met. Returns false otherwise.
269
+ */
270
+ I18n.prototype.hasIntlNumberFormatSupport = function () {
271
+ return Boolean(window.Intl && ('NumberFormat' in window.Intl && Intl.NumberFormat.supportedLocalesOf(this.locale).length))
272
+ };
273
+
274
+ /**
275
+ * Get the appropriate suffix for the plural form.
276
+ *
277
+ * Uses Intl.PluralRules (or our own fallback implementation) to get the
278
+ * 'preferred' form to use for the given count.
279
+ *
280
+ * Checks that a translation has been provided for that plural form – if it
281
+ * hasn't, it'll fall back to the 'other' plural form (unless that doesn't exist
282
+ * either, in which case an error will be thrown)
283
+ *
284
+ * @param {string} lookupKey - The lookup key of the string to use.
285
+ * @param {number} count - Number used to determine which pluralisation to use.
286
+ * @returns {PluralRule} The suffix associated with the correct pluralisation for this locale.
287
+ */
288
+ I18n.prototype.getPluralSuffix = function (lookupKey, count) {
289
+ // Validate that the number is actually a number.
290
+ //
291
+ // Number(count) will turn anything that can't be converted to a Number type
292
+ // into 'NaN'. isFinite filters out NaN, as it isn't a finite number.
293
+ count = Number(count);
294
+ if (!isFinite(count)) { return 'other' }
295
+
296
+ var preferredForm;
297
+
298
+ // Check to verify that all the requirements for Intl.PluralRules are met.
299
+ // If so, we can use that instead of our custom implementation. Otherwise,
300
+ // use the hardcoded fallback.
301
+ if (this.hasIntlPluralRulesSupport()) {
302
+ preferredForm = new Intl.PluralRules(this.locale).select(count);
303
+ } else {
304
+ preferredForm = this.selectPluralFormUsingFallbackRules(count);
305
+ }
306
+
307
+ // Use the correct plural form if provided
308
+ if (lookupKey + '.' + preferredForm in this.translations) {
309
+ return preferredForm
310
+ // Fall back to `other` if the plural form is missing, but log a warning
311
+ // to the console
312
+ } else if (lookupKey + '.other' in this.translations) {
313
+ if (console && 'warn' in console) {
314
+ console.warn('i18n: Missing plural form ".' + preferredForm + '" for "' +
315
+ this.locale + '" locale. Falling back to ".other".');
316
+ }
317
+
318
+ return 'other'
319
+ // If the required `other` plural form is missing, all we can do is error
320
+ } else {
321
+ throw new Error(
322
+ 'i18n: Plural form ".other" is required for "' + this.locale + '" locale'
323
+ )
324
+ }
325
+ };
326
+
327
+ /**
328
+ * Get the plural form using our fallback implementation
329
+ *
330
+ * This is split out into a separate function to make it easier to test the
331
+ * fallback behaviour in an environment where Intl.PluralRules exists.
332
+ *
333
+ * @param {number} count - Number used to determine which pluralisation to use.
334
+ * @returns {PluralRule} The pluralisation form for count in this locale.
335
+ */
336
+ I18n.prototype.selectPluralFormUsingFallbackRules = function (count) {
337
+ // Currently our custom code can only handle positive integers, so let's
338
+ // make sure our number is one of those.
339
+ count = Math.abs(Math.floor(count));
340
+
341
+ var ruleset = this.getPluralRulesForLocale();
342
+
343
+ if (ruleset) {
344
+ return I18n.pluralRules[ruleset](count)
345
+ }
346
+
347
+ return 'other'
348
+ };
349
+
350
+ /**
351
+ * Work out which pluralisation rules to use for the current locale
352
+ *
353
+ * The locale may include a regional indicator (such as en-GB), but we don't
354
+ * usually care about this part, as pluralisation rules are usually the same
355
+ * regardless of region. There are exceptions, however, (e.g. Portuguese) so
356
+ * this searches by both the full and shortened locale codes, just to be sure.
357
+ *
358
+ * @returns {PluralRuleName | undefined} The name of the pluralisation rule to use (a key for one
359
+ * of the functions in this.pluralRules)
360
+ */
361
+ I18n.prototype.getPluralRulesForLocale = function () {
362
+ var locale = this.locale;
363
+ var localeShort = locale.split('-')[0];
364
+
365
+ // Look through the plural rules map to find which `pluralRule` is
366
+ // appropriate for our current `locale`.
367
+ for (var pluralRule in I18n.pluralRulesMap) {
368
+ if (Object.prototype.hasOwnProperty.call(I18n.pluralRulesMap, pluralRule)) {
369
+ var languages = I18n.pluralRulesMap[pluralRule];
370
+ for (var i = 0; i < languages.length; i++) {
371
+ if (languages[i] === locale || languages[i] === localeShort) {
372
+ return pluralRule
373
+ }
374
+ }
375
+ }
376
+ }
377
+ };
378
+
379
+ /**
380
+ * Map of plural rules to languages where those rules apply.
381
+ *
382
+ * Note: These groups are named for the most dominant or recognisable language
383
+ * that uses each system. The groupings do not imply that the languages are
384
+ * related to one another. Many languages have evolved the same systems
385
+ * independently of one another.
386
+ *
387
+ * Code to support more languages can be found in the i18n spike:
388
+ * {@link https://github.com/alphagov/govuk-frontend/blob/spike-i18n-support/src/govuk/i18n.mjs}
389
+ *
390
+ * Languages currently supported:
391
+ *
392
+ * Arabic: Arabic (ar)
393
+ * Chinese: Burmese (my), Chinese (zh), Indonesian (id), Japanese (ja),
394
+ * Javanese (jv), Korean (ko), Malay (ms), Thai (th), Vietnamese (vi)
395
+ * French: Armenian (hy), Bangla (bn), French (fr), Gujarati (gu), Hindi (hi),
396
+ * Persian Farsi (fa), Punjabi (pa), Zulu (zu)
397
+ * German: Afrikaans (af), Albanian (sq), Azerbaijani (az), Basque (eu),
398
+ * Bulgarian (bg), Catalan (ca), Danish (da), Dutch (nl), English (en),
399
+ * Estonian (et), Finnish (fi), Georgian (ka), German (de), Greek (el),
400
+ * Hungarian (hu), Luxembourgish (lb), Norwegian (no), Somali (so),
401
+ * Swahili (sw), Swedish (sv), Tamil (ta), Telugu (te), Turkish (tr),
402
+ * Urdu (ur)
403
+ * Irish: Irish Gaelic (ga)
404
+ * Russian: Russian (ru), Ukrainian (uk)
405
+ * Scottish: Scottish Gaelic (gd)
406
+ * Spanish: European Portuguese (pt-PT), Italian (it), Spanish (es)
407
+ * Welsh: Welsh (cy)
408
+ *
409
+ * @type {Object<PluralRuleName, string[]>}
410
+ */
411
+ I18n.pluralRulesMap = {
412
+ arabic: ['ar'],
413
+ chinese: ['my', 'zh', 'id', 'ja', 'jv', 'ko', 'ms', 'th', 'vi'],
414
+ french: ['hy', 'bn', 'fr', 'gu', 'hi', 'fa', 'pa', 'zu'],
415
+ german: [
416
+ 'af', 'sq', 'az', 'eu', 'bg', 'ca', 'da', 'nl', 'en', 'et', 'fi', 'ka',
417
+ 'de', 'el', 'hu', 'lb', 'no', 'so', 'sw', 'sv', 'ta', 'te', 'tr', 'ur'
418
+ ],
419
+ irish: ['ga'],
420
+ russian: ['ru', 'uk'],
421
+ scottish: ['gd'],
422
+ spanish: ['pt-PT', 'it', 'es'],
423
+ welsh: ['cy']
424
+ };
425
+
426
+ /**
427
+ * Different pluralisation rule sets
428
+ *
429
+ * Returns the appropriate suffix for the plural form associated with `n`.
430
+ * Possible suffixes: 'zero', 'one', 'two', 'few', 'many', 'other' (the actual
431
+ * meaning of each differs per locale). 'other' should always exist, even in
432
+ * languages without plurals, such as Chinese.
433
+ * {@link https://cldr.unicode.org/index/cldr-spec/plural-rules}
434
+ *
435
+ * The count must be a positive integer. Negative numbers and decimals aren't accounted for
436
+ *
437
+ * @type {Object<string, function(number): PluralRule>}
438
+ */
439
+ I18n.pluralRules = {
440
+ arabic: function (n) {
441
+ if (n === 0) { return 'zero' }
442
+ if (n === 1) { return 'one' }
443
+ if (n === 2) { return 'two' }
444
+ if (n % 100 >= 3 && n % 100 <= 10) { return 'few' }
445
+ if (n % 100 >= 11 && n % 100 <= 99) { return 'many' }
446
+ return 'other'
447
+ },
448
+ chinese: function () {
449
+ return 'other'
450
+ },
451
+ french: function (n) {
452
+ return n === 0 || n === 1 ? 'one' : 'other'
453
+ },
454
+ german: function (n) {
455
+ return n === 1 ? 'one' : 'other'
456
+ },
457
+ irish: function (n) {
458
+ if (n === 1) { return 'one' }
459
+ if (n === 2) { return 'two' }
460
+ if (n >= 3 && n <= 6) { return 'few' }
461
+ if (n >= 7 && n <= 10) { return 'many' }
462
+ return 'other'
463
+ },
464
+ russian: function (n) {
465
+ var lastTwo = n % 100;
466
+ var last = lastTwo % 10;
467
+ if (last === 1 && lastTwo !== 11) { return 'one' }
468
+ if (last >= 2 && last <= 4 && !(lastTwo >= 12 && lastTwo <= 14)) { return 'few' }
469
+ if (last === 0 || (last >= 5 && last <= 9) || (lastTwo >= 11 && lastTwo <= 14)) { return 'many' }
470
+ // Note: The 'other' suffix is only used by decimal numbers in Russian.
471
+ // We don't anticipate it being used, but it's here for consistency.
472
+ return 'other'
473
+ },
474
+ scottish: function (n) {
475
+ if (n === 1 || n === 11) { return 'one' }
476
+ if (n === 2 || n === 12) { return 'two' }
477
+ if ((n >= 3 && n <= 10) || (n >= 13 && n <= 19)) { return 'few' }
478
+ return 'other'
479
+ },
480
+ spanish: function (n) {
481
+ if (n === 1) { return 'one' }
482
+ if (n % 1000000 === 0 && n !== 0) { return 'many' }
483
+ return 'other'
484
+ },
485
+ welsh: function (n) {
486
+ if (n === 0) { return 'zero' }
487
+ if (n === 1) { return 'one' }
488
+ if (n === 2) { return 'two' }
489
+ if (n === 3) { return 'few' }
490
+ if (n === 6) { return 'many' }
491
+ return 'other'
492
+ }
493
+ };
494
+
495
+ /**
496
+ * Supported languages for plural rules
497
+ *
498
+ * @typedef {'arabic' | 'chinese' | 'french' | 'german' | 'irish' | 'russian' | 'scottish' | 'spanish' | 'welsh'} PluralRuleName
499
+ */
500
+
501
+ /**
502
+ * Plural rule category mnemonic tags
503
+ *
504
+ * @typedef {'zero' | 'one' | 'two' | 'few' | 'many' | 'other'} PluralRule
505
+ */
506
+
507
+ /**
508
+ * Translated message by plural rule they correspond to.
509
+ *
510
+ * Allows to group pluralised messages under a single key when passing
511
+ * translations to a component's constructor
512
+ *
513
+ * @typedef {object} TranslationPluralForms
514
+ * @property {string} [other] - General plural form
515
+ * @property {string} [zero] - Plural form used with 0
516
+ * @property {string} [one] - Plural form used with 1
517
+ * @property {string} [two] - Plural form used with 2
518
+ * @property {string} [few] - Plural form used for a few
519
+ * @property {string} [many] - Plural form used for many
520
+ */
521
+
522
+ /**
523
+ * Translated messages (flattened)
524
+ *
525
+ * @private
526
+ * @typedef {Object<string, string> | {}} TranslationsFlattened
527
+ */
528
+
21
529
  (function(undefined) {
22
530
 
23
531
  // Detection from https://github.com/Financial-Times/polyfill-service/blob/master/packages/polyfill-library/polyfills/Object/defineProperty/detect.js
@@ -758,13 +1266,188 @@ if (detect) return
758
1266
 
759
1267
  }).call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
760
1268
 
761
- function Accordion ($module) {
1269
+ (function(undefined) {
1270
+
1271
+ // Detection from https://github.com/mdn/content/blob/cf607d68522cd35ee7670782d3ee3a361eaef2e4/files/en-us/web/javascript/reference/global_objects/string/trim/index.md#polyfill
1272
+ var detect = ('trim' in String.prototype);
1273
+
1274
+ if (detect) return
1275
+
1276
+ // Polyfill from https://github.com/mdn/content/blob/cf607d68522cd35ee7670782d3ee3a361eaef2e4/files/en-us/web/javascript/reference/global_objects/string/trim/index.md#polyfill
1277
+ String.prototype.trim = function () {
1278
+ return this.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
1279
+ };
1280
+
1281
+ }).call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
1282
+
1283
+ (function(undefined) {
1284
+
1285
+ // Detection from https://raw.githubusercontent.com/Financial-Times/polyfill-library/13cf7c340974d128d557580b5e2dafcd1b1192d1/polyfills/Element/prototype/dataset/detect.js
1286
+ var detect = (function(){
1287
+ if (!document.documentElement.dataset) {
1288
+ return false;
1289
+ }
1290
+ var el = document.createElement('div');
1291
+ el.setAttribute("data-a-b", "c");
1292
+ return el.dataset && el.dataset.aB == "c";
1293
+ }());
1294
+
1295
+ if (detect) return
1296
+
1297
+ // Polyfill derived from https://raw.githubusercontent.com/Financial-Times/polyfill-library/13cf7c340974d128d557580b5e2dafcd1b1192d1/polyfills/Element/prototype/dataset/polyfill.js
1298
+ Object.defineProperty(Element.prototype, 'dataset', {
1299
+ get: function() {
1300
+ var element = this;
1301
+ var attributes = this.attributes;
1302
+ var map = {};
1303
+
1304
+ for (var i = 0; i < attributes.length; i++) {
1305
+ var attribute = attributes[i];
1306
+
1307
+ // This regex has been edited from the original polyfill, to add
1308
+ // support for period (.) separators in data-* attribute names. These
1309
+ // are allowed in the HTML spec, but were not covered by the original
1310
+ // polyfill's regex. We use periods in our i18n implementation.
1311
+ if (attribute && attribute.name && (/^data-\w[.\w-]*$/).test(attribute.name)) {
1312
+ var name = attribute.name;
1313
+ var value = attribute.value;
1314
+
1315
+ var propName = name.substr(5).replace(/-./g, function (prop) {
1316
+ return prop.charAt(1).toUpperCase();
1317
+ });
1318
+
1319
+ // If this browser supports __defineGetter__ and __defineSetter__,
1320
+ // continue using defineProperty. If not (like IE 8 and below), we use
1321
+ // a hacky fallback which at least gives an object in the right format
1322
+ if ('__defineGetter__' in Object.prototype && '__defineSetter__' in Object.prototype) {
1323
+ Object.defineProperty(map, propName, {
1324
+ enumerable: true,
1325
+ get: function() {
1326
+ return this.value;
1327
+ }.bind({value: value || ''}),
1328
+ set: function setter(name, value) {
1329
+ if (typeof value !== 'undefined') {
1330
+ this.setAttribute(name, value);
1331
+ } else {
1332
+ this.removeAttribute(name);
1333
+ }
1334
+ }.bind(element, name)
1335
+ });
1336
+ } else {
1337
+ map[propName] = value;
1338
+ }
1339
+
1340
+ }
1341
+ }
1342
+
1343
+ return map;
1344
+ }
1345
+ });
1346
+
1347
+ }).call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
1348
+
1349
+ /**
1350
+ * Normalise string
1351
+ *
1352
+ * 'If it looks like a duck, and it quacks like a duck…' 🦆
1353
+ *
1354
+ * If the passed value looks like a boolean or a number, convert it to a boolean
1355
+ * or number.
1356
+ *
1357
+ * Designed to be used to convert config passed via data attributes (which are
1358
+ * always strings) into something sensible.
1359
+ *
1360
+ * @param {string} value - The value to normalise
1361
+ * @returns {string | boolean | number | undefined} Normalised data
1362
+ */
1363
+ function normaliseString (value) {
1364
+ if (typeof value !== 'string') {
1365
+ return value
1366
+ }
1367
+
1368
+ var trimmedValue = value.trim();
1369
+
1370
+ if (trimmedValue === 'true') {
1371
+ return true
1372
+ }
1373
+
1374
+ if (trimmedValue === 'false') {
1375
+ return false
1376
+ }
1377
+
1378
+ // Empty / whitespace-only strings are considered finite so we need to check
1379
+ // the length of the trimmed string as well
1380
+ if (trimmedValue.length > 0 && isFinite(trimmedValue)) {
1381
+ return Number(trimmedValue)
1382
+ }
1383
+
1384
+ return value
1385
+ }
1386
+
1387
+ /**
1388
+ * Normalise dataset
1389
+ *
1390
+ * Loop over an object and normalise each value using normaliseData function
1391
+ *
1392
+ * @param {DOMStringMap} dataset - HTML element dataset
1393
+ * @returns {Object<string, string | boolean | number | undefined>} Normalised dataset
1394
+ */
1395
+ function normaliseDataset (dataset) {
1396
+ var out = {};
1397
+
1398
+ for (var key in dataset) {
1399
+ out[key] = normaliseString(dataset[key]);
1400
+ }
1401
+
1402
+ return out
1403
+ }
1404
+
1405
+ /**
1406
+ * @constant
1407
+ * @type {AccordionTranslations}
1408
+ * @see Default value for {@link AccordionConfig.i18n}
1409
+ * @default
1410
+ */
1411
+ var ACCORDION_TRANSLATIONS = {
1412
+ hideAllSections: 'Hide all sections',
1413
+ hideSection: 'Hide',
1414
+ hideSectionAriaLabel: 'Hide this section',
1415
+ showAllSections: 'Show all sections',
1416
+ showSection: 'Show',
1417
+ showSectionAriaLabel: 'Show this section'
1418
+ };
1419
+
1420
+ /**
1421
+ * Accordion component
1422
+ *
1423
+ * This allows a collection of sections to be collapsed by default, showing only
1424
+ * their headers. Sections can be expanded or collapsed individually by clicking
1425
+ * their headers. A "Show all sections" button is also added to the top of the
1426
+ * accordion, which switches to "Hide all sections" when all the sections are
1427
+ * expanded.
1428
+ *
1429
+ * The state of each section is saved to the DOM via the `aria-expanded`
1430
+ * attribute, which also provides accessibility.
1431
+ *
1432
+ * @class
1433
+ * @param {HTMLElement} $module - HTML element to use for accordion
1434
+ * @param {AccordionConfig} [config] - Accordion config
1435
+ */
1436
+ function Accordion ($module, config) {
762
1437
  this.$module = $module;
763
- this.moduleId = $module.getAttribute('id');
764
1438
  this.$sections = $module.querySelectorAll('.govuk-accordion__section');
765
- this.$showAllButton = '';
766
1439
  this.browserSupportsSessionStorage = helper.checkForSessionStorage();
767
1440
 
1441
+ var defaultConfig = {
1442
+ i18n: ACCORDION_TRANSLATIONS
1443
+ };
1444
+ this.config = mergeConfigs(
1445
+ defaultConfig,
1446
+ config || {},
1447
+ normaliseDataset($module.dataset)
1448
+ );
1449
+ this.i18n = new I18n(extractConfigByNamespace(this.config, 'i18n'));
1450
+
768
1451
  this.controlsClass = 'govuk-accordion__controls';
769
1452
  this.showAllClass = 'govuk-accordion__show-all';
770
1453
  this.showAllTextClass = 'govuk-accordion__show-all-text';
@@ -855,7 +1538,7 @@ Accordion.prototype.constructHeaderMarkup = function ($headerWrapper, index) {
855
1538
  // Create a button element that will replace the '.govuk-accordion__section-button' span
856
1539
  var $button = document.createElement('button');
857
1540
  $button.setAttribute('type', 'button');
858
- $button.setAttribute('aria-controls', this.moduleId + '-content-' + (index + 1));
1541
+ $button.setAttribute('aria-controls', this.$module.id + '-content-' + (index + 1));
859
1542
 
860
1543
  // Copy all attributes (https://developer.mozilla.org/en-US/docs/Web/API/Element/attributes) from $span to $button
861
1544
  for (var i = 0; i < $span.attributes.length; i++) {
@@ -972,17 +1655,34 @@ Accordion.prototype.setExpanded = function (expanded, $section) {
972
1655
  var $icon = $section.querySelector('.' + this.upChevronIconClass);
973
1656
  var $showHideText = $section.querySelector('.' + this.sectionShowHideTextClass);
974
1657
  var $button = $section.querySelector('.' + this.sectionButtonClass);
975
- var newButtonText = expanded ? 'Hide' : 'Show';
1658
+ var newButtonText = expanded
1659
+ ? this.i18n.t('hideSection')
1660
+ : this.i18n.t('showSection');
976
1661
 
977
- // Build additional copy of "this section" for assistive technology and place inside toggle link
978
- var $visuallyHiddenText = document.createElement('span');
979
- $visuallyHiddenText.classList.add('govuk-visually-hidden');
980
- $visuallyHiddenText.innerHTML = ' this section';
981
-
982
- $showHideText.innerHTML = newButtonText;
983
- $showHideText.appendChild($visuallyHiddenText);
1662
+ $showHideText.innerText = newButtonText;
984
1663
  $button.setAttribute('aria-expanded', expanded);
985
1664
 
1665
+ // Update aria-label combining
1666
+ var $header = $section.querySelector('.' + this.sectionHeadingTextClass);
1667
+ var ariaLabelParts = [$header.innerText.trim()];
1668
+
1669
+ var $summary = $section.querySelector('.' + this.sectionSummaryClass);
1670
+ if ($summary) {
1671
+ ariaLabelParts.push($summary.innerText.trim());
1672
+ }
1673
+
1674
+ var ariaLabelMessage = expanded
1675
+ ? this.i18n.t('hideSectionAriaLabel')
1676
+ : this.i18n.t('showSectionAriaLabel');
1677
+ ariaLabelParts.push(ariaLabelMessage);
1678
+
1679
+ /*
1680
+ * Join with a comma to add pause for assistive technology.
1681
+ * Example: [heading]Section A ,[pause] Show this section.
1682
+ * https://accessibility.blog.gov.uk/2017/12/18/what-working-on-gov-uk-navigation-taught-us-about-accessibility/
1683
+ */
1684
+ $button.setAttribute('aria-label', ariaLabelParts.join(' , '));
1685
+
986
1686
  // Swap icon, change class
987
1687
  if (expanded) {
988
1688
  $section.classList.add(this.sectionExpandedClass);
@@ -1017,9 +1717,11 @@ Accordion.prototype.checkIfAllSectionsOpen = function () {
1017
1717
  Accordion.prototype.updateShowAllButton = function (expanded) {
1018
1718
  var $showAllIcon = this.$showAllButton.querySelector('.' + this.upChevronIconClass);
1019
1719
  var $showAllText = this.$showAllButton.querySelector('.' + this.showAllTextClass);
1020
- var newButtonText = expanded ? 'Hide all sections' : 'Show all sections';
1720
+ var newButtonText = expanded
1721
+ ? this.i18n.t('hideAllSections')
1722
+ : this.i18n.t('showAllSections');
1021
1723
  this.$showAllButton.setAttribute('aria-expanded', expanded);
1022
- $showAllText.innerHTML = newButtonText;
1724
+ $showAllText.innerText = newButtonText;
1023
1725
 
1024
1726
  // Swap icon, toggle class
1025
1727
  if (expanded) {
@@ -1082,17 +1784,14 @@ Accordion.prototype.setInitialState = function ($section) {
1082
1784
  };
1083
1785
 
1084
1786
  /**
1085
- * Create an element to improve semantics of the section button with punctuation
1086
- * @return {object} DOM element
1087
- *
1088
- * Used to add pause (with a comma) for assistive technology.
1089
- * Example: [heading]Section A ,[pause] Show this section.
1090
- * https://accessibility.blog.gov.uk/2017/12/18/what-working-on-gov-uk-navigation-taught-us-about-accessibility/
1091
- *
1092
- * Adding punctuation to the button can also improve its general semantics by dividing its contents
1093
- * into thematic chunks.
1094
- * See https://github.com/alphagov/govuk-frontend/issues/2327#issuecomment-922957442
1095
- */
1787
+ * Create an element to improve semantics of the section button with punctuation
1788
+ *
1789
+ * @returns {HTMLSpanElement} DOM element
1790
+ *
1791
+ * Adding punctuation to the button can also improve its general semantics by dividing its contents
1792
+ * into thematic chunks.
1793
+ * See https://github.com/alphagov/govuk-frontend/issues/2327#issuecomment-922957442
1794
+ */
1096
1795
  Accordion.prototype.getButtonPunctuationEl = function () {
1097
1796
  var $punctuationEl = document.createElement('span');
1098
1797
  $punctuationEl.classList.add('govuk-visually-hidden', 'govuk-accordion__section-heading-divider');
@@ -1100,6 +1799,35 @@ Accordion.prototype.getButtonPunctuationEl = function () {
1100
1799
  return $punctuationEl
1101
1800
  };
1102
1801
 
1802
+ /**
1803
+ * Accordion config
1804
+ *
1805
+ * @typedef {object} AccordionConfig
1806
+ * @property {AccordionTranslations} [i18n = ACCORDION_TRANSLATIONS] - See constant {@link ACCORDION_TRANSLATIONS}
1807
+ */
1808
+
1809
+ /**
1810
+ * Accordion translations
1811
+ *
1812
+ * @typedef {object} AccordionTranslations
1813
+ *
1814
+ * Messages used by the component for the labels of its buttons. This includes
1815
+ * the visible text shown on screen, and text to help assistive technology users
1816
+ * for the buttons toggling each section.
1817
+ * @property {string} [hideAllSections] - The text content for the 'Hide all
1818
+ * sections' button, used when at least one section is expanded.
1819
+ * @property {string} [hideSection] - The text content for the 'Hide'
1820
+ * button, used when a section is expanded.
1821
+ * @property {string} [hideSectionAriaLabel] - The text content appended to the
1822
+ * 'Hide' button's accessible name when a section is expanded.
1823
+ * @property {string} [showAllSections] - The text content for the 'Show all
1824
+ * sections' button, used when all sections are collapsed.
1825
+ * @property {string} [showSection] - The text content for the 'Show'
1826
+ * button, used when a section is collapsed.
1827
+ * @property {string} [showSectionAriaLabel] - The text content appended to the
1828
+ * 'Show' button's accessible name when a section is expanded.
1829
+ */
1830
+
1103
1831
  return Accordion;
1104
1832
 
1105
1833
  })));