govuk_publishing_components 35.3.3 → 35.3.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (272) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-core.js +4 -0
  3. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/init-ga4.js +1 -1
  4. data/app/assets/stylesheets/component_guide/application.scss +37 -1
  5. data/app/assets/stylesheets/govuk_publishing_components/components/_metadata.scss +10 -6
  6. data/app/assets/stylesheets/govuk_publishing_components/components/_organisation-logo.scss +2 -2
  7. data/app/assets/stylesheets/govuk_publishing_components/components/helpers/_brand-colours.scss +0 -24
  8. data/app/views/govuk_publishing_components/component_guide/example.html.erb +1 -1
  9. data/app/views/govuk_publishing_components/component_guide/preview.html.erb +1 -1
  10. data/app/views/govuk_publishing_components/component_guide/show.html.erb +1 -1
  11. data/app/views/govuk_publishing_components/components/_layout_super_navigation_header.html.erb +2 -2
  12. data/app/views/govuk_publishing_components/components/_metadata.html.erb +1 -1
  13. data/app/views/govuk_publishing_components/components/_related_navigation.html.erb +3 -1
  14. data/app/views/govuk_publishing_components/components/_share_links.html.erb +6 -2
  15. data/app/views/govuk_publishing_components/components/_step_by_step_nav_related.html.erb +1 -1
  16. data/app/views/govuk_publishing_components/components/docs/metadata.yml +1 -3
  17. data/app/views/govuk_publishing_components/components/docs/organisation_logo.yml +5 -5
  18. data/app/views/govuk_publishing_components/components/docs/step_by_step_nav_related.yml +1 -0
  19. data/app/views/govuk_publishing_components/components/related_navigation/_section.html.erb +1 -1
  20. data/app/views/layouts/govuk_publishing_components/application.html.erb +1 -0
  21. data/config/locales/en.yml +2 -2
  22. data/lib/govuk_publishing_components/app_helpers/asset_helper.rb +4 -0
  23. data/lib/govuk_publishing_components/presenters/breadcrumbs_helper.rb +1 -1
  24. data/lib/govuk_publishing_components/version.rb +1 -1
  25. data/node_modules/axe-core/axe.d.ts +77 -6
  26. data/node_modules/axe-core/axe.js +955 -600
  27. data/node_modules/axe-core/axe.min.js +2 -2
  28. data/node_modules/axe-core/locales/_template.json +6 -6
  29. data/node_modules/axe-core/locales/ja.json +91 -11
  30. data/node_modules/axe-core/package.json +5 -5
  31. data/node_modules/axe-core/sri-history.json +4 -0
  32. data/node_modules/govuk-frontend/README.md +4 -5
  33. data/node_modules/govuk-frontend/govuk/all-ie8.scss +8 -0
  34. data/node_modules/govuk-frontend/govuk/all.js +738 -258
  35. data/node_modules/govuk-frontend/govuk/all.js.map +1 -1
  36. data/node_modules/govuk-frontend/govuk/common/closest-attribute-value.js +11 -7
  37. data/node_modules/govuk-frontend/govuk/common/closest-attribute-value.js.map +1 -1
  38. data/node_modules/govuk-frontend/govuk/common/govuk-frontend-version.js +17 -0
  39. data/node_modules/govuk-frontend/govuk/common/govuk-frontend-version.js.map +1 -0
  40. data/node_modules/govuk-frontend/govuk/common/index.js +16 -4
  41. data/node_modules/govuk-frontend/govuk/common/index.js.map +1 -1
  42. data/node_modules/govuk-frontend/govuk/common/normalise-dataset.js +20 -10
  43. data/node_modules/govuk-frontend/govuk/common/normalise-dataset.js.map +1 -1
  44. data/node_modules/govuk-frontend/govuk/common.js +16 -4
  45. data/node_modules/govuk-frontend/govuk/common.js.map +1 -1
  46. data/node_modules/govuk-frontend/govuk/components/accordion/_index.scss +3 -3
  47. data/node_modules/govuk-frontend/govuk/components/accordion/accordion.js +234 -86
  48. data/node_modules/govuk-frontend/govuk/components/accordion/accordion.js.map +1 -1
  49. data/node_modules/govuk-frontend/govuk/components/accordion/fixtures.json +32 -13
  50. data/node_modules/govuk-frontend/govuk/components/accordion/macro-options.json +8 -2
  51. data/node_modules/govuk-frontend/govuk/components/accordion/template.njk +1 -0
  52. data/node_modules/govuk-frontend/govuk/components/back-link/_index.scss +1 -1
  53. data/node_modules/govuk-frontend/govuk/components/back-link/fixtures.json +1 -1
  54. data/node_modules/govuk-frontend/govuk/components/back-link/macro-options.json +1 -1
  55. data/node_modules/govuk-frontend/govuk/components/breadcrumbs/_index.scss +1 -1
  56. data/node_modules/govuk-frontend/govuk/components/breadcrumbs/fixtures.json +12 -12
  57. data/node_modules/govuk-frontend/govuk/components/breadcrumbs/macro-options.json +1 -1
  58. data/node_modules/govuk-frontend/govuk/components/button/_index.scss +8 -6
  59. data/node_modules/govuk-frontend/govuk/components/button/button.js +54 -19
  60. data/node_modules/govuk-frontend/govuk/components/button/button.js.map +1 -1
  61. data/node_modules/govuk-frontend/govuk/components/button/fixtures.json +11 -1
  62. data/node_modules/govuk-frontend/govuk/components/button/macro-options.json +7 -1
  63. data/node_modules/govuk-frontend/govuk/components/button/template.njk +1 -1
  64. data/node_modules/govuk-frontend/govuk/components/character-count/character-count.js +170 -76
  65. data/node_modules/govuk-frontend/govuk/components/character-count/character-count.js.map +1 -1
  66. data/node_modules/govuk-frontend/govuk/components/character-count/fixtures.json +24 -24
  67. data/node_modules/govuk-frontend/govuk/components/character-count/macro-options.json +3 -3
  68. data/node_modules/govuk-frontend/govuk/components/checkboxes/_index.scss +6 -6
  69. data/node_modules/govuk-frontend/govuk/components/checkboxes/checkboxes.js +75 -24
  70. data/node_modules/govuk-frontend/govuk/components/checkboxes/checkboxes.js.map +1 -1
  71. data/node_modules/govuk-frontend/govuk/components/checkboxes/fixtures.json +47 -47
  72. data/node_modules/govuk-frontend/govuk/components/checkboxes/macro-options.json +1 -1
  73. data/node_modules/govuk-frontend/govuk/components/checkboxes/template.njk +3 -3
  74. data/node_modules/govuk-frontend/govuk/components/cookie-banner/fixtures.json +24 -24
  75. data/node_modules/govuk-frontend/govuk/components/cookie-banner/macro-options.json +1 -1
  76. data/node_modules/govuk-frontend/govuk/components/date-input/fixtures.json +26 -26
  77. data/node_modules/govuk-frontend/govuk/components/date-input/macro-options.json +1 -1
  78. data/node_modules/govuk-frontend/govuk/components/date-input/template.njk +1 -1
  79. data/node_modules/govuk-frontend/govuk/components/details/details.js +39 -13
  80. data/node_modules/govuk-frontend/govuk/components/details/details.js.map +1 -1
  81. data/node_modules/govuk-frontend/govuk/components/details/fixtures.json +1 -1
  82. data/node_modules/govuk-frontend/govuk/components/details/macro-options.json +1 -1
  83. data/node_modules/govuk-frontend/govuk/components/error-message/fixtures.json +10 -10
  84. data/node_modules/govuk-frontend/govuk/components/error-message/macro-options.json +1 -1
  85. data/node_modules/govuk-frontend/govuk/components/error-summary/error-summary.js +67 -27
  86. data/node_modules/govuk-frontend/govuk/components/error-summary/error-summary.js.map +1 -1
  87. data/node_modules/govuk-frontend/govuk/components/error-summary/fixtures.json +19 -19
  88. data/node_modules/govuk-frontend/govuk/components/error-summary/macro-options.json +1 -1
  89. data/node_modules/govuk-frontend/govuk/components/fieldset/fixtures.json +19 -19
  90. data/node_modules/govuk-frontend/govuk/components/fieldset/macro-options.json +1 -1
  91. data/node_modules/govuk-frontend/govuk/components/file-upload/_index.scss +6 -1
  92. data/node_modules/govuk-frontend/govuk/components/file-upload/fixtures.json +14 -14
  93. data/node_modules/govuk-frontend/govuk/components/file-upload/macro-options.json +7 -1
  94. data/node_modules/govuk-frontend/govuk/components/file-upload/template.njk +2 -1
  95. data/node_modules/govuk-frontend/govuk/components/footer/_index.scss +0 -7
  96. data/node_modules/govuk-frontend/govuk/components/footer/fixtures.json +24 -24
  97. data/node_modules/govuk-frontend/govuk/components/footer/macro-options.json +1 -1
  98. data/node_modules/govuk-frontend/govuk/components/header/fixtures.json +22 -22
  99. data/node_modules/govuk-frontend/govuk/components/header/header.js +46 -14
  100. data/node_modules/govuk-frontend/govuk/components/header/header.js.map +1 -1
  101. data/node_modules/govuk-frontend/govuk/components/header/macro-options.json +1 -1
  102. data/node_modules/govuk-frontend/govuk/components/header/template.njk +1 -1
  103. data/node_modules/govuk-frontend/govuk/components/hint/fixtures.json +1 -1
  104. data/node_modules/govuk-frontend/govuk/components/hint/macro-options.json +1 -1
  105. data/node_modules/govuk-frontend/govuk/components/input/_index.scss +15 -3
  106. data/node_modules/govuk-frontend/govuk/components/input/fixtures.json +54 -41
  107. data/node_modules/govuk-frontend/govuk/components/input/macro-options.json +7 -1
  108. data/node_modules/govuk-frontend/govuk/components/input/template.njk +2 -1
  109. data/node_modules/govuk-frontend/govuk/components/inset-text/fixtures.json +1 -1
  110. data/node_modules/govuk-frontend/govuk/components/inset-text/macro-options.json +1 -1
  111. data/node_modules/govuk-frontend/govuk/components/label/fixtures.json +6 -6
  112. data/node_modules/govuk-frontend/govuk/components/label/macro-options.json +1 -1
  113. data/node_modules/govuk-frontend/govuk/components/notification-banner/fixtures.json +1 -1
  114. data/node_modules/govuk-frontend/govuk/components/notification-banner/macro-options.json +1 -1
  115. data/node_modules/govuk-frontend/govuk/components/notification-banner/notification-banner.js +49 -22
  116. data/node_modules/govuk-frontend/govuk/components/notification-banner/notification-banner.js.map +1 -1
  117. data/node_modules/govuk-frontend/govuk/components/pagination/fixtures.json +16 -16
  118. data/node_modules/govuk-frontend/govuk/components/pagination/macro-options.json +1 -1
  119. data/node_modules/govuk-frontend/govuk/components/panel/fixtures.json +10 -10
  120. data/node_modules/govuk-frontend/govuk/components/panel/macro-options.json +1 -1
  121. data/node_modules/govuk-frontend/govuk/components/phase-banner/fixtures.json +1 -1
  122. data/node_modules/govuk-frontend/govuk/components/phase-banner/macro-options.json +1 -1
  123. data/node_modules/govuk-frontend/govuk/components/radios/_index.scss +5 -5
  124. data/node_modules/govuk-frontend/govuk/components/radios/fixtures.json +46 -46
  125. data/node_modules/govuk-frontend/govuk/components/radios/macro-options.json +1 -1
  126. data/node_modules/govuk-frontend/govuk/components/radios/radios.js +65 -22
  127. data/node_modules/govuk-frontend/govuk/components/radios/radios.js.map +1 -1
  128. data/node_modules/govuk-frontend/govuk/components/radios/template.njk +2 -2
  129. data/node_modules/govuk-frontend/govuk/components/select/_index.scss +7 -1
  130. data/node_modules/govuk-frontend/govuk/components/select/fixtures.json +19 -20
  131. data/node_modules/govuk-frontend/govuk/components/select/macro-options.json +7 -1
  132. data/node_modules/govuk-frontend/govuk/components/select/template.njk +5 -2
  133. data/node_modules/govuk-frontend/govuk/components/skip-link/fixtures.json +1 -1
  134. data/node_modules/govuk-frontend/govuk/components/skip-link/macro-options.json +1 -1
  135. data/node_modules/govuk-frontend/govuk/components/skip-link/skip-link.js +41 -13
  136. data/node_modules/govuk-frontend/govuk/components/skip-link/skip-link.js.map +1 -1
  137. data/node_modules/govuk-frontend/govuk/components/summary-list/fixtures.json +34 -34
  138. data/node_modules/govuk-frontend/govuk/components/summary-list/macro-options.json +1 -1
  139. data/node_modules/govuk-frontend/govuk/components/table/fixtures.json +21 -21
  140. data/node_modules/govuk-frontend/govuk/components/table/macro-options.json +1 -1
  141. data/node_modules/govuk-frontend/govuk/components/tabs/fixtures.json +15 -15
  142. data/node_modules/govuk-frontend/govuk/components/tabs/macro-options.json +1 -1
  143. data/node_modules/govuk-frontend/govuk/components/tabs/tabs.js +178 -60
  144. data/node_modules/govuk-frontend/govuk/components/tabs/tabs.js.map +1 -1
  145. data/node_modules/govuk-frontend/govuk/components/tabs/template.njk +1 -1
  146. data/node_modules/govuk-frontend/govuk/components/tag/_index.scss +18 -18
  147. data/node_modules/govuk-frontend/govuk/components/tag/fixtures.json +1 -1
  148. data/node_modules/govuk-frontend/govuk/components/tag/macro-options.json +1 -1
  149. data/node_modules/govuk-frontend/govuk/components/textarea/_index.scss +8 -1
  150. data/node_modules/govuk-frontend/govuk/components/textarea/fixtures.json +18 -18
  151. data/node_modules/govuk-frontend/govuk/components/textarea/macro-options.json +7 -1
  152. data/node_modules/govuk-frontend/govuk/components/textarea/template.njk +2 -1
  153. data/node_modules/govuk-frontend/govuk/components/warning-text/fixtures.json +9 -5
  154. data/node_modules/govuk-frontend/govuk/components/warning-text/macro-options.json +3 -3
  155. data/node_modules/govuk-frontend/govuk/components/warning-text/template.njk +1 -1
  156. data/node_modules/govuk-frontend/govuk/core/_all.scss +1 -0
  157. data/node_modules/govuk-frontend/govuk/core/_govuk-frontend-version.scss +5 -0
  158. data/node_modules/govuk-frontend/govuk/helpers/_colour.scss +5 -2
  159. data/node_modules/govuk-frontend/govuk/helpers/_focused.scss +1 -1
  160. data/node_modules/govuk-frontend/govuk/helpers/_font-faces.scss +1 -1
  161. data/node_modules/govuk-frontend/govuk/i18n.js +38 -30
  162. data/node_modules/govuk-frontend/govuk/i18n.js.map +1 -1
  163. data/node_modules/govuk-frontend/govuk/macros/i18n.njk +12 -11
  164. data/node_modules/govuk-frontend/govuk/objects/_width-container.scss +1 -1
  165. data/node_modules/govuk-frontend/govuk/settings/_colours-organisations.scss +4 -0
  166. data/node_modules/govuk-frontend/govuk/settings/_ie8.scss +16 -0
  167. data/node_modules/govuk-frontend/govuk/settings/_links.scss +5 -1
  168. data/node_modules/govuk-frontend/govuk/tools/_ie8.scss +38 -2
  169. data/node_modules/govuk-frontend/govuk/vendor/polyfills/DOMTokenList.js +2 -1
  170. data/node_modules/govuk-frontend/govuk/vendor/polyfills/DOMTokenList.js.map +1 -1
  171. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Date/now.js +2 -1
  172. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Date/now.js.map +1 -1
  173. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Document.js +2 -1
  174. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Document.js.map +1 -1
  175. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Element/prototype/classList.js +10 -3
  176. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Element/prototype/classList.js.map +1 -1
  177. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Element/prototype/closest.js +4 -1
  178. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Element/prototype/closest.js.map +1 -1
  179. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Element/prototype/dataset.js +13 -7
  180. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Element/prototype/dataset.js.map +1 -1
  181. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Element/prototype/matches.js +2 -1
  182. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Element/prototype/matches.js.map +1 -1
  183. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Element/prototype/nextElementSibling.js +8 -2
  184. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Element/prototype/nextElementSibling.js.map +1 -1
  185. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Element/prototype/previousElementSibling.js +8 -2
  186. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Element/prototype/previousElementSibling.js.map +1 -1
  187. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Element.js +4 -1
  188. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Element.js.map +1 -1
  189. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Event.js +10 -3
  190. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Event.js.map +1 -1
  191. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Function/prototype/bind.js +4 -1
  192. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Function/prototype/bind.js.map +1 -1
  193. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Object/defineProperty.js +2 -1
  194. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Object/defineProperty.js.map +1 -1
  195. data/node_modules/govuk-frontend/govuk/vendor/polyfills/String/prototype/trim.js +3 -2
  196. data/node_modules/govuk-frontend/govuk/vendor/polyfills/String/prototype/trim.js.map +1 -1
  197. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Window.js +2 -1
  198. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Window.js.map +1 -1
  199. data/node_modules/govuk-frontend/govuk-esm/all.mjs +81 -60
  200. data/node_modules/govuk-frontend/govuk-esm/all.mjs.map +1 -0
  201. data/node_modules/govuk-frontend/govuk-esm/common/closest-attribute-value.mjs +12 -8
  202. data/node_modules/govuk-frontend/govuk-esm/common/closest-attribute-value.mjs.map +1 -0
  203. data/node_modules/govuk-frontend/govuk-esm/common/govuk-frontend-version.mjs +9 -0
  204. data/node_modules/govuk-frontend/govuk-esm/common/govuk-frontend-version.mjs.map +1 -0
  205. data/node_modules/govuk-frontend/govuk-esm/common/index.mjs +44 -29
  206. data/node_modules/govuk-frontend/govuk-esm/common/index.mjs.map +1 -0
  207. data/node_modules/govuk-frontend/govuk-esm/common/normalise-dataset.mjs +15 -9
  208. data/node_modules/govuk-frontend/govuk-esm/common/normalise-dataset.mjs.map +1 -0
  209. data/node_modules/govuk-frontend/govuk-esm/common.mjs +3 -5
  210. data/node_modules/govuk-frontend/govuk-esm/common.mjs.map +1 -0
  211. data/node_modules/govuk-frontend/govuk-esm/components/accordion/accordion.mjs +319 -211
  212. data/node_modules/govuk-frontend/govuk-esm/components/accordion/accordion.mjs.map +1 -0
  213. data/node_modules/govuk-frontend/govuk-esm/components/button/button.mjs +47 -30
  214. data/node_modules/govuk-frontend/govuk-esm/components/button/button.mjs.map +1 -0
  215. data/node_modules/govuk-frontend/govuk-esm/components/character-count/character-count.mjs +169 -115
  216. data/node_modules/govuk-frontend/govuk-esm/components/character-count/character-count.mjs.map +1 -0
  217. data/node_modules/govuk-frontend/govuk-esm/components/checkboxes/checkboxes.mjs +88 -51
  218. data/node_modules/govuk-frontend/govuk-esm/components/checkboxes/checkboxes.mjs.map +1 -0
  219. data/node_modules/govuk-frontend/govuk-esm/components/details/details.mjs +64 -54
  220. data/node_modules/govuk-frontend/govuk-esm/components/details/details.mjs.map +1 -0
  221. data/node_modules/govuk-frontend/govuk-esm/components/error-summary/error-summary.mjs +69 -50
  222. data/node_modules/govuk-frontend/govuk-esm/components/error-summary/error-summary.mjs.map +1 -0
  223. data/node_modules/govuk-frontend/govuk-esm/components/header/header.mjs +59 -35
  224. data/node_modules/govuk-frontend/govuk-esm/components/header/header.mjs.map +1 -0
  225. data/node_modules/govuk-frontend/govuk-esm/components/notification-banner/notification-banner.mjs +36 -25
  226. data/node_modules/govuk-frontend/govuk-esm/components/notification-banner/notification-banner.mjs.map +1 -0
  227. data/node_modules/govuk-frontend/govuk-esm/components/radios/radios.mjs +67 -38
  228. data/node_modules/govuk-frontend/govuk-esm/components/radios/radios.mjs.map +1 -0
  229. data/node_modules/govuk-frontend/govuk-esm/components/skip-link/skip-link.mjs +49 -32
  230. data/node_modules/govuk-frontend/govuk-esm/components/skip-link/skip-link.mjs.map +1 -0
  231. data/node_modules/govuk-frontend/govuk-esm/components/tabs/tabs.mjs +263 -163
  232. data/node_modules/govuk-frontend/govuk-esm/components/tabs/tabs.mjs.map +1 -0
  233. data/node_modules/govuk-frontend/govuk-esm/i18n.mjs +68 -57
  234. data/node_modules/govuk-frontend/govuk-esm/i18n.mjs.map +1 -0
  235. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/DOMTokenList.mjs +5 -3
  236. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/DOMTokenList.mjs.map +1 -0
  237. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Date/now.mjs +4 -2
  238. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Date/now.mjs.map +1 -0
  239. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Document.mjs +4 -2
  240. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Document.mjs.map +1 -0
  241. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Element/prototype/classList.mjs +7 -4
  242. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Element/prototype/classList.mjs.map +1 -0
  243. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Element/prototype/closest.mjs +5 -2
  244. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Element/prototype/closest.mjs.map +1 -0
  245. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Element/prototype/dataset.mjs +13 -10
  246. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Element/prototype/dataset.mjs.map +1 -0
  247. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Element/prototype/matches.mjs +4 -2
  248. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Element/prototype/matches.mjs.map +1 -0
  249. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Element/prototype/nextElementSibling.mjs +6 -3
  250. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Element/prototype/nextElementSibling.mjs.map +1 -0
  251. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Element/prototype/previousElementSibling.mjs +6 -3
  252. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Element/prototype/previousElementSibling.mjs.map +1 -0
  253. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Element.mjs +5 -2
  254. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Element.mjs.map +1 -0
  255. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Event.mjs +7 -4
  256. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Event.mjs.map +1 -0
  257. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Function/prototype/bind.mjs +5 -2
  258. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Function/prototype/bind.mjs.map +1 -0
  259. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Object/defineProperty.mjs +4 -2
  260. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Object/defineProperty.mjs.map +1 -0
  261. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/String/prototype/trim.mjs +5 -3
  262. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/String/prototype/trim.mjs.map +1 -0
  263. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Window.mjs +4 -2
  264. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Window.mjs.map +1 -0
  265. data/node_modules/govuk-frontend/govuk-prototype-kit/init.js +1 -0
  266. data/node_modules/govuk-frontend/govuk-prototype-kit.config.json +2 -1
  267. data/node_modules/govuk-frontend/package.json +3 -2
  268. metadata +44 -7
  269. /data/app/assets/images/govuk_publishing_components/crests/{dit_crest_13px.png → dbt_crest_13px.png} +0 -0
  270. /data/app/assets/images/govuk_publishing_components/crests/{dit_crest_13px_x2.png → dbt_crest_13px_x2.png} +0 -0
  271. /data/app/assets/images/govuk_publishing_components/crests/{dit_crest_18px.png → dbt_crest_18px.png} +0 -0
  272. /data/app/assets/images/govuk_publishing_components/crests/{dit_crest_18px_x2.png → dbt_crest_18px_x2.png} +0 -0
@@ -4,6 +4,13 @@
4
4
  (factory((global.GOVUKFrontend = {})));
5
5
  }(this, (function (exports) { 'use strict';
6
6
 
7
+ /*
8
+ * This variable is automatically overwritten during builds and releases.
9
+ * It doesn't need to be updated manually.
10
+ */
11
+
12
+ var version = '4.6.0';
13
+
7
14
  /**
8
15
  * Common helpers which do not require polyfill.
9
16
  *
@@ -19,8 +26,10 @@
19
26
  * This seems to fail in IE8, requires more investigation.
20
27
  * See: https://github.com/imagitama/nodelist-foreach-polyfill
21
28
  *
22
- * @param {NodeListOf<Element>} nodes - NodeList from querySelectorAll()
23
- * @param {nodeListIterator} callback - Callback function to run for each node
29
+ * @deprecated Will be made private in v5.0
30
+ * @template {Node} ElementType
31
+ * @param {NodeListOf<ElementType>} nodes - NodeList from querySelectorAll()
32
+ * @param {nodeListIterator<ElementType>} callback - Callback function to run for each node
24
33
  * @returns {void}
25
34
  */
26
35
  function nodeListForEach (nodes, callback) {
@@ -37,6 +46,7 @@
37
46
  * without them conflicting with each other.
38
47
  * https://stackoverflow.com/a/8809472
39
48
  *
49
+ * @deprecated Will be made private in v5.0
40
50
  * @returns {string} Unique ID
41
51
  */
42
52
  function generateUniqueID () {
@@ -58,6 +68,7 @@
58
68
  * (e.g. {'i18n.showSection': 'Show section'}) and combines them together, with
59
69
  * greatest priority on the LAST item passed in.
60
70
  *
71
+ * @deprecated Will be made private in v5.0
61
72
  * @returns {Object<string, unknown>} A flattened object of key-value pairs.
62
73
  */
63
74
  function mergeConfigs (/* configObject1, configObject2, ...configObjects */) {
@@ -72,6 +83,7 @@
72
83
  */
73
84
  var flattenObject = function (configObject) {
74
85
  // Prepare an empty return object
86
+ /** @type {Object<string, unknown>} */
75
87
  var flattenedObject = {};
76
88
 
77
89
  /**
@@ -108,6 +120,7 @@
108
120
  };
109
121
 
110
122
  // Start with an empty object as our base
123
+ /** @type {Object<string, unknown>} */
111
124
  var formattedConfigObject = {};
112
125
 
113
126
  // Loop through each of the remaining passed objects and push their keys
@@ -129,6 +142,7 @@
129
142
  * Extracts keys starting with a particular namespace from a flattened config
130
143
  * object, removing the namespace in the process.
131
144
  *
145
+ * @deprecated Will be made private in v5.0
132
146
  * @param {Object<string, unknown>} configObject - The object to extract key-value pairs from.
133
147
  * @param {string} namespace - The namespace to filter keys with.
134
148
  * @returns {Object<string, unknown>} Flattened object with dot-separated key namespace removed
@@ -140,10 +154,14 @@
140
154
  if (!configObject || typeof configObject !== 'object') {
141
155
  throw new Error('Provide a `configObject` of type "object".')
142
156
  }
157
+
143
158
  if (!namespace || typeof namespace !== 'string') {
144
159
  throw new Error('Provide a `namespace` of type "string" to filter the `configObject` by.')
145
160
  }
161
+
162
+ /** @type {Object<string, unknown>} */
146
163
  var newObject = {};
164
+
147
165
  for (var key in configObject) {
148
166
  // Split the key into parts, using . as our namespace separator
149
167
  var keyParts = key.split('.');
@@ -164,14 +182,16 @@
164
182
  }
165
183
 
166
184
  /**
185
+ * @template {Node} ElementType
167
186
  * @callback nodeListIterator
168
- * @param {Element} value - The current node being iterated on
187
+ * @param {ElementType} value - The current node being iterated on
169
188
  * @param {number} index - The current index in the iteration
170
- * @param {NodeListOf<Element>} nodes - NodeList from querySelectorAll()
189
+ * @param {NodeListOf<ElementType>} nodes - NodeList from querySelectorAll()
171
190
  * @returns {void}
172
191
  */
173
192
 
174
- (function(undefined) {
193
+ // @ts-nocheck
194
+ (function (undefined) {
175
195
 
176
196
  // Detection from https://github.com/Financial-Times/polyfill-service/blob/master/packages/polyfill-library/polyfills/Object/defineProperty/detect.js
177
197
  var detect = (
@@ -258,7 +278,8 @@
258
278
  })
259
279
  .call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
260
280
 
261
- (function(undefined) {
281
+ // @ts-nocheck
282
+ (function (undefined) {
262
283
 
263
284
  // Detection from https://github.com/Financial-Times/polyfill-service/blob/master/packages/polyfill-library/polyfills/Document/detect.js
264
285
  var detect = ("Document" in this);
@@ -285,6 +306,8 @@
285
306
  })
286
307
  .call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
287
308
 
309
+ // @ts-nocheck
310
+
288
311
  (function(undefined) {
289
312
 
290
313
  // Detection from https://github.com/Financial-Times/polyfill-service/blob/master/packages/polyfill-library/polyfills/Element/detect.js
@@ -398,6 +421,8 @@
398
421
  })
399
422
  .call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
400
423
 
424
+ // @ts-nocheck
425
+
401
426
  (function(undefined) {
402
427
 
403
428
  // Detection from https://raw.githubusercontent.com/Financial-Times/polyfill-library/13cf7c340974d128d557580b5e2dafcd1b1192d1/polyfills/Element/prototype/dataset/detect.js
@@ -418,10 +443,10 @@
418
443
  var element = this;
419
444
  var attributes = this.attributes;
420
445
  var map = {};
421
-
446
+
422
447
  for (var i = 0; i < attributes.length; i++) {
423
448
  var attribute = attributes[i];
424
-
449
+
425
450
  // This regex has been edited from the original polyfill, to add
426
451
  // support for period (.) separators in data-* attribute names. These
427
452
  // are allowed in the HTML spec, but were not covered by the original
@@ -429,11 +454,11 @@
429
454
  if (attribute && attribute.name && (/^data-\w[.\w-]*$/).test(attribute.name)) {
430
455
  var name = attribute.name;
431
456
  var value = attribute.value;
432
-
457
+
433
458
  var propName = name.substr(5).replace(/-./g, function (prop) {
434
459
  return prop.charAt(1).toUpperCase();
435
460
  });
436
-
461
+
437
462
  // If this browser supports __defineGetter__ and __defineSetter__,
438
463
  // continue using defineProperty. If not (like IE 8 and below), we use
439
464
  // a hacky fallback which at least gives an object in the right format
@@ -457,18 +482,19 @@
457
482
 
458
483
  }
459
484
  }
460
-
485
+
461
486
  return map;
462
487
  }
463
488
  });
464
489
 
465
490
  }).call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
466
491
 
467
- (function(undefined) {
492
+ // @ts-nocheck
493
+ (function (undefined) {
468
494
 
469
495
  // Detection from https://github.com/mdn/content/blob/cf607d68522cd35ee7670782d3ee3a361eaef2e4/files/en-us/web/javascript/reference/global_objects/string/trim/index.md#polyfill
470
496
  var detect = ('trim' in String.prototype);
471
-
497
+
472
498
  if (detect) return
473
499
 
474
500
  // Polyfill from https://github.com/mdn/content/blob/cf607d68522cd35ee7670782d3ee3a361eaef2e4/files/en-us/web/javascript/reference/global_objects/string/trim/index.md#polyfill
@@ -491,6 +517,7 @@
491
517
  * Designed to be used to convert config passed via data attributes (which are
492
518
  * always strings) into something sensible.
493
519
  *
520
+ * @deprecated Will be made private in v5.0
494
521
  * @param {string} value - The value to normalise
495
522
  * @returns {string | boolean | number | undefined} Normalised data
496
523
  */
@@ -511,7 +538,7 @@
511
538
 
512
539
  // Empty / whitespace-only strings are considered finite so we need to check
513
540
  // the length of the trimmed string as well
514
- if (trimmedValue.length > 0 && isFinite(trimmedValue)) {
541
+ if (trimmedValue.length > 0 && isFinite(Number(trimmedValue))) {
515
542
  return Number(trimmedValue)
516
543
  }
517
544
 
@@ -523,10 +550,12 @@
523
550
  *
524
551
  * Loop over an object and normalise each value using normaliseData function
525
552
  *
553
+ * @deprecated Will be made private in v5.0
526
554
  * @param {DOMStringMap} dataset - HTML element dataset
527
555
  * @returns {Object<string, unknown>} Normalised dataset
528
556
  */
529
557
  function normaliseDataset (dataset) {
558
+ /** @type {Object<string, unknown>} */
530
559
  var out = {};
531
560
 
532
561
  for (var key in dataset) {
@@ -571,17 +600,17 @@
571
600
  }
572
601
 
573
602
  // If the `count` option is set, determine which plural suffix is needed and
574
- // change the lookupKey to match. We check to see if it's undefined instead of
603
+ // change the lookupKey to match. We check to see if it's numeric instead of
575
604
  // falsy, as this could legitimately be 0.
576
- if (options && typeof options.count !== 'undefined') {
605
+ if (options && typeof options.count === 'number') {
577
606
  // Get the plural suffix
578
607
  lookupKey = lookupKey + '.' + this.getPluralSuffix(lookupKey, options.count);
579
608
  }
580
609
 
581
- if (lookupKey in this.translations) {
582
- // Fetch the translation string for that lookup key
583
- var translationString = this.translations[lookupKey];
610
+ // Fetch the translation string for that lookup key
611
+ var translationString = this.translations[lookupKey];
584
612
 
613
+ if (typeof translationString === 'string') {
585
614
  // Check for ${} placeholders in the translation string
586
615
  if (translationString.match(/%{(.\S+)}/)) {
587
616
  if (!options) {
@@ -608,32 +637,46 @@
608
637
  * @returns {string} The translation string to output, with ${} placeholders replaced
609
638
  */
610
639
  I18n.prototype.replacePlaceholders = function (translationString, options) {
640
+ /** @type {Intl.NumberFormat | undefined} */
611
641
  var formatter;
612
642
 
613
643
  if (this.hasIntlNumberFormatSupport()) {
614
644
  formatter = new Intl.NumberFormat(this.locale);
615
645
  }
616
646
 
617
- return translationString.replace(/%{(.\S+)}/g, function (placeholderWithBraces, placeholderKey) {
618
- if (Object.prototype.hasOwnProperty.call(options, placeholderKey)) {
619
- var placeholderValue = options[placeholderKey];
647
+ return translationString.replace(
648
+ /%{(.\S+)}/g,
620
649
 
621
- // If a user has passed `false` as the value for the placeholder
622
- // treat it as though the value should not be displayed
623
- if (placeholderValue === false) {
624
- return ''
625
- }
650
+ /**
651
+ * Replace translation string placeholders
652
+ *
653
+ * @param {string} placeholderWithBraces - Placeholder with braces
654
+ * @param {string} placeholderKey - Placeholder key
655
+ * @returns {string} Placeholder value
656
+ */
657
+ function (placeholderWithBraces, placeholderKey) {
658
+ if (Object.prototype.hasOwnProperty.call(options, placeholderKey)) {
659
+ var placeholderValue = options[placeholderKey];
660
+
661
+ // If a user has passed `false` as the value for the placeholder
662
+ // treat it as though the value should not be displayed
663
+ if (placeholderValue === false || (
664
+ typeof placeholderValue !== 'number' &&
665
+ typeof placeholderValue !== 'string')
666
+ ) {
667
+ return ''
668
+ }
626
669
 
627
- // If the placeholder's value is a number, localise the number formatting
628
- if (typeof placeholderValue === 'number' && formatter) {
629
- return formatter.format(placeholderValue)
630
- }
670
+ // If the placeholder's value is a number, localise the number formatting
671
+ if (typeof placeholderValue === 'number') {
672
+ return formatter ? formatter.format(placeholderValue) : placeholderValue.toString()
673
+ }
631
674
 
632
- return placeholderValue
633
- } else {
634
- throw new Error('i18n: no data found to replace ' + placeholderWithBraces + ' placeholder in string')
635
- }
636
- })
675
+ return placeholderValue
676
+ } else {
677
+ throw new Error('i18n: no data found to replace ' + placeholderWithBraces + ' placeholder in string')
678
+ }
679
+ })
637
680
  };
638
681
 
639
682
  /**
@@ -749,7 +792,7 @@
749
792
  * regardless of region. There are exceptions, however, (e.g. Portuguese) so
750
793
  * this searches by both the full and shortened locale codes, just to be sure.
751
794
  *
752
- * @returns {PluralRuleName | undefined} The name of the pluralisation rule to use (a key for one
795
+ * @returns {string | undefined} The name of the pluralisation rule to use (a key for one
753
796
  * of the functions in this.pluralRules)
754
797
  */
755
798
  I18n.prototype.getPluralRulesForLocale = function () {
@@ -800,7 +843,7 @@
800
843
  * Spanish: European Portuguese (pt-PT), Italian (it), Spanish (es)
801
844
  * Welsh: Welsh (cy)
802
845
  *
803
- * @type {Object<PluralRuleName, string[]>}
846
+ * @type {Object<string, string[]>}
804
847
  */
805
848
  I18n.pluralRulesMap = {
806
849
  arabic: ['ar'],
@@ -888,12 +931,6 @@
888
931
  /* eslint-enable jsdoc/require-jsdoc */
889
932
  };
890
933
 
891
- /**
892
- * Supported languages for plural rules
893
- *
894
- * @typedef {'arabic' | 'chinese' | 'french' | 'german' | 'irish' | 'russian' | 'scottish' | 'spanish' | 'welsh'} PluralRuleName
895
- */
896
-
897
934
  /**
898
935
  * Plural rule category mnemonic tags
899
936
  *
@@ -915,7 +952,8 @@
915
952
  * @property {string} [many] - Plural form used for many
916
953
  */
917
954
 
918
- (function(undefined) {
955
+ // @ts-nocheck
956
+ (function (undefined) {
919
957
 
920
958
  // Detection from https://raw.githubusercontent.com/Financial-Times/polyfill-service/master/packages/polyfill-library/polyfills/DOMTokenList/detect.js
921
959
  var detect = (
@@ -1180,6 +1218,8 @@
1180
1218
 
1181
1219
  }).call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
1182
1220
 
1221
+ // @ts-nocheck
1222
+
1183
1223
  (function(undefined) {
1184
1224
 
1185
1225
  // Detection from https://raw.githubusercontent.com/Financial-Times/polyfill-service/8717a9e04ac7aff99b4980fbedead98036b0929a/packages/polyfill-library/polyfills/Element/prototype/classList/detect.js
@@ -1270,7 +1310,8 @@
1270
1310
 
1271
1311
  }).call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
1272
1312
 
1273
- (function(undefined) {
1313
+ // @ts-nocheck
1314
+ (function (undefined) {
1274
1315
 
1275
1316
  // Detection from https://raw.githubusercontent.com/Financial-Times/polyfill-service/1f3c09b402f65bf6e393f933a15ba63f1b86ef1f/packages/polyfill-library/polyfills/Element/prototype/matches/detect.js
1276
1317
  var detect = (
@@ -1294,6 +1335,8 @@
1294
1335
 
1295
1336
  }).call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
1296
1337
 
1338
+ // @ts-nocheck
1339
+
1297
1340
  (function(undefined) {
1298
1341
 
1299
1342
  // Detection from https://raw.githubusercontent.com/Financial-Times/polyfill-service/1f3c09b402f65bf6e393f933a15ba63f1b86ef1f/packages/polyfill-library/polyfills/Element/prototype/closest/detect.js
@@ -1317,7 +1360,8 @@
1317
1360
 
1318
1361
  }).call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
1319
1362
 
1320
- (function(undefined) {
1363
+ // @ts-nocheck
1364
+ (function (undefined) {
1321
1365
 
1322
1366
  // Detection from https://github.com/Financial-Times/polyfill-service/blob/master/packages/polyfill-library/polyfills/Window/detect.js
1323
1367
  var detect = ('Window' in this);
@@ -1338,6 +1382,8 @@
1338
1382
  })
1339
1383
  .call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
1340
1384
 
1385
+ // @ts-nocheck
1386
+
1341
1387
  (function(undefined) {
1342
1388
 
1343
1389
  // Detection from https://github.com/Financial-Times/polyfill-service/blob/master/packages/polyfill-library/polyfills/Event/detect.js
@@ -1587,6 +1633,8 @@
1587
1633
  })
1588
1634
  .call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
1589
1635
 
1636
+ // @ts-nocheck
1637
+
1590
1638
  (function(undefined) {
1591
1639
  // Detection from https://github.com/Financial-Times/polyfill-service/blob/master/packages/polyfill-library/polyfills/Function/prototype/bind/detect.js
1592
1640
  var detect = 'bind' in Function.prototype;
@@ -1775,57 +1823,119 @@
1775
1823
  * attribute, which also provides accessibility.
1776
1824
  *
1777
1825
  * @class
1778
- * @param {HTMLElement} $module - HTML element to use for accordion
1826
+ * @param {Element} $module - HTML element to use for accordion
1779
1827
  * @param {AccordionConfig} [config] - Accordion config
1780
1828
  */
1781
1829
  function Accordion ($module, config) {
1830
+ if (!($module instanceof HTMLElement)) {
1831
+ return this
1832
+ }
1833
+
1834
+ /** @deprecated Will be made private in v5.0 */
1782
1835
  this.$module = $module;
1783
1836
 
1784
1837
  var defaultConfig = {
1785
- i18n: ACCORDION_TRANSLATIONS
1838
+ i18n: ACCORDION_TRANSLATIONS,
1839
+ rememberExpanded: true
1786
1840
  };
1787
1841
 
1842
+ /**
1843
+ * @deprecated Will be made private in v5.0
1844
+ * @type {AccordionConfig}
1845
+ */
1788
1846
  this.config = mergeConfigs(
1789
1847
  defaultConfig,
1790
1848
  config || {},
1791
1849
  normaliseDataset($module.dataset)
1792
1850
  );
1793
1851
 
1852
+ /** @deprecated Will be made private in v5.0 */
1794
1853
  this.i18n = new I18n(extractConfigByNamespace(this.config, 'i18n'));
1795
1854
 
1855
+ /** @deprecated Will be made private in v5.0 */
1796
1856
  this.controlsClass = 'govuk-accordion__controls';
1857
+
1858
+ /** @deprecated Will be made private in v5.0 */
1797
1859
  this.showAllClass = 'govuk-accordion__show-all';
1860
+
1861
+ /** @deprecated Will be made private in v5.0 */
1798
1862
  this.showAllTextClass = 'govuk-accordion__show-all-text';
1799
1863
 
1864
+ /** @deprecated Will be made private in v5.0 */
1800
1865
  this.sectionClass = 'govuk-accordion__section';
1866
+
1867
+ /** @deprecated Will be made private in v5.0 */
1801
1868
  this.sectionExpandedClass = 'govuk-accordion__section--expanded';
1869
+
1870
+ /** @deprecated Will be made private in v5.0 */
1802
1871
  this.sectionButtonClass = 'govuk-accordion__section-button';
1872
+
1873
+ /** @deprecated Will be made private in v5.0 */
1803
1874
  this.sectionHeaderClass = 'govuk-accordion__section-header';
1875
+
1876
+ /** @deprecated Will be made private in v5.0 */
1804
1877
  this.sectionHeadingClass = 'govuk-accordion__section-heading';
1878
+
1879
+ /** @deprecated Will be made private in v5.0 */
1805
1880
  this.sectionHeadingDividerClass = 'govuk-accordion__section-heading-divider';
1881
+
1882
+ /** @deprecated Will be made private in v5.0 */
1806
1883
  this.sectionHeadingTextClass = 'govuk-accordion__section-heading-text';
1884
+
1885
+ /** @deprecated Will be made private in v5.0 */
1807
1886
  this.sectionHeadingTextFocusClass = 'govuk-accordion__section-heading-text-focus';
1808
1887
 
1888
+ /** @deprecated Will be made private in v5.0 */
1809
1889
  this.sectionShowHideToggleClass = 'govuk-accordion__section-toggle';
1890
+
1891
+ /** @deprecated Will be made private in v5.0 */
1810
1892
  this.sectionShowHideToggleFocusClass = 'govuk-accordion__section-toggle-focus';
1893
+
1894
+ /** @deprecated Will be made private in v5.0 */
1811
1895
  this.sectionShowHideTextClass = 'govuk-accordion__section-toggle-text';
1896
+
1897
+ /** @deprecated Will be made private in v5.0 */
1812
1898
  this.upChevronIconClass = 'govuk-accordion-nav__chevron';
1899
+
1900
+ /** @deprecated Will be made private in v5.0 */
1813
1901
  this.downChevronIconClass = 'govuk-accordion-nav__chevron--down';
1814
1902
 
1903
+ /** @deprecated Will be made private in v5.0 */
1815
1904
  this.sectionSummaryClass = 'govuk-accordion__section-summary';
1905
+
1906
+ /** @deprecated Will be made private in v5.0 */
1816
1907
  this.sectionSummaryFocusClass = 'govuk-accordion__section-summary-focus';
1908
+
1909
+ /** @deprecated Will be made private in v5.0 */
1817
1910
  this.sectionContentClass = 'govuk-accordion__section-content';
1818
1911
 
1819
- this.$sections = this.$module.querySelectorAll('.' + this.sectionClass);
1912
+ var $sections = this.$module.querySelectorAll('.' + this.sectionClass);
1913
+ if (!$sections.length) {
1914
+ return this
1915
+ }
1916
+
1917
+ /** @deprecated Will be made private in v5.0 */
1918
+ this.$sections = $sections;
1919
+
1920
+ /** @deprecated Will be made private in v5.0 */
1820
1921
  this.browserSupportsSessionStorage = helper.checkForSessionStorage();
1922
+
1923
+ /** @deprecated Will be made private in v5.0 */
1924
+ this.$showAllButton = null;
1925
+
1926
+ /** @deprecated Will be made private in v5.0 */
1927
+ this.$showAllIcon = null;
1928
+
1929
+ /** @deprecated Will be made private in v5.0 */
1930
+ this.$showAllText = null;
1821
1931
  }
1822
1932
 
1823
1933
  /**
1824
1934
  * Initialise component
1825
1935
  */
1826
1936
  Accordion.prototype.init = function () {
1827
- // Check for module
1828
- if (!this.$module) {
1937
+ // Check that required elements are present
1938
+ if (!this.$module || !this.$sections) {
1829
1939
  return
1830
1940
  }
1831
1941
 
@@ -1839,6 +1949,8 @@
1839
1949
 
1840
1950
  /**
1841
1951
  * Initialise controls and set attributes
1952
+ *
1953
+ * @deprecated Will be made private in v5.0
1842
1954
  */
1843
1955
  Accordion.prototype.initControls = function () {
1844
1956
  // Create "Show all" button and set attributes
@@ -1874,28 +1986,38 @@
1874
1986
 
1875
1987
  /**
1876
1988
  * Initialise section headers
1989
+ *
1990
+ * @deprecated Will be made private in v5.0
1877
1991
  */
1878
1992
  Accordion.prototype.initSectionHeaders = function () {
1879
- // Loop through section headers
1880
- nodeListForEach(this.$sections, function ($section, i) {
1993
+ var $component = this;
1994
+ var $sections = this.$sections;
1995
+
1996
+ // Loop through sections
1997
+ nodeListForEach($sections, function ($section, i) {
1998
+ var $header = $section.querySelector('.' + $component.sectionHeaderClass);
1999
+ if (!$header) {
2000
+ return
2001
+ }
2002
+
1881
2003
  // Set header attributes
1882
- var $header = $section.querySelector('.' + this.sectionHeaderClass);
1883
- this.constructHeaderMarkup($header, i);
1884
- this.setExpanded(this.isExpanded($section), $section);
2004
+ $component.constructHeaderMarkup($header, i);
2005
+ $component.setExpanded($component.isExpanded($section), $section);
1885
2006
 
1886
2007
  // Handle events
1887
- $header.addEventListener('click', this.onSectionToggle.bind(this, $section));
2008
+ $header.addEventListener('click', $component.onSectionToggle.bind($component, $section));
1888
2009
 
1889
2010
  // See if there is any state stored in sessionStorage and set the sections to
1890
2011
  // open or closed.
1891
- this.setInitialState($section);
1892
- }.bind(this));
2012
+ $component.setInitialState($section);
2013
+ });
1893
2014
  };
1894
2015
 
1895
2016
  /**
1896
2017
  * Construct section header
1897
2018
  *
1898
- * @param {HTMLDivElement} $header - Section header
2019
+ * @deprecated Will be made private in v5.0
2020
+ * @param {Element} $header - Section header
1899
2021
  * @param {number} index - Section index
1900
2022
  */
1901
2023
  Accordion.prototype.constructHeaderMarkup = function ($header, index) {
@@ -1903,10 +2025,14 @@
1903
2025
  var $heading = $header.querySelector('.' + this.sectionHeadingClass);
1904
2026
  var $summary = $header.querySelector('.' + this.sectionSummaryClass);
1905
2027
 
2028
+ if (!$span || !$heading) {
2029
+ return
2030
+ }
2031
+
1906
2032
  // Create a button element that will replace the '.govuk-accordion__section-button' span
1907
2033
  var $button = document.createElement('button');
1908
2034
  $button.setAttribute('type', 'button');
1909
- $button.setAttribute('aria-controls', this.$module.id + '-content-' + (index + 1));
2035
+ $button.setAttribute('aria-controls', this.$module.id + '-content-' + (index + 1).toString());
1910
2036
 
1911
2037
  // Copy all attributes (https://developer.mozilla.org/en-US/docs/Web/API/Element/attributes) from $span to $button
1912
2038
  for (var i = 0; i < $span.attributes.length; i++) {
@@ -1960,7 +2086,7 @@
1960
2086
  $button.appendChild(this.getButtonPunctuationEl());
1961
2087
 
1962
2088
  // If summary content exists add to DOM in correct order
1963
- if (typeof ($summary) !== 'undefined' && $summary !== null) {
2089
+ if ($summary) {
1964
2090
  // Create a new `span` element and copy the summary line content from the original `div` to the
1965
2091
  // new `span`
1966
2092
  // This is because the summary line text is now inside a button element, which can only contain
@@ -1997,10 +2123,19 @@
1997
2123
  /**
1998
2124
  * When a section is opened by the user agent via the 'beforematch' event
1999
2125
  *
2126
+ * @deprecated Will be made private in v5.0
2000
2127
  * @param {Event} event - Generic event
2001
2128
  */
2002
2129
  Accordion.prototype.onBeforeMatch = function (event) {
2003
- var $section = event.target.closest('.' + this.sectionClass);
2130
+ var $fragment = event.target;
2131
+
2132
+ // Handle elements with `.closest()` support only
2133
+ if (!($fragment instanceof Element)) {
2134
+ return
2135
+ }
2136
+
2137
+ // Handle when fragment is inside section
2138
+ var $section = $fragment.closest('.' + this.sectionClass);
2004
2139
  if ($section) {
2005
2140
  this.setExpanded(true, $section);
2006
2141
  }
@@ -2009,7 +2144,8 @@
2009
2144
  /**
2010
2145
  * When section toggled, set and store state
2011
2146
  *
2012
- * @param {HTMLElement} $section - Section element
2147
+ * @deprecated Will be made private in v5.0
2148
+ * @param {Element} $section - Section element
2013
2149
  */
2014
2150
  Accordion.prototype.onSectionToggle = function ($section) {
2015
2151
  var expanded = this.isExpanded($section);
@@ -2021,26 +2157,31 @@
2021
2157
 
2022
2158
  /**
2023
2159
  * When Open/Close All toggled, set and store state
2160
+ *
2161
+ * @deprecated Will be made private in v5.0
2024
2162
  */
2025
2163
  Accordion.prototype.onShowOrHideAllToggle = function () {
2026
- var $module = this;
2164
+ var $component = this;
2027
2165
  var $sections = this.$sections;
2166
+
2028
2167
  var nowExpanded = !this.checkIfAllSectionsOpen();
2029
2168
 
2169
+ // Loop through sections
2030
2170
  nodeListForEach($sections, function ($section) {
2031
- $module.setExpanded(nowExpanded, $section);
2171
+ $component.setExpanded(nowExpanded, $section);
2032
2172
  // Store the state in sessionStorage when a change is triggered
2033
- $module.storeState($section);
2173
+ $component.storeState($section);
2034
2174
  });
2035
2175
 
2036
- $module.updateShowAllButton(nowExpanded);
2176
+ $component.updateShowAllButton(nowExpanded);
2037
2177
  };
2038
2178
 
2039
2179
  /**
2040
2180
  * Set section attributes when opened/closed
2041
2181
  *
2182
+ * @deprecated Will be made private in v5.0
2042
2183
  * @param {boolean} expanded - Section expanded
2043
- * @param {HTMLElement} $section - Section element
2184
+ * @param {Element} $section - Section element
2044
2185
  */
2045
2186
  Accordion.prototype.setExpanded = function (expanded, $section) {
2046
2187
  var $showHideIcon = $section.querySelector('.' + this.upChevronIconClass);
@@ -2048,23 +2189,30 @@
2048
2189
  var $button = $section.querySelector('.' + this.sectionButtonClass);
2049
2190
  var $content = $section.querySelector('.' + this.sectionContentClass);
2050
2191
 
2192
+ if (!$showHideIcon ||
2193
+ !($showHideText instanceof HTMLElement) ||
2194
+ !$button ||
2195
+ !$content) {
2196
+ return
2197
+ }
2198
+
2051
2199
  var newButtonText = expanded
2052
2200
  ? this.i18n.t('hideSection')
2053
2201
  : this.i18n.t('showSection');
2054
2202
 
2055
2203
  $showHideText.innerText = newButtonText;
2056
- $button.setAttribute('aria-expanded', expanded);
2204
+ $button.setAttribute('aria-expanded', expanded.toString());
2057
2205
 
2058
2206
  // Update aria-label combining
2059
2207
  var ariaLabelParts = [];
2060
2208
 
2061
2209
  var $headingText = $section.querySelector('.' + this.sectionHeadingTextClass);
2062
- if ($headingText) {
2210
+ if ($headingText instanceof HTMLElement) {
2063
2211
  ariaLabelParts.push($headingText.innerText.trim());
2064
2212
  }
2065
2213
 
2066
2214
  var $summary = $section.querySelector('.' + this.sectionSummaryClass);
2067
- if ($summary) {
2215
+ if ($summary instanceof HTMLElement) {
2068
2216
  ariaLabelParts.push($summary.innerText.trim());
2069
2217
  }
2070
2218
 
@@ -2099,7 +2247,8 @@
2099
2247
  /**
2100
2248
  * Get state of section
2101
2249
  *
2102
- * @param {HTMLElement} $section - Section element
2250
+ * @deprecated Will be made private in v5.0
2251
+ * @param {Element} $section - Section element
2103
2252
  * @returns {boolean} True if expanded
2104
2253
  */
2105
2254
  Accordion.prototype.isExpanded = function ($section) {
@@ -2109,6 +2258,7 @@
2109
2258
  /**
2110
2259
  * Check if all sections are open
2111
2260
  *
2261
+ * @deprecated Will be made private in v5.0
2112
2262
  * @returns {boolean} True if all sections are open
2113
2263
  */
2114
2264
  Accordion.prototype.checkIfAllSectionsOpen = function () {
@@ -2124,6 +2274,7 @@
2124
2274
  /**
2125
2275
  * Update "Show all sections" button
2126
2276
  *
2277
+ * @deprecated Will be made private in v5.0
2127
2278
  * @param {boolean} expanded - Section expanded
2128
2279
  */
2129
2280
  Accordion.prototype.updateShowAllButton = function (expanded) {
@@ -2131,7 +2282,7 @@
2131
2282
  ? this.i18n.t('hideAllSections')
2132
2283
  : this.i18n.t('showAllSections');
2133
2284
 
2134
- this.$showAllButton.setAttribute('aria-expanded', expanded);
2285
+ this.$showAllButton.setAttribute('aria-expanded', expanded.toString());
2135
2286
  this.$showAllText.innerText = newButtonText;
2136
2287
 
2137
2288
  // Swap icon, toggle class
@@ -2165,10 +2316,11 @@
2165
2316
  /**
2166
2317
  * Set the state of the accordions in sessionStorage
2167
2318
  *
2168
- * @param {HTMLElement} $section - Section element
2319
+ * @deprecated Will be made private in v5.0
2320
+ * @param {Element} $section - Section element
2169
2321
  */
2170
2322
  Accordion.prototype.storeState = function ($section) {
2171
- if (this.browserSupportsSessionStorage) {
2323
+ if (this.browserSupportsSessionStorage && this.config.rememberExpanded) {
2172
2324
  // We need a unique way of identifying each content in the Accordion. Since
2173
2325
  // an `#id` should be unique and an `id` is required for `aria-` attributes
2174
2326
  // `id` can be safely used.
@@ -2189,10 +2341,11 @@
2189
2341
  /**
2190
2342
  * Read the state of the accordions from sessionStorage
2191
2343
  *
2192
- * @param {HTMLElement} $section - Section element
2344
+ * @deprecated Will be made private in v5.0
2345
+ * @param {Element} $section - Section element
2193
2346
  */
2194
2347
  Accordion.prototype.setInitialState = function ($section) {
2195
- if (this.browserSupportsSessionStorage) {
2348
+ if (this.browserSupportsSessionStorage && this.config.rememberExpanded) {
2196
2349
  var $button = $section.querySelector('.' + this.sectionButtonClass);
2197
2350
 
2198
2351
  if ($button) {
@@ -2213,7 +2366,8 @@
2213
2366
  * into thematic chunks.
2214
2367
  * See https://github.com/alphagov/govuk-frontend/issues/2327#issuecomment-922957442
2215
2368
  *
2216
- * @returns {HTMLElement} DOM element
2369
+ * @deprecated Will be made private in v5.0
2370
+ * @returns {Element} DOM element
2217
2371
  */
2218
2372
  Accordion.prototype.getButtonPunctuationEl = function () {
2219
2373
  var $punctuationEl = document.createElement('span');
@@ -2227,6 +2381,8 @@
2227
2381
  *
2228
2382
  * @typedef {object} AccordionConfig
2229
2383
  * @property {AccordionTranslations} [i18n = ACCORDION_TRANSLATIONS] - See constant {@link ACCORDION_TRANSLATIONS}
2384
+ * @property {boolean} [rememberExpanded] - Whether the expanded and collapsed
2385
+ * state of each section is remembered and restored when navigating.
2230
2386
  */
2231
2387
 
2232
2388
  /**
@@ -2238,17 +2394,17 @@
2238
2394
  * the visible text shown on screen, and text to help assistive technology users
2239
2395
  * for the buttons toggling each section.
2240
2396
  * @property {string} [hideAllSections] - The text content for the 'Hide all
2241
- * sections' button, used when at least one section is expanded.
2397
+ * sections' button, used when at least one section is expanded.
2242
2398
  * @property {string} [hideSection] - The text content for the 'Hide'
2243
- * button, used when a section is expanded.
2399
+ * button, used when a section is expanded.
2244
2400
  * @property {string} [hideSectionAriaLabel] - The text content appended to the
2245
- * 'Hide' button's accessible name when a section is expanded.
2401
+ * 'Hide' button's accessible name when a section is expanded.
2246
2402
  * @property {string} [showAllSections] - The text content for the 'Show all
2247
- * sections' button, used when all sections are collapsed.
2403
+ * sections' button, used when all sections are collapsed.
2248
2404
  * @property {string} [showSection] - The text content for the 'Show'
2249
- * button, used when a section is collapsed.
2405
+ * button, used when a section is collapsed.
2250
2406
  * @property {string} [showSectionAriaLabel] - The text content appended to the
2251
- * 'Show' button's accessible name when a section is expanded.
2407
+ * 'Show' button's accessible name when a section is expanded.
2252
2408
  */
2253
2409
 
2254
2410
  /* eslint-disable es-x/no-function-prototype-bind -- Polyfill imported */
@@ -2260,20 +2416,28 @@
2260
2416
  * JavaScript enhancements for the Button component
2261
2417
  *
2262
2418
  * @class
2263
- * @param {HTMLElement} $module - HTML element to use for button
2419
+ * @param {Element} $module - HTML element to use for button
2264
2420
  * @param {ButtonConfig} [config] - Button config
2265
2421
  */
2266
2422
  function Button ($module, config) {
2267
- if (!$module) {
2423
+ if (!($module instanceof HTMLElement)) {
2268
2424
  return this
2269
2425
  }
2270
2426
 
2427
+ /** @deprecated Will be made private in v5.0 */
2271
2428
  this.$module = $module;
2429
+
2430
+ /** @deprecated Will be made private in v5.0 */
2272
2431
  this.debounceFormSubmitTimer = null;
2273
2432
 
2274
2433
  var defaultConfig = {
2275
2434
  preventDoubleClick: false
2276
2435
  };
2436
+
2437
+ /**
2438
+ * @deprecated Will be made private in v5.0
2439
+ * @type {ButtonConfig}
2440
+ */
2277
2441
  this.config = mergeConfigs(
2278
2442
  defaultConfig,
2279
2443
  config || {},
@@ -2285,6 +2449,7 @@
2285
2449
  * Initialise component
2286
2450
  */
2287
2451
  Button.prototype.init = function () {
2452
+ // Check that required elements are present
2288
2453
  if (!this.$module) {
2289
2454
  return
2290
2455
  }
@@ -2301,12 +2466,19 @@
2301
2466
  *
2302
2467
  * See https://github.com/alphagov/govuk_elements/pull/272#issuecomment-233028270
2303
2468
  *
2469
+ * @deprecated Will be made private in v5.0
2304
2470
  * @param {KeyboardEvent} event - Keydown event
2305
2471
  */
2306
2472
  Button.prototype.handleKeyDown = function (event) {
2307
2473
  var $target = event.target;
2308
2474
 
2309
- if ($target.getAttribute('role') === 'button' && event.keyCode === KEY_SPACE) {
2475
+ // Handle space bar only
2476
+ if (event.keyCode !== KEY_SPACE) {
2477
+ return
2478
+ }
2479
+
2480
+ // Handle elements with [role="button"] only
2481
+ if ($target instanceof HTMLElement && $target.getAttribute('role') === 'button') {
2310
2482
  event.preventDefault(); // prevent the page from scrolling
2311
2483
  $target.click();
2312
2484
  }
@@ -2319,6 +2491,7 @@
2319
2491
  * stops people accidentally causing multiple form submissions by double
2320
2492
  * clicking buttons.
2321
2493
  *
2494
+ * @deprecated Will be made private in v5.0
2322
2495
  * @param {MouseEvent} event - Mouse click event
2323
2496
  * @returns {undefined | false} Returns undefined, or false when debounced
2324
2497
  */
@@ -2343,26 +2516,27 @@
2343
2516
  * Button config
2344
2517
  *
2345
2518
  * @typedef {object} ButtonConfig
2346
- * @property {boolean} [preventDoubleClick = false] -
2347
- * Prevent accidental double clicks on submit buttons from submitting forms
2348
- * multiple times.
2519
+ * @property {boolean} [preventDoubleClick = false] - Prevent accidental double
2520
+ * clicks on submit buttons from submitting forms multiple times.
2349
2521
  */
2350
2522
 
2351
2523
  /**
2352
2524
  * Returns the value of the given attribute closest to the given element (including itself)
2353
2525
  *
2354
- * @param {HTMLElement} $element - The element to start walking the DOM tree up
2526
+ * @deprecated Will be made private in v5.0
2527
+ * @param {Element} $element - The element to start walking the DOM tree up
2355
2528
  * @param {string} attributeName - The name of the attribute
2356
- * @returns {string | undefined} Attribute value
2529
+ * @returns {string | null} Attribute value
2357
2530
  */
2358
2531
  function closestAttributeValue ($element, attributeName) {
2359
- var closestElementWithAttribute = $element.closest('[' + attributeName + ']');
2360
- if (closestElementWithAttribute) {
2361
- return closestElementWithAttribute.getAttribute(attributeName)
2362
- }
2532
+ var $closestElementWithAttribute = $element.closest('[' + attributeName + ']');
2533
+ return $closestElementWithAttribute
2534
+ ? $closestElementWithAttribute.getAttribute(attributeName)
2535
+ : null
2363
2536
  }
2364
2537
 
2365
- (function(undefined) {
2538
+ // @ts-nocheck
2539
+ (function (undefined) {
2366
2540
 
2367
2541
  // Detection from https://github.com/Financial-Times/polyfill-library/blob/v3.111.0/polyfills/Date/now/detect.js
2368
2542
  var detect = ('Date' in self && 'now' in self.Date && 'getTime' in self.Date.prototype);
@@ -2421,11 +2595,21 @@
2421
2595
  * of the available characters/words has been entered.
2422
2596
  *
2423
2597
  * @class
2424
- * @param {HTMLElement} $module - HTML element to use for character count
2598
+ * @param {Element} $module - HTML element to use for character count
2425
2599
  * @param {CharacterCountConfig} [config] - Character count config
2426
2600
  */
2427
2601
  function CharacterCount ($module, config) {
2428
- if (!$module) {
2602
+ if (!($module instanceof HTMLElement)) {
2603
+ return this
2604
+ }
2605
+
2606
+ var $textarea = $module.querySelector('.govuk-js-character-count');
2607
+ if (
2608
+ !(
2609
+ $textarea instanceof HTMLTextAreaElement ||
2610
+ $textarea instanceof HTMLInputElement
2611
+ )
2612
+ ) {
2429
2613
  return this
2430
2614
  }
2431
2615
 
@@ -2451,6 +2635,10 @@
2451
2635
  };
2452
2636
  }
2453
2637
 
2638
+ /**
2639
+ * @deprecated Will be made private in v5.0
2640
+ * @type {CharacterCountConfig}
2641
+ */
2454
2642
  this.config = mergeConfigs(
2455
2643
  defaultConfig,
2456
2644
  config || {},
@@ -2458,25 +2646,43 @@
2458
2646
  datasetConfig
2459
2647
  );
2460
2648
 
2649
+ /** @deprecated Will be made private in v5.0 */
2461
2650
  this.i18n = new I18n(extractConfigByNamespace(this.config, 'i18n'), {
2462
2651
  // Read the fallback if necessary rather than have it set in the defaults
2463
2652
  locale: closestAttributeValue($module, 'lang')
2464
2653
  });
2465
2654
 
2655
+ /** @deprecated Will be made private in v5.0 */
2656
+ this.maxLength = Infinity;
2466
2657
  // Determine the limit attribute (characters or words)
2467
- if (this.config.maxwords) {
2658
+ if ('maxwords' in this.config && this.config.maxwords) {
2468
2659
  this.maxLength = this.config.maxwords;
2469
- } else if (this.config.maxlength) {
2660
+ } else if ('maxlength' in this.config && this.config.maxlength) {
2470
2661
  this.maxLength = this.config.maxlength;
2471
2662
  } else {
2472
2663
  return
2473
2664
  }
2474
2665
 
2666
+ /** @deprecated Will be made private in v5.0 */
2475
2667
  this.$module = $module;
2476
- this.$textarea = $module.querySelector('.govuk-js-character-count');
2668
+
2669
+ /** @deprecated Will be made private in v5.0 */
2670
+ this.$textarea = $textarea;
2671
+
2672
+ /** @deprecated Will be made private in v5.0 */
2477
2673
  this.$visibleCountMessage = null;
2674
+
2675
+ /** @deprecated Will be made private in v5.0 */
2478
2676
  this.$screenReaderCountMessage = null;
2677
+
2678
+ /** @deprecated Will be made private in v5.0 */
2479
2679
  this.lastInputTimestamp = null;
2680
+
2681
+ /** @deprecated Will be made private in v5.0 */
2682
+ this.lastInputValue = '';
2683
+
2684
+ /** @deprecated Will be made private in v5.0 */
2685
+ this.valueChecker = null;
2480
2686
  }
2481
2687
 
2482
2688
  /**
@@ -2484,14 +2690,17 @@
2484
2690
  */
2485
2691
  CharacterCount.prototype.init = function () {
2486
2692
  // Check that required elements are present
2487
- if (!this.$textarea) {
2693
+ if (!this.$module || !this.$textarea) {
2488
2694
  return
2489
2695
  }
2490
2696
 
2491
2697
  var $textarea = this.$textarea;
2492
2698
  var $textareaDescription = document.getElementById($textarea.id + '-info');
2699
+ if (!$textareaDescription) {
2700
+ return
2701
+ }
2493
2702
 
2494
- // Inject a decription for the textarea if none is present already
2703
+ // Inject a description for the textarea if none is present already
2495
2704
  // for when the component was rendered with no maxlength, maxwords
2496
2705
  // nor custom textareaDescriptionText
2497
2706
  if ($textareaDescription.innerText.match(/^\s*$/)) {
@@ -2532,11 +2741,11 @@
2532
2741
  // state of the character count is not restored until *after* the
2533
2742
  // DOMContentLoaded event is fired, so we need to manually update it after the
2534
2743
  // pageshow event in browsers that support it.
2535
- if ('onpageshow' in window) {
2536
- window.addEventListener('pageshow', this.updateCountMessage.bind(this));
2537
- } else {
2538
- window.addEventListener('DOMContentLoaded', this.updateCountMessage.bind(this));
2539
- }
2744
+ window.addEventListener(
2745
+ 'onpageshow' in window ? 'pageshow' : 'DOMContentLoaded',
2746
+ this.updateCountMessage.bind(this)
2747
+ );
2748
+
2540
2749
  this.updateCountMessage();
2541
2750
  };
2542
2751
 
@@ -2545,6 +2754,8 @@
2545
2754
  *
2546
2755
  * Set up event listeners on the $textarea so that the count messages update
2547
2756
  * when the user types.
2757
+ *
2758
+ * @deprecated Will be made private in v5.0
2548
2759
  */
2549
2760
  CharacterCount.prototype.bindChangeEvents = function () {
2550
2761
  var $textarea = this.$textarea;
@@ -2560,6 +2771,8 @@
2560
2771
  *
2561
2772
  * Update the visible character counter and keep track of when the last update
2562
2773
  * happened for each keypress
2774
+ *
2775
+ * @deprecated Will be made private in v5.0
2563
2776
  */
2564
2777
  CharacterCount.prototype.handleKeyUp = function () {
2565
2778
  this.updateVisibleCountMessage();
@@ -2578,6 +2791,8 @@
2578
2791
  *
2579
2792
  * This is so that the update triggered by the manual comparison doesn't
2580
2793
  * conflict with debounced KeyboardEvent updates.
2794
+ *
2795
+ * @deprecated Will be made private in v5.0
2581
2796
  */
2582
2797
  CharacterCount.prototype.handleFocus = function () {
2583
2798
  this.valueChecker = setInterval(function () {
@@ -2591,6 +2806,8 @@
2591
2806
  * Handle blur event
2592
2807
  *
2593
2808
  * Stop checking the textarea value once the textarea no longer has focus
2809
+ *
2810
+ * @deprecated Will be made private in v5.0
2594
2811
  */
2595
2812
  CharacterCount.prototype.handleBlur = function () {
2596
2813
  // Cancel value checking on blur
@@ -2599,11 +2816,12 @@
2599
2816
 
2600
2817
  /**
2601
2818
  * Update count message if textarea value has changed
2819
+ *
2820
+ * @deprecated Will be made private in v5.0
2602
2821
  */
2603
2822
  CharacterCount.prototype.updateIfValueChanged = function () {
2604
- if (!this.$textarea.oldValue) this.$textarea.oldValue = '';
2605
- if (this.$textarea.value !== this.$textarea.oldValue) {
2606
- this.$textarea.oldValue = this.$textarea.value;
2823
+ if (this.$textarea.value !== this.lastInputValue) {
2824
+ this.lastInputValue = this.$textarea.value;
2607
2825
  this.updateCountMessage();
2608
2826
  }
2609
2827
  };
@@ -2613,6 +2831,8 @@
2613
2831
  *
2614
2832
  * Helper function to update both the visible and screen reader-specific
2615
2833
  * counters simultaneously (e.g. on init)
2834
+ *
2835
+ * @deprecated Will be made private in v5.0
2616
2836
  */
2617
2837
  CharacterCount.prototype.updateCountMessage = function () {
2618
2838
  this.updateVisibleCountMessage();
@@ -2621,6 +2841,8 @@
2621
2841
 
2622
2842
  /**
2623
2843
  * Update visible count message
2844
+ *
2845
+ * @deprecated Will be made private in v5.0
2624
2846
  */
2625
2847
  CharacterCount.prototype.updateVisibleCountMessage = function () {
2626
2848
  var $textarea = this.$textarea;
@@ -2652,6 +2874,8 @@
2652
2874
 
2653
2875
  /**
2654
2876
  * Update screen reader count message
2877
+ *
2878
+ * @deprecated Will be made private in v5.0
2655
2879
  */
2656
2880
  CharacterCount.prototype.updateScreenReaderCountMessage = function () {
2657
2881
  var $screenReaderCountMessage = this.$screenReaderCountMessage;
@@ -2661,7 +2885,7 @@
2661
2885
  if (this.isOverThreshold()) {
2662
2886
  $screenReaderCountMessage.removeAttribute('aria-hidden');
2663
2887
  } else {
2664
- $screenReaderCountMessage.setAttribute('aria-hidden', true);
2888
+ $screenReaderCountMessage.setAttribute('aria-hidden', 'true');
2665
2889
  }
2666
2890
 
2667
2891
  // Update message
@@ -2672,11 +2896,12 @@
2672
2896
  * Count the number of characters (or words, if `config.maxwords` is set)
2673
2897
  * in the given text
2674
2898
  *
2899
+ * @deprecated Will be made private in v5.0
2675
2900
  * @param {string} text - The text to count the characters of
2676
2901
  * @returns {number} the number of characters (or words) in the text
2677
2902
  */
2678
2903
  CharacterCount.prototype.count = function (text) {
2679
- if (this.config.maxwords) {
2904
+ if ('maxwords' in this.config && this.config.maxwords) {
2680
2905
  var tokens = text.match(/\S+/g) || []; // Matches consecutive non-whitespace chars
2681
2906
  return tokens.length
2682
2907
  } else {
@@ -2687,12 +2912,13 @@
2687
2912
  /**
2688
2913
  * Get count message
2689
2914
  *
2915
+ * @deprecated Will be made private in v5.0
2690
2916
  * @returns {string} Status message
2691
2917
  */
2692
2918
  CharacterCount.prototype.getCountMessage = function () {
2693
2919
  var remainingNumber = this.maxLength - this.count(this.$textarea.value);
2694
2920
 
2695
- var countType = this.config.maxwords ? 'words' : 'characters';
2921
+ var countType = 'maxwords' in this.config && this.config.maxwords ? 'words' : 'characters';
2696
2922
  return this.formatCountMessage(remainingNumber, countType)
2697
2923
  };
2698
2924
 
@@ -2700,6 +2926,7 @@
2700
2926
  * Formats the message shown to users according to what's counted
2701
2927
  * and how many remain
2702
2928
  *
2929
+ * @deprecated Will be made private in v5.0
2703
2930
  * @param {number} remainingNumber - The number of words/characaters remaining
2704
2931
  * @param {string} countType - "words" or "characters"
2705
2932
  * @returns {string} Status message
@@ -2721,6 +2948,7 @@
2721
2948
  * If there is no configured threshold, it is set to 0 and this function will
2722
2949
  * always return true.
2723
2950
  *
2951
+ * @deprecated Will be made private in v5.0
2724
2952
  * @returns {boolean} true if the current count is over the config.threshold
2725
2953
  * (or no threshold is set)
2726
2954
  */
@@ -2752,10 +2980,10 @@
2752
2980
  *
2753
2981
  * @typedef {object} CharacterCountConfigWithMaxLength
2754
2982
  * @property {number} [maxlength] - The maximum number of characters.
2755
- * If maxwords is provided, the maxlength option will be ignored.
2983
+ * If maxwords is provided, the maxlength option will be ignored.
2756
2984
  * @property {number} [threshold = 0] - The percentage value of the limit at
2757
- * which point the count message is displayed. If this attribute is set, the
2758
- * count message will be hidden by default.
2985
+ * which point the count message is displayed. If this attribute is set, the
2986
+ * count message will be hidden by default.
2759
2987
  * @property {CharacterCountTranslations} [i18n = CHARACTER_COUNT_TRANSLATIONS] - See constant {@link CHARACTER_COUNT_TRANSLATIONS}
2760
2988
  */
2761
2989
 
@@ -2764,10 +2992,10 @@
2764
2992
  *
2765
2993
  * @typedef {object} CharacterCountConfigWithMaxWords
2766
2994
  * @property {number} [maxwords] - The maximum number of words. If maxwords is
2767
- * provided, the maxlength option will be ignored.
2995
+ * provided, the maxlength option will be ignored.
2768
2996
  * @property {number} [threshold = 0] - The percentage value of the limit at
2769
- * which point the count message is displayed. If this attribute is set, the
2770
- * count message will be hidden by default.
2997
+ * which point the count message is displayed. If this attribute is set, the
2998
+ * count message will be hidden by default.
2771
2999
  * @property {CharacterCountTranslations} [i18n = CHARACTER_COUNT_TRANSLATIONS] - See constant {@link CHARACTER_COUNT_TRANSLATIONS}
2772
3000
  */
2773
3001
 
@@ -2827,11 +3055,23 @@
2827
3055
  * Checkboxes component
2828
3056
  *
2829
3057
  * @class
2830
- * @param {HTMLElement} $module - HTML element to use for checkboxes
3058
+ * @param {Element} $module - HTML element to use for checkboxes
2831
3059
  */
2832
3060
  function Checkboxes ($module) {
3061
+ if (!($module instanceof HTMLElement)) {
3062
+ return this
3063
+ }
3064
+
3065
+ var $inputs = $module.querySelectorAll('input[type="checkbox"]');
3066
+ if (!$inputs.length) {
3067
+ return this
3068
+ }
3069
+
3070
+ /** @deprecated Will be made private in v5.0 */
2833
3071
  this.$module = $module;
2834
- this.$inputs = $module.querySelectorAll('input[type="checkbox"]');
3072
+
3073
+ /** @deprecated Will be made private in v5.0 */
3074
+ this.$inputs = $inputs;
2835
3075
  }
2836
3076
 
2837
3077
  /**
@@ -2849,6 +3089,11 @@
2849
3089
  * the reveal in sync with the checkbox state.
2850
3090
  */
2851
3091
  Checkboxes.prototype.init = function () {
3092
+ // Check that required elements are present
3093
+ if (!this.$module || !this.$inputs) {
3094
+ return
3095
+ }
3096
+
2852
3097
  var $module = this.$module;
2853
3098
  var $inputs = this.$inputs;
2854
3099
 
@@ -2871,11 +3116,10 @@
2871
3116
  // state of form controls is not restored until *after* the DOMContentLoaded
2872
3117
  // event is fired, so we need to sync after the pageshow event in browsers
2873
3118
  // that support it.
2874
- if ('onpageshow' in window) {
2875
- window.addEventListener('pageshow', this.syncAllConditionalReveals.bind(this));
2876
- } else {
2877
- window.addEventListener('DOMContentLoaded', this.syncAllConditionalReveals.bind(this));
2878
- }
3119
+ window.addEventListener(
3120
+ 'onpageshow' in window ? 'pageshow' : 'DOMContentLoaded',
3121
+ this.syncAllConditionalReveals.bind(this)
3122
+ );
2879
3123
 
2880
3124
  // Although we've set up handlers to sync state on the pageshow or
2881
3125
  // DOMContentLoaded event, init could be called after those events have fired,
@@ -2888,6 +3132,8 @@
2888
3132
 
2889
3133
  /**
2890
3134
  * Sync the conditional reveal states for all checkboxes in this $module.
3135
+ *
3136
+ * @deprecated Will be made private in v5.0
2891
3137
  */
2892
3138
  Checkboxes.prototype.syncAllConditionalReveals = function () {
2893
3139
  nodeListForEach(this.$inputs, this.syncConditionalRevealWithInputState.bind(this));
@@ -2899,15 +3145,20 @@
2899
3145
  * Synchronise the visibility of the conditional reveal, and its accessible
2900
3146
  * state, with the input's checked state.
2901
3147
  *
3148
+ * @deprecated Will be made private in v5.0
2902
3149
  * @param {HTMLInputElement} $input - Checkbox input
2903
3150
  */
2904
3151
  Checkboxes.prototype.syncConditionalRevealWithInputState = function ($input) {
2905
- var $target = document.getElementById($input.getAttribute('aria-controls'));
3152
+ var targetId = $input.getAttribute('aria-controls');
3153
+ if (!targetId) {
3154
+ return
3155
+ }
2906
3156
 
3157
+ var $target = document.getElementById(targetId);
2907
3158
  if ($target && $target.classList.contains('govuk-checkboxes__conditional')) {
2908
3159
  var inputIsChecked = $input.checked;
2909
3160
 
2910
- $input.setAttribute('aria-expanded', inputIsChecked);
3161
+ $input.setAttribute('aria-expanded', inputIsChecked.toString());
2911
3162
  $target.classList.toggle('govuk-checkboxes__conditional--hidden', !inputIsChecked);
2912
3163
  }
2913
3164
  };
@@ -2918,18 +3169,25 @@
2918
3169
  * Find any other checkbox inputs with the same name value, and uncheck them.
2919
3170
  * This is useful for when a “None of these" checkbox is checked.
2920
3171
  *
2921
- * @param {HTMLElement} $input - Checkbox input
3172
+ * @deprecated Will be made private in v5.0
3173
+ * @param {HTMLInputElement} $input - Checkbox input
2922
3174
  */
2923
3175
  Checkboxes.prototype.unCheckAllInputsExcept = function ($input) {
2924
- var allInputsWithSameName = document.querySelectorAll('input[type="checkbox"][name="' + $input.name + '"]');
3176
+ var $component = this;
3177
+
3178
+ /** @type {NodeListOf<HTMLInputElement>} */
3179
+ // @ts-expect-error `NodeListOf<HTMLInputElement>` type expected
3180
+ var allInputsWithSameName = document.querySelectorAll(
3181
+ 'input[type="checkbox"][name="' + $input.name + '"]'
3182
+ );
2925
3183
 
2926
3184
  nodeListForEach(allInputsWithSameName, function ($inputWithSameName) {
2927
3185
  var hasSameFormOwner = ($input.form === $inputWithSameName.form);
2928
3186
  if (hasSameFormOwner && $inputWithSameName !== $input) {
2929
3187
  $inputWithSameName.checked = false;
2930
- this.syncConditionalRevealWithInputState($inputWithSameName);
3188
+ $component.syncConditionalRevealWithInputState($inputWithSameName);
2931
3189
  }
2932
- }.bind(this));
3190
+ });
2933
3191
  };
2934
3192
 
2935
3193
  /**
@@ -2939,9 +3197,14 @@
2939
3197
  * and uncheck them. This helps prevent someone checking both a regular checkbox and a
2940
3198
  * "None of these" checkbox in the same fieldset.
2941
3199
  *
3200
+ * @deprecated Will be made private in v5.0
2942
3201
  * @param {HTMLInputElement} $input - Checkbox input
2943
3202
  */
2944
3203
  Checkboxes.prototype.unCheckExclusiveInputs = function ($input) {
3204
+ var $component = this;
3205
+
3206
+ /** @type {NodeListOf<HTMLInputElement>} */
3207
+ // @ts-expect-error `NodeListOf<HTMLInputElement>` type expected
2945
3208
  var allInputsWithSameNameAndExclusiveBehaviour = document.querySelectorAll(
2946
3209
  'input[data-behaviour="exclusive"][type="checkbox"][name="' + $input.name + '"]'
2947
3210
  );
@@ -2950,9 +3213,9 @@
2950
3213
  var hasSameFormOwner = ($input.form === $exclusiveInput.form);
2951
3214
  if (hasSameFormOwner) {
2952
3215
  $exclusiveInput.checked = false;
2953
- this.syncConditionalRevealWithInputState($exclusiveInput);
3216
+ $component.syncConditionalRevealWithInputState($exclusiveInput);
2954
3217
  }
2955
- }.bind(this));
3218
+ });
2956
3219
  };
2957
3220
 
2958
3221
  /**
@@ -2961,13 +3224,14 @@
2961
3224
  * Handle a click within the $module – if the click occurred on a checkbox, sync
2962
3225
  * the state of any associated conditional reveal with the checkbox state.
2963
3226
  *
3227
+ * @deprecated Will be made private in v5.0
2964
3228
  * @param {MouseEvent} event - Click event
2965
3229
  */
2966
3230
  Checkboxes.prototype.handleClick = function (event) {
2967
3231
  var $clickedInput = event.target;
2968
3232
 
2969
3233
  // Ignore clicks on things that aren't checkbox inputs
2970
- if ($clickedInput.type !== 'checkbox') {
3234
+ if (!($clickedInput instanceof HTMLInputElement) || $clickedInput.type !== 'checkbox') {
2971
3235
  return
2972
3236
  }
2973
3237
 
@@ -3000,32 +3264,45 @@
3000
3264
  * Details component
3001
3265
  *
3002
3266
  * @class
3003
- * @param {HTMLElement} $module - HTML element to use for details
3267
+ * @param {Element} $module - HTML element to use for details
3004
3268
  */
3005
3269
  function Details ($module) {
3270
+ if (!($module instanceof HTMLElement)) {
3271
+ return this
3272
+ }
3273
+
3274
+ /** @deprecated Will be made private in v5.0 */
3006
3275
  this.$module = $module;
3276
+
3277
+ /** @deprecated Will be made private in v5.0 */
3278
+ this.$summary = null;
3279
+
3280
+ /** @deprecated Will be made private in v5.0 */
3281
+ this.$content = null;
3007
3282
  }
3008
3283
 
3009
3284
  /**
3010
3285
  * Initialise component
3011
3286
  */
3012
3287
  Details.prototype.init = function () {
3288
+ // Check that required elements are present
3013
3289
  if (!this.$module) {
3014
3290
  return
3015
3291
  }
3016
3292
 
3017
3293
  // If there is native details support, we want to avoid running code to polyfill native behaviour.
3018
- var hasNativeDetails = typeof this.$module.open === 'boolean';
3294
+ var hasNativeDetails = 'HTMLDetailsElement' in window &&
3295
+ this.$module instanceof HTMLDetailsElement;
3019
3296
 
3020
- if (hasNativeDetails) {
3021
- return
3297
+ if (!hasNativeDetails) {
3298
+ this.polyfillDetails();
3022
3299
  }
3023
-
3024
- this.polyfillDetails();
3025
3300
  };
3026
3301
 
3027
3302
  /**
3028
3303
  * Polyfill component in older browsers
3304
+ *
3305
+ * @deprecated Will be made private in v5.0
3029
3306
  */
3030
3307
  Details.prototype.polyfillDetails = function () {
3031
3308
  var $module = this.$module;
@@ -3076,6 +3353,7 @@
3076
3353
  /**
3077
3354
  * Define a statechange function that updates aria-expanded and style.display
3078
3355
  *
3356
+ * @deprecated Will be made private in v5.0
3079
3357
  * @returns {boolean} Returns true
3080
3358
  */
3081
3359
  Details.prototype.polyfillSetAttributes = function () {
@@ -3095,6 +3373,7 @@
3095
3373
  /**
3096
3374
  * Handle cross-modal click events
3097
3375
  *
3376
+ * @deprecated Will be made private in v5.0
3098
3377
  * @param {polyfillHandleInputsCallback} callback - function
3099
3378
  */
3100
3379
  Details.prototype.polyfillHandleInputs = function (callback) {
@@ -3102,7 +3381,7 @@
3102
3381
  var $target = event.target;
3103
3382
  // When the key gets pressed - check if it is enter or space
3104
3383
  if (event.keyCode === KEY_ENTER || event.keyCode === KEY_SPACE$1) {
3105
- if ($target.nodeName.toLowerCase() === 'summary') {
3384
+ if ($target instanceof HTMLElement && $target.nodeName.toLowerCase() === 'summary') {
3106
3385
  // Prevent space from scrolling the page
3107
3386
  // and enter from submitting a form
3108
3387
  event.preventDefault();
@@ -3121,7 +3400,7 @@
3121
3400
  this.$summary.addEventListener('keyup', function (event) {
3122
3401
  var $target = event.target;
3123
3402
  if (event.keyCode === KEY_SPACE$1) {
3124
- if ($target.nodeName.toLowerCase() === 'summary') {
3403
+ if ($target instanceof HTMLElement && $target.nodeName.toLowerCase() === 'summary') {
3125
3404
  event.preventDefault();
3126
3405
  }
3127
3406
  }
@@ -3144,7 +3423,7 @@
3144
3423
  * Takes focus on initialisation for accessible announcement, unless disabled in configuration.
3145
3424
  *
3146
3425
  * @class
3147
- * @param {HTMLElement} $module - HTML element to use for error summary
3426
+ * @param {Element} $module - HTML element to use for error summary
3148
3427
  * @param {ErrorSummaryConfig} [config] - Error summary config
3149
3428
  */
3150
3429
  function ErrorSummary ($module, config) {
@@ -3155,17 +3434,23 @@
3155
3434
  // To avoid breaking further JavaScript initialisation
3156
3435
  // we need to safeguard against this so things keep
3157
3436
  // working the same now we read the elements data attributes
3158
- if (!$module) {
3437
+ if (!($module instanceof HTMLElement)) {
3159
3438
  // Little safety in case code gets ported as-is
3160
3439
  // into and ES6 class constructor, where the return value matters
3161
3440
  return this
3162
3441
  }
3163
3442
 
3443
+ /** @deprecated Will be made private in v5.0 */
3164
3444
  this.$module = $module;
3165
3445
 
3166
3446
  var defaultConfig = {
3167
3447
  disableAutoFocus: false
3168
3448
  };
3449
+
3450
+ /**
3451
+ * @deprecated Will be made private in v5.0
3452
+ * @type {ErrorSummaryConfig}
3453
+ */
3169
3454
  this.config = mergeConfigs(
3170
3455
  defaultConfig,
3171
3456
  config || {},
@@ -3177,17 +3462,21 @@
3177
3462
  * Initialise component
3178
3463
  */
3179
3464
  ErrorSummary.prototype.init = function () {
3180
- var $module = this.$module;
3181
- if (!$module) {
3465
+ // Check that required elements are present
3466
+ if (!this.$module) {
3182
3467
  return
3183
3468
  }
3184
3469
 
3470
+ var $module = this.$module;
3471
+
3185
3472
  this.setFocus();
3186
3473
  $module.addEventListener('click', this.handleClick.bind(this));
3187
3474
  };
3188
3475
 
3189
3476
  /**
3190
3477
  * Focus the error summary
3478
+ *
3479
+ * @deprecated Will be made private in v5.0
3191
3480
  */
3192
3481
  ErrorSummary.prototype.setFocus = function () {
3193
3482
  var $module = this.$module;
@@ -3210,6 +3499,7 @@
3210
3499
  /**
3211
3500
  * Click event handler
3212
3501
  *
3502
+ * @deprecated Will be made private in v5.0
3213
3503
  * @param {MouseEvent} event - Click event
3214
3504
  */
3215
3505
  ErrorSummary.prototype.handleClick = function (event) {
@@ -3234,16 +3524,21 @@
3234
3524
  * NVDA (as tested in 2018.3.2) - without this only the field type is announced
3235
3525
  * (e.g. "Edit, has autocomplete").
3236
3526
  *
3527
+ * @deprecated Will be made private in v5.0
3237
3528
  * @param {EventTarget} $target - Event target
3238
3529
  * @returns {boolean} True if the target was able to be focussed
3239
3530
  */
3240
3531
  ErrorSummary.prototype.focusTarget = function ($target) {
3241
3532
  // If the element that was clicked was not a link, return early
3242
- if ($target.tagName !== 'A' || $target.href === false) {
3533
+ if (!($target instanceof HTMLAnchorElement)) {
3243
3534
  return false
3244
3535
  }
3245
3536
 
3246
3537
  var inputId = this.getFragmentFromUrl($target.href);
3538
+ if (!inputId) {
3539
+ return false
3540
+ }
3541
+
3247
3542
  var $input = document.getElementById(inputId);
3248
3543
  if (!$input) {
3249
3544
  return false
@@ -3269,12 +3564,13 @@
3269
3564
  * Extract the fragment (everything after the hash) from a URL, but not including
3270
3565
  * the hash.
3271
3566
  *
3567
+ * @deprecated Will be made private in v5.0
3272
3568
  * @param {string} url - URL
3273
- * @returns {string} Fragment from URL, without the hash
3569
+ * @returns {string | undefined} Fragment from URL, without the hash
3274
3570
  */
3275
3571
  ErrorSummary.prototype.getFragmentFromUrl = function (url) {
3276
3572
  if (url.indexOf('#') === -1) {
3277
- return false
3573
+ return undefined
3278
3574
  }
3279
3575
 
3280
3576
  return url.split('#').pop()
@@ -3291,9 +3587,10 @@
3291
3587
  * - The first `<label>` that is associated with the input using for="inputId"
3292
3588
  * - The closest parent `<label>`
3293
3589
  *
3294
- * @param {HTMLElement} $input - The input
3295
- * @returns {HTMLElement} Associated legend or label, or null if no associated
3296
- * legend or label can be found
3590
+ * @deprecated Will be made private in v5.0
3591
+ * @param {Element} $input - The input
3592
+ * @returns {Element | null} Associated legend or label, or null if no associated
3593
+ * legend or label can be found
3297
3594
  */
3298
3595
  ErrorSummary.prototype.getAssociatedLegendOrLabel = function ($input) {
3299
3596
  var $fieldset = $input.closest('fieldset');
@@ -3306,7 +3603,7 @@
3306
3603
 
3307
3604
  // If the input type is radio or checkbox, always use the legend if there
3308
3605
  // is one.
3309
- if ($input.type === 'checkbox' || $input.type === 'radio') {
3606
+ if ($input instanceof HTMLInputElement && ($input.type === 'checkbox' || $input.type === 'radio')) {
3310
3607
  return $candidateLegend
3311
3608
  }
3312
3609
 
@@ -3339,8 +3636,8 @@
3339
3636
  * Error summary config
3340
3637
  *
3341
3638
  * @typedef {object} ErrorSummaryConfig
3342
- * @property {boolean} [disableAutoFocus = false] -
3343
- * If set to `true` the error summary will not be focussed when the page loads.
3639
+ * @property {boolean} [disableAutoFocus = false] - If set to `true` the error
3640
+ * summary will not be focussed when the page loads.
3344
3641
  */
3345
3642
 
3346
3643
  /* eslint-disable es-x/no-function-prototype-bind -- Polyfill imported */
@@ -3349,24 +3646,41 @@
3349
3646
  * Header component
3350
3647
  *
3351
3648
  * @class
3352
- * @param {HTMLElement} $module - HTML element to use for header
3649
+ * @param {Element} $module - HTML element to use for header
3353
3650
  */
3354
3651
  function Header ($module) {
3652
+ if (!($module instanceof HTMLElement)) {
3653
+ return this
3654
+ }
3655
+
3656
+ /** @deprecated Will be made private in v5.0 */
3355
3657
  this.$module = $module;
3356
- this.$menuButton = $module && $module.querySelector('.govuk-js-header-toggle');
3658
+
3659
+ /** @deprecated Will be made private in v5.0 */
3660
+ this.$menuButton = $module.querySelector('.govuk-js-header-toggle');
3661
+
3662
+ /** @deprecated Will be made private in v5.0 */
3357
3663
  this.$menu = this.$menuButton && $module.querySelector(
3358
3664
  '#' + this.$menuButton.getAttribute('aria-controls')
3359
3665
  );
3360
3666
 
3361
- // Save the opened/closed state for the nav in memory so that we can
3362
- // accurately maintain state when the screen is changed from small to
3363
- // big and back to small
3667
+ /**
3668
+ * Save the opened/closed state for the nav in memory so that we can
3669
+ * accurately maintain state when the screen is changed from small to
3670
+ * big and back to small
3671
+ *
3672
+ * @deprecated Will be made private in v5.0
3673
+ */
3364
3674
  this.menuIsOpen = false;
3365
3675
 
3366
- // A global const for storing a matchMedia instance which we'll use to
3367
- // detect when a screen size change happens. We set this later during the
3368
- // init function and rely on it being null if the feature isn't available
3369
- // to initially apply hidden attributes
3676
+ /**
3677
+ * A global const for storing a matchMedia instance which we'll use to
3678
+ * detect when a screen size change happens. We set this later during the
3679
+ * init function and rely on it being null if the feature isn't available
3680
+ * to initially apply hidden attributes
3681
+ *
3682
+ * @deprecated Will be made private in v5.0
3683
+ */
3370
3684
  this.mql = null;
3371
3685
  }
3372
3686
 
@@ -3381,6 +3695,7 @@
3381
3695
  * version of the menu to the user.
3382
3696
  */
3383
3697
  Header.prototype.init = function () {
3698
+ // Check that required elements are present
3384
3699
  if (!this.$module || !this.$menuButton || !this.$menu) {
3385
3700
  return
3386
3701
  }
@@ -3393,8 +3708,9 @@
3393
3708
  this.mql.addEventListener('change', this.syncState.bind(this));
3394
3709
  } else {
3395
3710
  // addListener is a deprecated function, however addEventListener
3396
- // isn't supported by IE or Safari. We therefore add this in as
3711
+ // isn't supported by IE or Safari < 14. We therefore add this in as
3397
3712
  // a fallback for those browsers
3713
+ // @ts-expect-error Property 'addListener' does not exist
3398
3714
  this.mql.addListener(this.syncState.bind(this));
3399
3715
  }
3400
3716
 
@@ -3412,6 +3728,8 @@
3412
3728
  * visual states of the menu and the menu button.
3413
3729
  * Additionally will force the menu to be visible and the menu button to be
3414
3730
  * hidden if the matchMedia is triggered to desktop.
3731
+ *
3732
+ * @deprecated Will be made private in v5.0
3415
3733
  */
3416
3734
  Header.prototype.syncState = function () {
3417
3735
  if (this.mql.matches) {
@@ -3419,7 +3737,7 @@
3419
3737
  this.$menuButton.setAttribute('hidden', '');
3420
3738
  } else {
3421
3739
  this.$menuButton.removeAttribute('hidden');
3422
- this.$menuButton.setAttribute('aria-expanded', this.menuIsOpen);
3740
+ this.$menuButton.setAttribute('aria-expanded', this.menuIsOpen.toString());
3423
3741
 
3424
3742
  if (this.menuIsOpen) {
3425
3743
  this.$menu.removeAttribute('hidden');
@@ -3434,6 +3752,8 @@
3434
3752
  *
3435
3753
  * When the menu button is clicked, change the visibility of the menu and then
3436
3754
  * sync the accessibility state and menu button state
3755
+ *
3756
+ * @deprecated Will be made private in v5.0
3437
3757
  */
3438
3758
  Header.prototype.handleMenuButtonClick = function () {
3439
3759
  this.menuIsOpen = !this.menuIsOpen;
@@ -3444,15 +3764,25 @@
3444
3764
  * Notification Banner component
3445
3765
  *
3446
3766
  * @class
3447
- * @param {HTMLElement} $module - HTML element to use for notification banner
3767
+ * @param {Element} $module - HTML element to use for notification banner
3448
3768
  * @param {NotificationBannerConfig} [config] - Notification banner config
3449
3769
  */
3450
3770
  function NotificationBanner ($module, config) {
3771
+ if (!($module instanceof HTMLElement)) {
3772
+ return this
3773
+ }
3774
+
3775
+ /** @deprecated Will be made private in v5.0 */
3451
3776
  this.$module = $module;
3452
3777
 
3453
3778
  var defaultConfig = {
3454
3779
  disableAutoFocus: false
3455
3780
  };
3781
+
3782
+ /**
3783
+ * @deprecated Will be made private in v5.0
3784
+ * @type {NotificationBannerConfig}
3785
+ */
3456
3786
  this.config = mergeConfigs(
3457
3787
  defaultConfig,
3458
3788
  config || {},
@@ -3464,9 +3794,8 @@
3464
3794
  * Initialise component
3465
3795
  */
3466
3796
  NotificationBanner.prototype.init = function () {
3467
- var $module = this.$module;
3468
- // Check for module
3469
- if (!$module) {
3797
+ // Check that required elements are present
3798
+ if (!this.$module) {
3470
3799
  return
3471
3800
  }
3472
3801
 
@@ -3482,6 +3811,8 @@
3482
3811
  * You can turn off the auto-focus functionality by setting `data-disable-auto-focus="true"` in the
3483
3812
  * component HTML. You might wish to do this based on user research findings, or to avoid a clash
3484
3813
  * with another element which should be focused when the page loads.
3814
+ *
3815
+ * @deprecated Will be made private in v5.0
3485
3816
  */
3486
3817
  NotificationBanner.prototype.setFocus = function () {
3487
3818
  var $module = this.$module;
@@ -3512,11 +3843,10 @@
3512
3843
  * Notification banner config
3513
3844
  *
3514
3845
  * @typedef {object} NotificationBannerConfig
3515
- * @property {boolean} [disableAutoFocus = false] -
3516
- * If set to `true` the notification banner will not be focussed when the page
3517
- * loads. This only applies if the component has a `role` of `alert` – in
3518
- * other cases the component will not be focused on page load, regardless of
3519
- * this option.
3846
+ * @property {boolean} [disableAutoFocus = false] - If set to `true` the
3847
+ * notification banner will not be focussed when the page loads. This only
3848
+ * applies if the component has a `role` of `alert` – in other cases the
3849
+ * component will not be focused on page load, regardless of this option.
3520
3850
  */
3521
3851
 
3522
3852
  /* eslint-disable es-x/no-function-prototype-bind -- Polyfill imported */
@@ -3525,11 +3855,23 @@
3525
3855
  * Radios component
3526
3856
  *
3527
3857
  * @class
3528
- * @param {HTMLElement} $module - HTML element to use for radios
3858
+ * @param {Element} $module - HTML element to use for radios
3529
3859
  */
3530
3860
  function Radios ($module) {
3861
+ if (!($module instanceof HTMLElement)) {
3862
+ return this
3863
+ }
3864
+
3865
+ var $inputs = $module.querySelectorAll('input[type="radio"]');
3866
+ if (!$inputs.length) {
3867
+ return this
3868
+ }
3869
+
3870
+ /** @deprecated Will be made private in v5.0 */
3531
3871
  this.$module = $module;
3532
- this.$inputs = $module.querySelectorAll('input[type="radio"]');
3872
+
3873
+ /** @deprecated Will be made private in v5.0 */
3874
+ this.$inputs = $inputs;
3533
3875
  }
3534
3876
 
3535
3877
  /**
@@ -3547,6 +3889,11 @@
3547
3889
  * the reveal in sync with the radio state.
3548
3890
  */
3549
3891
  Radios.prototype.init = function () {
3892
+ // Check that required elements are present
3893
+ if (!this.$module || !this.$inputs) {
3894
+ return
3895
+ }
3896
+
3550
3897
  var $module = this.$module;
3551
3898
  var $inputs = this.$inputs;
3552
3899
 
@@ -3569,11 +3916,10 @@
3569
3916
  // state of form controls is not restored until *after* the DOMContentLoaded
3570
3917
  // event is fired, so we need to sync after the pageshow event in browsers
3571
3918
  // that support it.
3572
- if ('onpageshow' in window) {
3573
- window.addEventListener('pageshow', this.syncAllConditionalReveals.bind(this));
3574
- } else {
3575
- window.addEventListener('DOMContentLoaded', this.syncAllConditionalReveals.bind(this));
3576
- }
3919
+ window.addEventListener(
3920
+ 'onpageshow' in window ? 'pageshow' : 'DOMContentLoaded',
3921
+ this.syncAllConditionalReveals.bind(this)
3922
+ );
3577
3923
 
3578
3924
  // Although we've set up handlers to sync state on the pageshow or
3579
3925
  // DOMContentLoaded event, init could be called after those events have fired,
@@ -3586,6 +3932,8 @@
3586
3932
 
3587
3933
  /**
3588
3934
  * Sync the conditional reveal states for all radio buttons in this $module.
3935
+ *
3936
+ * @deprecated Will be made private in v5.0
3589
3937
  */
3590
3938
  Radios.prototype.syncAllConditionalReveals = function () {
3591
3939
  nodeListForEach(this.$inputs, this.syncConditionalRevealWithInputState.bind(this));
@@ -3597,15 +3945,20 @@
3597
3945
  * Synchronise the visibility of the conditional reveal, and its accessible
3598
3946
  * state, with the input's checked state.
3599
3947
  *
3948
+ * @deprecated Will be made private in v5.0
3600
3949
  * @param {HTMLInputElement} $input - Radio input
3601
3950
  */
3602
3951
  Radios.prototype.syncConditionalRevealWithInputState = function ($input) {
3603
- var $target = document.getElementById($input.getAttribute('aria-controls'));
3952
+ var targetId = $input.getAttribute('aria-controls');
3953
+ if (!targetId) {
3954
+ return
3955
+ }
3604
3956
 
3957
+ var $target = document.getElementById(targetId);
3605
3958
  if ($target && $target.classList.contains('govuk-radios__conditional')) {
3606
3959
  var inputIsChecked = $input.checked;
3607
3960
 
3608
- $input.setAttribute('aria-expanded', inputIsChecked);
3961
+ $input.setAttribute('aria-expanded', inputIsChecked.toString());
3609
3962
  $target.classList.toggle('govuk-radios__conditional--hidden', !inputIsChecked);
3610
3963
  }
3611
3964
  };
@@ -3618,13 +3971,15 @@
3618
3971
  * with the same name (because checking one radio could have un-checked a radio
3619
3972
  * in another $module)
3620
3973
  *
3974
+ * @deprecated Will be made private in v5.0
3621
3975
  * @param {MouseEvent} event - Click event
3622
3976
  */
3623
3977
  Radios.prototype.handleClick = function (event) {
3978
+ var $component = this;
3624
3979
  var $clickedInput = event.target;
3625
3980
 
3626
3981
  // Ignore clicks on things that aren't radio buttons
3627
- if ($clickedInput.type !== 'radio') {
3982
+ if (!($clickedInput instanceof HTMLInputElement) || $clickedInput.type !== 'radio') {
3628
3983
  return
3629
3984
  }
3630
3985
 
@@ -3632,14 +3987,17 @@
3632
3987
  // aria-controls attributes.
3633
3988
  var $allInputs = document.querySelectorAll('input[type="radio"][aria-controls]');
3634
3989
 
3990
+ var $clickedInputForm = $clickedInput.form;
3991
+ var $clickedInputName = $clickedInput.name;
3992
+
3635
3993
  nodeListForEach($allInputs, function ($input) {
3636
- var hasSameFormOwner = ($input.form === $clickedInput.form);
3637
- var hasSameName = ($input.name === $clickedInput.name);
3994
+ var hasSameFormOwner = $input.form === $clickedInputForm;
3995
+ var hasSameName = $input.name === $clickedInputName;
3638
3996
 
3639
3997
  if (hasSameName && hasSameFormOwner) {
3640
- this.syncConditionalRevealWithInputState($input);
3998
+ $component.syncConditionalRevealWithInputState($input);
3641
3999
  }
3642
- }.bind(this));
4000
+ });
3643
4001
  };
3644
4002
 
3645
4003
  /* eslint-disable es-x/no-function-prototype-bind -- Polyfill imported */
@@ -3648,11 +4006,20 @@
3648
4006
  * Skip link component
3649
4007
  *
3650
4008
  * @class
3651
- * @param {HTMLElement} $module - HTML element to use for skip link
4009
+ * @param {Element} $module - HTML element to use for skip link
3652
4010
  */
3653
4011
  function SkipLink ($module) {
4012
+ if (!($module instanceof HTMLAnchorElement)) {
4013
+ return this
4014
+ }
4015
+
4016
+ /** @deprecated Will be made private in v5.0 */
3654
4017
  this.$module = $module;
4018
+
4019
+ /** @deprecated Will be made private in v5.0 */
3655
4020
  this.$linkedElement = null;
4021
+
4022
+ /** @deprecated Will be made private in v5.0 */
3656
4023
  this.linkedElementListener = false;
3657
4024
  }
3658
4025
 
@@ -3660,30 +4027,31 @@
3660
4027
  * Initialise component
3661
4028
  */
3662
4029
  SkipLink.prototype.init = function () {
3663
- // Check for module
4030
+ // Check that required elements are present
3664
4031
  if (!this.$module) {
3665
4032
  return
3666
4033
  }
3667
4034
 
3668
4035
  // Check for linked element
3669
- this.$linkedElement = this.getLinkedElement();
3670
- if (!this.$linkedElement) {
4036
+ var $linkedElement = this.getLinkedElement();
4037
+ if (!$linkedElement) {
3671
4038
  return
3672
4039
  }
3673
4040
 
4041
+ this.$linkedElement = $linkedElement;
3674
4042
  this.$module.addEventListener('click', this.focusLinkedElement.bind(this));
3675
4043
  };
3676
4044
 
3677
4045
  /**
3678
4046
  * Get linked element
3679
4047
  *
3680
- * @returns {HTMLElement} $linkedElement - DOM element linked to from the skip link
4048
+ * @deprecated Will be made private in v5.0
4049
+ * @returns {HTMLElement | null} $linkedElement - DOM element linked to from the skip link
3681
4050
  */
3682
4051
  SkipLink.prototype.getLinkedElement = function () {
3683
4052
  var linkedElementId = this.getFragmentFromUrl();
3684
-
3685
4053
  if (!linkedElementId) {
3686
- return false
4054
+ return null
3687
4055
  }
3688
4056
 
3689
4057
  return document.getElementById(linkedElementId)
@@ -3693,6 +4061,8 @@
3693
4061
  * Focus the linked element
3694
4062
  *
3695
4063
  * Set tabindex and helper CSS class. Set listener to remove them on blur.
4064
+ *
4065
+ * @deprecated Will be made private in v5.0
3696
4066
  */
3697
4067
  SkipLink.prototype.focusLinkedElement = function () {
3698
4068
  var $linkedElement = this.$linkedElement;
@@ -3708,6 +4078,7 @@
3708
4078
  this.linkedElementListener = true;
3709
4079
  }
3710
4080
  }
4081
+
3711
4082
  $linkedElement.focus();
3712
4083
  };
3713
4084
 
@@ -3716,6 +4087,8 @@
3716
4087
  * focusable until it has received programmatic focus and a screen reader has announced it.
3717
4088
  *
3718
4089
  * Remove the CSS class that removes the native focus styles.
4090
+ *
4091
+ * @deprecated Will be made private in v5.0
3719
4092
  */
3720
4093
  SkipLink.prototype.removeFocusProperties = function () {
3721
4094
  this.$linkedElement.removeAttribute('tabindex');
@@ -3728,17 +4101,20 @@
3728
4101
  * Extract the fragment (everything after the hash symbol) from a URL, but not including
3729
4102
  * the symbol.
3730
4103
  *
3731
- * @returns {string} Fragment from URL, without the hash symbol
4104
+ * @deprecated Will be made private in v5.0
4105
+ * @returns {string | undefined} Fragment from URL, without the hash symbol
3732
4106
  */
3733
4107
  SkipLink.prototype.getFragmentFromUrl = function () {
3734
4108
  // Bail if the anchor link doesn't have a hash
3735
4109
  if (!this.$module.hash) {
3736
- return false
4110
+ return
3737
4111
  }
3738
4112
 
3739
4113
  return this.$module.hash.split('#').pop()
3740
4114
  };
3741
4115
 
4116
+ // @ts-nocheck
4117
+
3742
4118
  (function(undefined) {
3743
4119
 
3744
4120
  // Detection from https://raw.githubusercontent.com/Financial-Times/polyfill-library/master/polyfills/Element/prototype/nextElementSibling/detect.js
@@ -3759,6 +4135,8 @@
3759
4135
 
3760
4136
  }).call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
3761
4137
 
4138
+ // @ts-nocheck
4139
+
3762
4140
  (function(undefined) {
3763
4141
 
3764
4142
  // Detection from https://raw.githubusercontent.com/Financial-Times/polyfill-library/master/polyfills/Element/prototype/previousElementSibling/detect.js
@@ -3785,20 +4163,54 @@
3785
4163
  * Tabs component
3786
4164
  *
3787
4165
  * @class
3788
- * @param {HTMLElement} $module - HTML element to use for tabs
4166
+ * @param {Element} $module - HTML element to use for tabs
3789
4167
  */
3790
4168
  function Tabs ($module) {
4169
+ if (!($module instanceof HTMLElement)) {
4170
+ return this
4171
+ }
4172
+
4173
+ var $tabs = $module.querySelectorAll('a.govuk-tabs__tab');
4174
+ if (!$tabs.length) {
4175
+ return this
4176
+ }
4177
+
4178
+ /** @deprecated Will be made private in v5.0 */
3791
4179
  this.$module = $module;
3792
- this.$tabs = $module.querySelectorAll('.govuk-tabs__tab');
3793
4180
 
4181
+ /** @deprecated Will be made private in v5.0 */
4182
+ this.$tabs = $tabs;
4183
+
4184
+ /** @deprecated Will be made private in v5.0 */
3794
4185
  this.keys = { left: 37, right: 39, up: 38, down: 40 };
4186
+
4187
+ /** @deprecated Will be made private in v5.0 */
3795
4188
  this.jsHiddenClass = 'govuk-tabs__panel--hidden';
4189
+
4190
+ // Save bounded functions to use when removing event listeners during teardown
4191
+
4192
+ /** @deprecated Will be made private in v5.0 */
4193
+ this.boundTabClick = this.onTabClick.bind(this);
4194
+
4195
+ /** @deprecated Will be made private in v5.0 */
4196
+ this.boundTabKeydown = this.onTabKeydown.bind(this);
4197
+
4198
+ /** @deprecated Will be made private in v5.0 */
4199
+ this.boundOnHashChange = this.onHashChange.bind(this);
4200
+
4201
+ /** @deprecated Will be made private in v5.0 */
4202
+ this.changingHash = false;
3796
4203
  }
3797
4204
 
3798
4205
  /**
3799
4206
  * Initialise component
3800
4207
  */
3801
4208
  Tabs.prototype.init = function () {
4209
+ // Check that required elements are present
4210
+ if (!this.$module || !this.$tabs) {
4211
+ return
4212
+ }
4213
+
3802
4214
  if (typeof window.matchMedia === 'function') {
3803
4215
  this.setupResponsiveChecks();
3804
4216
  } else {
@@ -3808,8 +4220,11 @@
3808
4220
 
3809
4221
  /**
3810
4222
  * Setup viewport resize check
4223
+ *
4224
+ * @deprecated Will be made private in v5.0
3811
4225
  */
3812
4226
  Tabs.prototype.setupResponsiveChecks = function () {
4227
+ /** @deprecated Will be made private in v5.0 */
3813
4228
  this.mql = window.matchMedia('(min-width: 40.0625em)');
3814
4229
  this.mql.addListener(this.checkMode.bind(this));
3815
4230
  this.checkMode();
@@ -3817,6 +4232,8 @@
3817
4232
 
3818
4233
  /**
3819
4234
  * Setup or teardown handler for viewport resize check
4235
+ *
4236
+ * @deprecated Will be made private in v5.0
3820
4237
  */
3821
4238
  Tabs.prototype.checkMode = function () {
3822
4239
  if (this.mql.matches) {
@@ -3828,8 +4245,11 @@
3828
4245
 
3829
4246
  /**
3830
4247
  * Setup tab component
4248
+ *
4249
+ * @deprecated Will be made private in v5.0
3831
4250
  */
3832
4251
  Tabs.prototype.setup = function () {
4252
+ var $component = this;
3833
4253
  var $module = this.$module;
3834
4254
  var $tabs = this.$tabs;
3835
4255
  var $tabList = $module.querySelector('.govuk-tabs__list');
@@ -3847,37 +4267,39 @@
3847
4267
 
3848
4268
  nodeListForEach($tabs, function ($tab) {
3849
4269
  // Set HTML attributes
3850
- this.setAttributes($tab);
3851
-
3852
- // Save bounded functions to use when removing event listeners during teardown
3853
- $tab.boundTabClick = this.onTabClick.bind(this);
3854
- $tab.boundTabKeydown = this.onTabKeydown.bind(this);
4270
+ $component.setAttributes($tab);
3855
4271
 
3856
4272
  // Handle events
3857
- $tab.addEventListener('click', $tab.boundTabClick, true);
3858
- $tab.addEventListener('keydown', $tab.boundTabKeydown, true);
4273
+ $tab.addEventListener('click', $component.boundTabClick, true);
4274
+ $tab.addEventListener('keydown', $component.boundTabKeydown, true);
3859
4275
 
3860
4276
  // Remove old active panels
3861
- this.hideTab($tab);
3862
- }.bind(this));
4277
+ $component.hideTab($tab);
4278
+ });
3863
4279
 
3864
4280
  // Show either the active tab according to the URL's hash or the first tab
3865
4281
  var $activeTab = this.getTab(window.location.hash) || this.$tabs[0];
4282
+ if (!$activeTab) {
4283
+ return
4284
+ }
4285
+
3866
4286
  this.showTab($activeTab);
3867
4287
 
3868
4288
  // Handle hashchange events
3869
- $module.boundOnHashChange = this.onHashChange.bind(this);
3870
- window.addEventListener('hashchange', $module.boundOnHashChange, true);
4289
+ window.addEventListener('hashchange', this.boundOnHashChange, true);
3871
4290
  };
3872
4291
 
3873
4292
  /**
3874
4293
  * Teardown tab component
4294
+ *
4295
+ * @deprecated Will be made private in v5.0
3875
4296
  */
3876
4297
  Tabs.prototype.teardown = function () {
4298
+ var $component = this;
3877
4299
  var $module = this.$module;
3878
4300
  var $tabs = this.$tabs;
3879
4301
  var $tabList = $module.querySelector('.govuk-tabs__list');
3880
- var $tabListItems = $module.querySelectorAll('.govuk-tabs__list-item');
4302
+ var $tabListItems = $module.querySelectorAll('a.govuk-tabs__list-item');
3881
4303
 
3882
4304
  if (!$tabs || !$tabList || !$tabListItems) {
3883
4305
  return
@@ -3886,29 +4308,29 @@
3886
4308
  $tabList.removeAttribute('role');
3887
4309
 
3888
4310
  nodeListForEach($tabListItems, function ($item) {
3889
- $item.removeAttribute('role', 'presentation');
4311
+ $item.removeAttribute('role');
3890
4312
  });
3891
4313
 
3892
4314
  nodeListForEach($tabs, function ($tab) {
3893
4315
  // Remove events
3894
- $tab.removeEventListener('click', $tab.boundTabClick, true);
3895
- $tab.removeEventListener('keydown', $tab.boundTabKeydown, true);
4316
+ $tab.removeEventListener('click', $component.boundTabClick, true);
4317
+ $tab.removeEventListener('keydown', $component.boundTabKeydown, true);
3896
4318
 
3897
4319
  // Unset HTML attributes
3898
- this.unsetAttributes($tab);
3899
- }.bind(this));
4320
+ $component.unsetAttributes($tab);
4321
+ });
3900
4322
 
3901
4323
  // Remove hashchange event handler
3902
- window.removeEventListener('hashchange', $module.boundOnHashChange, true);
4324
+ window.removeEventListener('hashchange', this.boundOnHashChange, true);
3903
4325
  };
3904
4326
 
3905
4327
  /**
3906
4328
  * Handle hashchange event
3907
4329
  *
3908
- * @param {HashChangeEvent} event - Hash change event
4330
+ * @deprecated Will be made private in v5.0
3909
4331
  * @returns {void | undefined} Returns void, or undefined when prevented
3910
4332
  */
3911
- Tabs.prototype.onHashChange = function (event) {
4333
+ Tabs.prototype.onHashChange = function () {
3912
4334
  var hash = window.location.hash;
3913
4335
  var $tabWithHash = this.getTab(hash);
3914
4336
  if (!$tabWithHash) {
@@ -3923,6 +4345,9 @@
3923
4345
 
3924
4346
  // Show either the active tab according to the URL's hash or the first tab
3925
4347
  var $previousTab = this.getCurrentTab();
4348
+ if (!$previousTab) {
4349
+ return
4350
+ }
3926
4351
 
3927
4352
  this.hideTab($previousTab);
3928
4353
  this.showTab($tabWithHash);
@@ -3932,6 +4357,7 @@
3932
4357
  /**
3933
4358
  * Hide panel for tab link
3934
4359
  *
4360
+ * @deprecated Will be made private in v5.0
3935
4361
  * @param {HTMLAnchorElement} $tab - Tab link
3936
4362
  */
3937
4363
  Tabs.prototype.hideTab = function ($tab) {
@@ -3942,6 +4368,7 @@
3942
4368
  /**
3943
4369
  * Show panel for tab link
3944
4370
  *
4371
+ * @deprecated Will be made private in v5.0
3945
4372
  * @param {HTMLAnchorElement} $tab - Tab link
3946
4373
  */
3947
4374
  Tabs.prototype.showTab = function ($tab) {
@@ -3952,16 +4379,19 @@
3952
4379
  /**
3953
4380
  * Get tab link by hash
3954
4381
  *
4382
+ * @deprecated Will be made private in v5.0
3955
4383
  * @param {string} hash - Hash fragment including #
3956
4384
  * @returns {HTMLAnchorElement | null} Tab link
3957
4385
  */
3958
4386
  Tabs.prototype.getTab = function (hash) {
3959
- return this.$module.querySelector('.govuk-tabs__tab[href="' + hash + '"]')
4387
+ // @ts-expect-error `HTMLAnchorElement` type expected
4388
+ return this.$module.querySelector('a.govuk-tabs__tab[href="' + hash + '"]')
3960
4389
  };
3961
4390
 
3962
4391
  /**
3963
4392
  * Set tab link and panel attributes
3964
4393
  *
4394
+ * @deprecated Will be made private in v5.0
3965
4395
  * @param {HTMLAnchorElement} $tab - Tab link
3966
4396
  */
3967
4397
  Tabs.prototype.setAttributes = function ($tab) {
@@ -3975,6 +4405,10 @@
3975
4405
 
3976
4406
  // set panel attributes
3977
4407
  var $panel = this.getPanel($tab);
4408
+ if (!$panel) {
4409
+ return
4410
+ }
4411
+
3978
4412
  $panel.setAttribute('role', 'tabpanel');
3979
4413
  $panel.setAttribute('aria-labelledby', $tab.id);
3980
4414
  $panel.classList.add(this.jsHiddenClass);
@@ -3983,6 +4417,7 @@
3983
4417
  /**
3984
4418
  * Unset tab link and panel attributes
3985
4419
  *
4420
+ * @deprecated Will be made private in v5.0
3986
4421
  * @param {HTMLAnchorElement} $tab - Tab link
3987
4422
  */
3988
4423
  Tabs.prototype.unsetAttributes = function ($tab) {
@@ -3995,6 +4430,10 @@
3995
4430
 
3996
4431
  // unset panel attributes
3997
4432
  var $panel = this.getPanel($tab);
4433
+ if (!$panel) {
4434
+ return
4435
+ }
4436
+
3998
4437
  $panel.removeAttribute('role');
3999
4438
  $panel.removeAttribute('aria-labelledby');
4000
4439
  $panel.classList.remove(this.jsHiddenClass);
@@ -4003,20 +4442,23 @@
4003
4442
  /**
4004
4443
  * Handle tab link clicks
4005
4444
  *
4445
+ * @deprecated Will be made private in v5.0
4006
4446
  * @param {MouseEvent} event - Mouse click event
4007
- * @returns {void | false} Returns void, or false within tab link
4447
+ * @returns {void} Returns void
4008
4448
  */
4009
4449
  Tabs.prototype.onTabClick = function (event) {
4010
- if (!event.target.classList.contains('govuk-tabs__tab')) {
4011
- // Allow events on child DOM elements to bubble up to tab parent
4012
- return false
4450
+ var $currentTab = this.getCurrentTab();
4451
+ var $nextTab = event.currentTarget;
4452
+
4453
+ if (!$currentTab || !($nextTab instanceof HTMLAnchorElement)) {
4454
+ return
4013
4455
  }
4456
+
4014
4457
  event.preventDefault();
4015
- var $newTab = event.target;
4016
- var $currentTab = this.getCurrentTab();
4458
+
4017
4459
  this.hideTab($currentTab);
4018
- this.showTab($newTab);
4019
- this.createHistoryEntry($newTab);
4460
+ this.showTab($nextTab);
4461
+ this.createHistoryEntry($nextTab);
4020
4462
  };
4021
4463
 
4022
4464
  /**
@@ -4025,10 +4467,14 @@
4025
4467
  * - Allows back/forward to navigate tabs
4026
4468
  * - Avoids page jump when hash changes
4027
4469
  *
4470
+ * @deprecated Will be made private in v5.0
4028
4471
  * @param {HTMLAnchorElement} $tab - Tab link
4029
4472
  */
4030
4473
  Tabs.prototype.createHistoryEntry = function ($tab) {
4031
4474
  var $panel = this.getPanel($tab);
4475
+ if (!$panel) {
4476
+ return
4477
+ }
4032
4478
 
4033
4479
  // Save and restore the id
4034
4480
  // so the page doesn't jump when a user clicks a tab (which changes the hash)
@@ -4045,6 +4491,7 @@
4045
4491
  * - Press right/down arrow for next tab
4046
4492
  * - Press left/up arrow for previous tab
4047
4493
  *
4494
+ * @deprecated Will be made private in v5.0
4048
4495
  * @param {KeyboardEvent} event - Keydown event
4049
4496
  */
4050
4497
  Tabs.prototype.onTabKeydown = function (event) {
@@ -4064,10 +4511,12 @@
4064
4511
 
4065
4512
  /**
4066
4513
  * Activate next tab
4514
+ *
4515
+ * @deprecated Will be made private in v5.0
4067
4516
  */
4068
4517
  Tabs.prototype.activateNextTab = function () {
4069
4518
  var $currentTab = this.getCurrentTab();
4070
- if (!$currentTab) {
4519
+ if (!$currentTab || !$currentTab.parentElement) {
4071
4520
  return
4072
4521
  }
4073
4522
 
@@ -4076,21 +4525,25 @@
4076
4525
  return
4077
4526
  }
4078
4527
 
4079
- var $nextTab = $nextTabListItem.querySelector('.govuk-tabs__tab');
4080
- if ($nextTab) {
4081
- this.hideTab($currentTab);
4082
- this.showTab($nextTab);
4083
- $nextTab.focus();
4084
- this.createHistoryEntry($nextTab);
4528
+ var $nextTab = $nextTabListItem.querySelector('a.govuk-tabs__tab');
4529
+ if (!$nextTab) {
4530
+ return
4085
4531
  }
4532
+
4533
+ this.hideTab($currentTab);
4534
+ this.showTab($nextTab);
4535
+ $nextTab.focus();
4536
+ this.createHistoryEntry($nextTab);
4086
4537
  };
4087
4538
 
4088
4539
  /**
4089
4540
  * Activate previous tab
4541
+ *
4542
+ * @deprecated Will be made private in v5.0
4090
4543
  */
4091
4544
  Tabs.prototype.activatePreviousTab = function () {
4092
4545
  var $currentTab = this.getCurrentTab();
4093
- if (!$currentTab) {
4546
+ if (!$currentTab || !$currentTab.parentElement) {
4094
4547
  return
4095
4548
  }
4096
4549
 
@@ -4099,75 +4552,98 @@
4099
4552
  return
4100
4553
  }
4101
4554
 
4102
- var $previousTab = $previousTabListItem.querySelector('.govuk-tabs__tab');
4103
- if ($previousTab) {
4104
- this.hideTab($currentTab);
4105
- this.showTab($previousTab);
4106
- $previousTab.focus();
4107
- this.createHistoryEntry($previousTab);
4555
+ var $previousTab = $previousTabListItem.querySelector('a.govuk-tabs__tab');
4556
+ if (!$previousTab) {
4557
+ return
4108
4558
  }
4559
+
4560
+ this.hideTab($currentTab);
4561
+ this.showTab($previousTab);
4562
+ $previousTab.focus();
4563
+ this.createHistoryEntry($previousTab);
4109
4564
  };
4110
4565
 
4111
4566
  /**
4112
4567
  * Get tab panel for tab link
4113
4568
  *
4569
+ * @deprecated Will be made private in v5.0
4114
4570
  * @param {HTMLAnchorElement} $tab - Tab link
4115
- * @returns {HTMLDivElement} Tab panel
4571
+ * @returns {Element | null} Tab panel
4116
4572
  */
4117
4573
  Tabs.prototype.getPanel = function ($tab) {
4118
- var $panel = this.$module.querySelector(this.getHref($tab));
4119
- return $panel
4574
+ return this.$module.querySelector(this.getHref($tab))
4120
4575
  };
4121
4576
 
4122
4577
  /**
4123
4578
  * Show tab panel for tab link
4124
4579
  *
4580
+ * @deprecated Will be made private in v5.0
4125
4581
  * @param {HTMLAnchorElement} $tab - Tab link
4126
4582
  */
4127
4583
  Tabs.prototype.showPanel = function ($tab) {
4128
4584
  var $panel = this.getPanel($tab);
4585
+ if (!$panel) {
4586
+ return
4587
+ }
4588
+
4129
4589
  $panel.classList.remove(this.jsHiddenClass);
4130
4590
  };
4131
4591
 
4132
4592
  /**
4133
4593
  * Hide tab panel for tab link
4134
4594
  *
4595
+ * @deprecated Will be made private in v5.0
4135
4596
  * @param {HTMLAnchorElement} $tab - Tab link
4136
4597
  */
4137
4598
  Tabs.prototype.hidePanel = function ($tab) {
4138
4599
  var $panel = this.getPanel($tab);
4600
+ if (!$panel) {
4601
+ return
4602
+ }
4603
+
4139
4604
  $panel.classList.add(this.jsHiddenClass);
4140
4605
  };
4141
4606
 
4142
4607
  /**
4143
4608
  * Unset 'selected' state for tab link
4144
4609
  *
4610
+ * @deprecated Will be made private in v5.0
4145
4611
  * @param {HTMLAnchorElement} $tab - Tab link
4146
4612
  */
4147
4613
  Tabs.prototype.unhighlightTab = function ($tab) {
4614
+ if (!$tab.parentElement) {
4615
+ return
4616
+ }
4617
+
4148
4618
  $tab.setAttribute('aria-selected', 'false');
4149
- $tab.parentNode.classList.remove('govuk-tabs__list-item--selected');
4619
+ $tab.parentElement.classList.remove('govuk-tabs__list-item--selected');
4150
4620
  $tab.setAttribute('tabindex', '-1');
4151
4621
  };
4152
4622
 
4153
4623
  /**
4154
4624
  * Set 'selected' state for tab link
4155
4625
  *
4626
+ * @deprecated Will be made private in v5.0
4156
4627
  * @param {HTMLAnchorElement} $tab - Tab link
4157
4628
  */
4158
4629
  Tabs.prototype.highlightTab = function ($tab) {
4630
+ if (!$tab.parentElement) {
4631
+ return
4632
+ }
4633
+
4159
4634
  $tab.setAttribute('aria-selected', 'true');
4160
- $tab.parentNode.classList.add('govuk-tabs__list-item--selected');
4635
+ $tab.parentElement.classList.add('govuk-tabs__list-item--selected');
4161
4636
  $tab.setAttribute('tabindex', '0');
4162
4637
  };
4163
4638
 
4164
4639
  /**
4165
4640
  * Get current tab link
4166
4641
  *
4167
- * @returns {HTMLAnchorElement | undefined} Tab link
4642
+ * @deprecated Will be made private in v5.0
4643
+ * @returns {HTMLAnchorElement | null} Tab link
4168
4644
  */
4169
4645
  Tabs.prototype.getCurrentTab = function () {
4170
- return this.$module.querySelector('.govuk-tabs__list-item--selected .govuk-tabs__tab')
4646
+ return this.$module.querySelector('.govuk-tabs__list-item--selected a.govuk-tabs__tab')
4171
4647
  };
4172
4648
 
4173
4649
  /**
@@ -4177,6 +4653,7 @@
4177
4653
  * should be a utility function most prob
4178
4654
  * {@link http://labs.thesedays.com/blog/2010/01/08/getting-the-href-value-with-jquery-in-ie/}
4179
4655
  *
4656
+ * @deprecated Will be made private in v5.0
4180
4657
  * @param {HTMLAnchorElement} $tab - Tab link
4181
4658
  * @returns {string} Hash fragment including #
4182
4659
  */
@@ -4199,7 +4676,7 @@
4199
4676
 
4200
4677
  // Allow the user to initialise GOV.UK Frontend in only certain sections of the page
4201
4678
  // Defaults to the entire document if nothing is set.
4202
- var $scope = typeof config.scope !== 'undefined' ? config.scope : document;
4679
+ var $scope = config.scope instanceof HTMLElement ? config.scope : document;
4203
4680
 
4204
4681
  var $accordions = $scope.querySelectorAll('[data-module="govuk-accordion"]');
4205
4682
  nodeListForEach($accordions, function ($accordion) {
@@ -4250,7 +4727,9 @@
4250
4727
 
4251
4728
  // Find first skip link module to enhance.
4252
4729
  var $skipLink = $scope.querySelector('[data-module="govuk-skip-link"]');
4253
- new SkipLink($skipLink).init();
4730
+ if ($skipLink) {
4731
+ new SkipLink($skipLink).init();
4732
+ }
4254
4733
 
4255
4734
  var $tabs = $scope.querySelectorAll('[data-module="govuk-tabs"]');
4256
4735
  nodeListForEach($tabs, function ($tabs) {
@@ -4262,7 +4741,7 @@
4262
4741
  * Config for all components
4263
4742
  *
4264
4743
  * @typedef {object} Config
4265
- * @property {HTMLElement} [scope=document] - Scope to query for components
4744
+ * @property {Element} [scope=document] - Scope to query for components
4266
4745
  * @property {import('./components/accordion/accordion.mjs').AccordionConfig} [accordion] - Accordion config
4267
4746
  * @property {import('./components/button/button.mjs').ButtonConfig} [button] - Button config
4268
4747
  * @property {import('./components/character-count/character-count.mjs').CharacterCountConfig} [characterCount] - Character Count config
@@ -4271,6 +4750,7 @@
4271
4750
  */
4272
4751
 
4273
4752
  exports.initAll = initAll;
4753
+ exports.version = version;
4274
4754
  exports.Accordion = Accordion;
4275
4755
  exports.Button = Button;
4276
4756
  exports.Details = Details;