govuk_tech_docs 3.4.5 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (327) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/publish.yaml +8 -4
  3. data/.github/workflows/test.yaml +11 -4
  4. data/.rubocop.yml +1 -1
  5. data/CHANGELOG.md +22 -1
  6. data/example/.ruby-version +1 -1
  7. data/example/source/javascripts/govuk_frontend.js +1 -0
  8. data/govuk_tech_docs.gemspec +17 -11
  9. data/lib/assets/javascripts/_modules/page-expiry.js +2 -2
  10. data/lib/assets/javascripts/_modules/table-of-contents.js +48 -2
  11. data/lib/assets/javascripts/govuk_frontend_all.js +3 -0
  12. data/lib/assets/javascripts/govuk_tech_docs.js +0 -2
  13. data/lib/assets/stylesheets/_govuk_tech_docs.scss +4 -10
  14. data/lib/assets/stylesheets/modules/_page-review.scss +0 -2
  15. data/lib/assets/stylesheets/modules/_search.scss +0 -4
  16. data/lib/govuk_tech_docs/meta_tags.rb +1 -1
  17. data/lib/govuk_tech_docs/redirects.rb +1 -1
  18. data/lib/govuk_tech_docs/table_of_contents/heading_tree_builder.rb +1 -1
  19. data/lib/govuk_tech_docs/table_of_contents/helpers.rb +2 -2
  20. data/lib/govuk_tech_docs/tech_docs_html_renderer.rb +6 -2
  21. data/lib/govuk_tech_docs/version.rb +1 -1
  22. data/lib/govuk_tech_docs.rb +20 -3
  23. data/lib/source/layouts/_header.erb +8 -12
  24. data/lib/source/layouts/_page_review.erb +1 -1
  25. data/lib/source/layouts/_search.erb +2 -2
  26. data/lib/source/layouts/core.erb +8 -2
  27. data/lib/source/stylesheets/manifest.css +1 -1
  28. data/node_modules/govuk-frontend/dist/govuk/_base.scss +5 -0
  29. data/node_modules/govuk-frontend/dist/govuk/all.bundle.js +2554 -0
  30. data/node_modules/govuk-frontend/dist/govuk/all.bundle.mjs +2529 -0
  31. data/node_modules/govuk-frontend/dist/govuk/all.mjs +18 -0
  32. data/node_modules/govuk-frontend/dist/govuk/all.scss +3 -0
  33. data/node_modules/govuk-frontend/dist/govuk/assets/images/favicon.svg +1 -0
  34. data/node_modules/govuk-frontend/dist/govuk/assets/images/govuk-crest.svg +1 -0
  35. data/node_modules/govuk-frontend/dist/govuk/assets/images/govuk-icon-180.png +0 -0
  36. data/node_modules/govuk-frontend/dist/govuk/assets/images/govuk-icon-192.png +0 -0
  37. data/node_modules/govuk-frontend/dist/govuk/assets/images/govuk-icon-512.png +0 -0
  38. data/node_modules/govuk-frontend/dist/govuk/assets/images/govuk-icon-mask.svg +1 -0
  39. data/node_modules/govuk-frontend/dist/govuk/assets/images/govuk-opengraph-image.png +0 -0
  40. data/node_modules/govuk-frontend/dist/govuk/common/closest-attribute-value.mjs +7 -0
  41. data/node_modules/govuk-frontend/dist/govuk/common/govuk-frontend-version.mjs +4 -0
  42. data/node_modules/govuk-frontend/dist/govuk/common/index.mjs +159 -0
  43. data/node_modules/govuk-frontend/dist/govuk/common/normalise-dataset.mjs +18 -0
  44. data/node_modules/govuk-frontend/dist/govuk/common/normalise-string.mjs +31 -0
  45. data/node_modules/govuk-frontend/dist/govuk/components/_all.scss +10 -0
  46. data/node_modules/govuk-frontend/{govuk/components/_all.scss → dist/govuk/components/_index.scss} +8 -3
  47. data/node_modules/govuk-frontend/dist/govuk/components/accordion/_accordion.scss +4 -0
  48. data/node_modules/govuk-frontend/{govuk → dist/govuk}/components/accordion/_index.scss +55 -77
  49. data/node_modules/govuk-frontend/dist/govuk/components/accordion/accordion.bundle.js +785 -0
  50. data/node_modules/govuk-frontend/dist/govuk/components/accordion/accordion.bundle.mjs +777 -0
  51. data/node_modules/govuk-frontend/dist/govuk/components/accordion/accordion.mjs +349 -0
  52. data/node_modules/govuk-frontend/dist/govuk/components/back-link/_back-link.scss +4 -0
  53. data/node_modules/govuk-frontend/{govuk → dist/govuk}/components/back-link/_index.scss +12 -36
  54. data/node_modules/govuk-frontend/dist/govuk/components/breadcrumbs/_breadcrumbs.scss +4 -0
  55. data/node_modules/govuk-frontend/{govuk → dist/govuk}/components/breadcrumbs/_index.scss +11 -36
  56. data/node_modules/govuk-frontend/dist/govuk/components/button/_button.scss +4 -0
  57. data/node_modules/govuk-frontend/{govuk → dist/govuk}/components/button/_index.scss +30 -103
  58. data/node_modules/govuk-frontend/dist/govuk/components/button/button.bundle.js +318 -0
  59. data/node_modules/govuk-frontend/dist/govuk/components/button/button.bundle.mjs +310 -0
  60. data/node_modules/govuk-frontend/dist/govuk/components/button/button.mjs +73 -0
  61. data/node_modules/govuk-frontend/dist/govuk/components/character-count/_character-count.scss +4 -0
  62. data/node_modules/govuk-frontend/{govuk → dist/govuk}/components/character-count/_index.scss +8 -6
  63. data/node_modules/govuk-frontend/dist/govuk/components/character-count/character-count.bundle.js +761 -0
  64. data/node_modules/govuk-frontend/dist/govuk/components/character-count/character-count.bundle.mjs +753 -0
  65. data/node_modules/govuk-frontend/dist/govuk/components/character-count/character-count.mjs +295 -0
  66. data/node_modules/govuk-frontend/dist/govuk/components/checkboxes/_checkboxes.scss +4 -0
  67. data/node_modules/govuk-frontend/{govuk → dist/govuk}/components/checkboxes/_index.scss +108 -132
  68. data/node_modules/govuk-frontend/dist/govuk/components/checkboxes/checkboxes.bundle.js +268 -0
  69. data/node_modules/govuk-frontend/dist/govuk/components/checkboxes/checkboxes.bundle.mjs +260 -0
  70. data/node_modules/govuk-frontend/dist/govuk/components/checkboxes/checkboxes.mjs +111 -0
  71. data/node_modules/govuk-frontend/dist/govuk/components/cookie-banner/_cookie-banner.scss +4 -0
  72. data/node_modules/govuk-frontend/{govuk → dist/govuk}/components/cookie-banner/_index.scss +19 -15
  73. data/node_modules/govuk-frontend/dist/govuk/components/date-input/_date-input.scss +4 -0
  74. data/node_modules/govuk-frontend/{govuk → dist/govuk}/components/date-input/_index.scss +3 -2
  75. data/node_modules/govuk-frontend/dist/govuk/components/details/_details.scss +4 -0
  76. data/node_modules/govuk-frontend/dist/govuk/components/details/_index.scss +139 -0
  77. data/node_modules/govuk-frontend/dist/govuk/components/error-message/_error-message.scss +4 -0
  78. data/node_modules/govuk-frontend/{govuk → dist/govuk}/components/error-message/_index.scss +2 -0
  79. data/node_modules/govuk-frontend/dist/govuk/components/error-summary/_error-summary.scss +4 -0
  80. data/node_modules/govuk-frontend/{govuk → dist/govuk}/components/error-summary/_index.scss +19 -6
  81. data/node_modules/govuk-frontend/dist/govuk/components/error-summary/error-summary.bundle.js +378 -0
  82. data/node_modules/govuk-frontend/dist/govuk/components/error-summary/error-summary.bundle.mjs +370 -0
  83. data/node_modules/govuk-frontend/dist/govuk/components/error-summary/error-summary.mjs +103 -0
  84. data/node_modules/govuk-frontend/dist/govuk/components/exit-this-page/_exit-this-page.scss +4 -0
  85. data/node_modules/govuk-frontend/{govuk → dist/govuk}/components/exit-this-page/_index.scss +5 -10
  86. data/node_modules/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.bundle.js +662 -0
  87. data/node_modules/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.bundle.mjs +654 -0
  88. data/node_modules/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.mjs +226 -0
  89. data/node_modules/govuk-frontend/dist/govuk/components/fieldset/_fieldset.scss +4 -0
  90. data/node_modules/govuk-frontend/{govuk → dist/govuk}/components/fieldset/_index.scss +20 -15
  91. data/node_modules/govuk-frontend/dist/govuk/components/file-upload/_file-upload.scss +4 -0
  92. data/node_modules/govuk-frontend/{govuk → dist/govuk}/components/file-upload/_index.scss +11 -15
  93. data/node_modules/govuk-frontend/dist/govuk/components/footer/_footer.scss +4 -0
  94. data/node_modules/govuk-frontend/{govuk → dist/govuk}/components/footer/_index.scss +25 -68
  95. data/node_modules/govuk-frontend/dist/govuk/components/header/_header.scss +4 -0
  96. data/node_modules/govuk-frontend/{govuk → dist/govuk}/components/header/_index.scss +84 -79
  97. data/node_modules/govuk-frontend/dist/govuk/components/header/header.bundle.js +252 -0
  98. data/node_modules/govuk-frontend/dist/govuk/components/header/header.bundle.mjs +244 -0
  99. data/node_modules/govuk-frontend/dist/govuk/components/header/header.mjs +88 -0
  100. data/node_modules/govuk-frontend/{govuk → dist/govuk}/components/hint/_hint.scss +2 -0
  101. data/node_modules/govuk-frontend/{govuk → dist/govuk}/components/hint/_index.scss +3 -0
  102. data/node_modules/govuk-frontend/{govuk → dist/govuk}/components/input/_index.scss +24 -49
  103. data/node_modules/govuk-frontend/{govuk → dist/govuk}/components/input/_input.scss +2 -0
  104. data/node_modules/govuk-frontend/{govuk → dist/govuk}/components/inset-text/_index.scss +2 -0
  105. data/node_modules/govuk-frontend/dist/govuk/components/inset-text/_inset-text.scss +4 -0
  106. data/node_modules/govuk-frontend/{govuk → dist/govuk}/components/label/_index.scss +12 -7
  107. data/node_modules/govuk-frontend/{govuk → dist/govuk}/components/label/_label.scss +2 -0
  108. data/node_modules/govuk-frontend/{govuk → dist/govuk}/components/notification-banner/_index.scss +11 -7
  109. data/node_modules/govuk-frontend/dist/govuk/components/notification-banner/_notification-banner.scss +4 -0
  110. data/node_modules/govuk-frontend/dist/govuk/components/notification-banner/notification-banner.bundle.js +320 -0
  111. data/node_modules/govuk-frontend/dist/govuk/components/notification-banner/notification-banner.bundle.mjs +312 -0
  112. data/node_modules/govuk-frontend/dist/govuk/components/notification-banner/notification-banner.mjs +51 -0
  113. data/node_modules/govuk-frontend/{govuk → dist/govuk}/components/pagination/_index.scss +36 -55
  114. data/node_modules/govuk-frontend/dist/govuk/components/pagination/_pagination.scss +4 -0
  115. data/node_modules/govuk-frontend/dist/govuk/components/panel/_index.scss +58 -0
  116. data/node_modules/govuk-frontend/{govuk → dist/govuk}/components/panel/_panel.scss +2 -0
  117. data/node_modules/govuk-frontend/dist/govuk/components/password-input/_index.scss +57 -0
  118. data/node_modules/govuk-frontend/dist/govuk/components/password-input/_password-input.scss +4 -0
  119. data/node_modules/govuk-frontend/dist/govuk/components/password-input/password-input.bundle.js +594 -0
  120. data/node_modules/govuk-frontend/dist/govuk/components/password-input/password-input.bundle.mjs +586 -0
  121. data/node_modules/govuk-frontend/dist/govuk/components/password-input/password-input.mjs +154 -0
  122. data/node_modules/govuk-frontend/{govuk → dist/govuk}/components/phase-banner/_index.scss +10 -1
  123. data/node_modules/govuk-frontend/dist/govuk/components/phase-banner/_phase-banner.scss +4 -0
  124. data/node_modules/govuk-frontend/{govuk → dist/govuk}/components/radios/_index.scss +92 -113
  125. data/node_modules/govuk-frontend/dist/govuk/components/radios/_radios.scss +4 -0
  126. data/node_modules/govuk-frontend/dist/govuk/components/radios/radios.bundle.js +245 -0
  127. data/node_modules/govuk-frontend/dist/govuk/components/radios/radios.bundle.mjs +237 -0
  128. data/node_modules/govuk-frontend/dist/govuk/components/radios/radios.mjs +88 -0
  129. data/node_modules/govuk-frontend/{govuk → dist/govuk}/components/select/_index.scss +9 -16
  130. data/node_modules/govuk-frontend/dist/govuk/components/select/_select.scss +4 -0
  131. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/_index.scss +168 -0
  132. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/_service-navigation.scss +4 -0
  133. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/service-navigation.bundle.js +249 -0
  134. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/service-navigation.bundle.mjs +241 -0
  135. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/service-navigation.mjs +85 -0
  136. data/node_modules/govuk-frontend/{govuk → dist/govuk}/components/skip-link/_index.scss +10 -5
  137. data/node_modules/govuk-frontend/dist/govuk/components/skip-link/_skip-link.scss +4 -0
  138. data/node_modules/govuk-frontend/dist/govuk/components/skip-link/skip-link.bundle.js +244 -0
  139. data/node_modules/govuk-frontend/dist/govuk/components/skip-link/skip-link.bundle.mjs +236 -0
  140. data/node_modules/govuk-frontend/dist/govuk/components/skip-link/skip-link.mjs +58 -0
  141. data/node_modules/govuk-frontend/{govuk → dist/govuk}/components/summary-list/_index.scss +22 -20
  142. data/node_modules/govuk-frontend/dist/govuk/components/summary-list/_summary-list.scss +4 -0
  143. data/node_modules/govuk-frontend/{govuk → dist/govuk}/components/table/_index.scss +21 -17
  144. data/node_modules/govuk-frontend/{govuk → dist/govuk}/components/table/_table.scss +2 -0
  145. data/node_modules/govuk-frontend/{govuk → dist/govuk}/components/tabs/_index.scss +13 -9
  146. data/node_modules/govuk-frontend/{govuk → dist/govuk}/components/tabs/_tabs.scss +2 -0
  147. data/node_modules/govuk-frontend/dist/govuk/components/tabs/tabs.bundle.js +453 -0
  148. data/node_modules/govuk-frontend/dist/govuk/components/tabs/tabs.bundle.mjs +445 -0
  149. data/node_modules/govuk-frontend/dist/govuk/components/tabs/tabs.mjs +283 -0
  150. data/node_modules/govuk-frontend/dist/govuk/components/tag/_index.scss +97 -0
  151. data/node_modules/govuk-frontend/{govuk → dist/govuk}/components/tag/_tag.scss +2 -0
  152. data/node_modules/govuk-frontend/dist/govuk/components/task-list/_index.scss +79 -0
  153. data/node_modules/govuk-frontend/dist/govuk/components/task-list/_task-list.scss +4 -0
  154. data/node_modules/govuk-frontend/{govuk → dist/govuk}/components/textarea/_index.scss +6 -10
  155. data/node_modules/govuk-frontend/dist/govuk/components/textarea/_textarea.scss +4 -0
  156. data/node_modules/govuk-frontend/{govuk → dist/govuk}/components/warning-text/_index.scss +13 -13
  157. data/node_modules/govuk-frontend/dist/govuk/components/warning-text/_warning-text.scss +4 -0
  158. data/node_modules/govuk-frontend/dist/govuk/core/_all.scss +10 -0
  159. data/node_modules/govuk-frontend/{govuk → dist/govuk}/core/_global-styles.scss +2 -1
  160. data/node_modules/govuk-frontend/dist/govuk/core/_govuk-frontend-properties.scss +12 -0
  161. data/node_modules/govuk-frontend/{govuk/core/_all.scss → dist/govuk/core/_index.scss} +3 -1
  162. data/node_modules/govuk-frontend/{govuk → dist/govuk}/core/_links.scss +8 -1
  163. data/node_modules/govuk-frontend/{govuk → dist/govuk}/core/_lists.scss +2 -1
  164. data/node_modules/govuk-frontend/{govuk → dist/govuk}/core/_section-break.scss +2 -7
  165. data/node_modules/govuk-frontend/{govuk → dist/govuk}/core/_typography.scss +8 -5
  166. data/node_modules/govuk-frontend/dist/govuk/errors/index.mjs +57 -0
  167. data/node_modules/govuk-frontend/dist/govuk/govuk-frontend-component.mjs +60 -0
  168. data/node_modules/govuk-frontend/dist/govuk/govuk-frontend.min.js +1 -0
  169. data/node_modules/govuk-frontend/dist/govuk/helpers/_all.scss +10 -0
  170. data/node_modules/govuk-frontend/{govuk → dist/govuk}/helpers/_clearfix.scss +3 -1
  171. data/node_modules/govuk-frontend/dist/govuk/helpers/_colour.scss +127 -0
  172. data/node_modules/govuk-frontend/{govuk → dist/govuk}/helpers/_device-pixels.scss +4 -3
  173. data/node_modules/govuk-frontend/{govuk → dist/govuk}/helpers/_focused.scss +22 -6
  174. data/node_modules/govuk-frontend/dist/govuk/helpers/_font-faces.scss +41 -0
  175. data/node_modules/govuk-frontend/{govuk → dist/govuk}/helpers/_grid.scss +3 -1
  176. data/node_modules/govuk-frontend/{govuk/helpers/_all.scss → dist/govuk/helpers/_index.scss} +2 -0
  177. data/node_modules/govuk-frontend/{govuk → dist/govuk}/helpers/_links.scss +38 -76
  178. data/node_modules/govuk-frontend/{govuk → dist/govuk}/helpers/_media-queries.scss +3 -12
  179. data/node_modules/govuk-frontend/{govuk → dist/govuk}/helpers/_shape-arrow.scss +3 -1
  180. data/node_modules/govuk-frontend/{govuk → dist/govuk}/helpers/_spacing.scss +12 -10
  181. data/node_modules/govuk-frontend/dist/govuk/helpers/_typography.scss +288 -0
  182. data/node_modules/govuk-frontend/{govuk → dist/govuk}/helpers/_visually-hidden.scss +39 -50
  183. data/node_modules/govuk-frontend/dist/govuk/i18n.mjs +195 -0
  184. data/node_modules/govuk-frontend/dist/govuk/index.scss +11 -0
  185. data/node_modules/govuk-frontend/dist/govuk/init.mjs +162 -0
  186. data/node_modules/govuk-frontend/dist/govuk/objects/_all.scss +10 -0
  187. data/node_modules/govuk-frontend/{govuk → dist/govuk}/objects/_button-group.scss +7 -12
  188. data/node_modules/govuk-frontend/{govuk → dist/govuk}/objects/_form-group.scss +2 -0
  189. data/node_modules/govuk-frontend/{govuk → dist/govuk}/objects/_grid.scss +4 -2
  190. data/node_modules/govuk-frontend/{govuk/objects/_all.scss → dist/govuk/objects/_index.scss} +2 -0
  191. data/node_modules/govuk-frontend/{govuk → dist/govuk}/objects/_main-wrapper.scss +2 -0
  192. data/node_modules/govuk-frontend/{govuk → dist/govuk}/objects/_template.scss +2 -1
  193. data/node_modules/govuk-frontend/{govuk → dist/govuk}/objects/_width-container.scss +2 -9
  194. data/node_modules/govuk-frontend/dist/govuk/overrides/_all.scss +9 -0
  195. data/node_modules/govuk-frontend/{govuk → dist/govuk}/overrides/_display.scss +2 -0
  196. data/node_modules/govuk-frontend/{govuk/overrides/_all.scss → dist/govuk/overrides/_index.scss} +2 -0
  197. data/node_modules/govuk-frontend/{govuk → dist/govuk}/overrides/_spacing.scss +3 -19
  198. data/node_modules/govuk-frontend/{govuk → dist/govuk}/overrides/_text-align.scss +2 -0
  199. data/node_modules/govuk-frontend/dist/govuk/overrides/_typography.scss +42 -0
  200. data/node_modules/govuk-frontend/{govuk → dist/govuk}/overrides/_width.scss +2 -0
  201. data/node_modules/govuk-frontend/dist/govuk/settings/_all.scss +10 -0
  202. data/node_modules/govuk-frontend/{govuk → dist/govuk}/settings/_assets.scss +2 -0
  203. data/node_modules/govuk-frontend/{govuk → dist/govuk}/settings/_colours-applied.scss +9 -7
  204. data/node_modules/govuk-frontend/dist/govuk/settings/_colours-organisations.scss +378 -0
  205. data/node_modules/govuk-frontend/dist/govuk/settings/_colours-palette.scss +37 -0
  206. data/node_modules/govuk-frontend/{govuk → dist/govuk}/settings/_global-styles.scss +2 -0
  207. data/node_modules/govuk-frontend/{govuk/settings/_all.scss → dist/govuk/settings/_index.scss} +2 -3
  208. data/node_modules/govuk-frontend/{govuk → dist/govuk}/settings/_links.scss +3 -20
  209. data/node_modules/govuk-frontend/{govuk → dist/govuk}/settings/_measurements.scss +18 -6
  210. data/node_modules/govuk-frontend/{govuk → dist/govuk}/settings/_media-queries.scss +5 -3
  211. data/node_modules/govuk-frontend/{govuk → dist/govuk}/settings/_spacing.scss +2 -0
  212. data/node_modules/govuk-frontend/dist/govuk/settings/_typography-font.scss +52 -0
  213. data/node_modules/govuk-frontend/dist/govuk/settings/_typography-responsive.scss +322 -0
  214. data/node_modules/govuk-frontend/{govuk → dist/govuk}/settings/_warnings.scss +33 -8
  215. data/node_modules/govuk-frontend/dist/govuk/tools/_all.scss +10 -0
  216. data/node_modules/govuk-frontend/{govuk → dist/govuk}/tools/_exports.scss +2 -0
  217. data/node_modules/govuk-frontend/{govuk → dist/govuk}/tools/_font-url.scss +3 -0
  218. data/node_modules/govuk-frontend/{govuk → dist/govuk}/tools/_image-url.scss +3 -0
  219. data/node_modules/govuk-frontend/{govuk/tools/_all.scss → dist/govuk/tools/_index.scss} +2 -2
  220. data/node_modules/govuk-frontend/{govuk → dist/govuk}/tools/_px-to-em.scss +2 -0
  221. data/node_modules/govuk-frontend/{govuk → dist/govuk}/tools/_px-to-rem.scss +2 -0
  222. data/node_modules/govuk-frontend/dist/govuk/utilities/_all.scss +10 -0
  223. data/node_modules/govuk-frontend/{govuk → dist/govuk}/utilities/_clearfix.scss +2 -0
  224. data/node_modules/govuk-frontend/{govuk/utilities/_all.scss → dist/govuk/utilities/_index.scss} +2 -0
  225. data/node_modules/govuk-frontend/{govuk → dist/govuk}/utilities/_visually-hidden.scss +2 -0
  226. data/node_modules/govuk-frontend/{govuk → dist/govuk}/vendor/_sass-mq.scss +3 -1
  227. data/node_modules/govuk-frontend/dist/govuk-prototype-kit/init.js +15 -0
  228. data/node_modules/govuk-frontend/{govuk-prototype-kit → dist/govuk-prototype-kit}/init.scss +3 -2
  229. data/package-lock.json +37 -37
  230. data/package.json +6 -1
  231. metadata +316 -206
  232. data/node_modules/govuk-frontend/govuk/_base.scss +0 -3
  233. data/node_modules/govuk-frontend/govuk/all-ie8.scss +0 -14
  234. data/node_modules/govuk-frontend/govuk/all.js +0 -5172
  235. data/node_modules/govuk-frontend/govuk/all.scss +0 -9
  236. data/node_modules/govuk-frontend/govuk/assets/images/favicon.ico +0 -0
  237. data/node_modules/govuk-frontend/govuk/assets/images/govuk-apple-touch-icon-152x152.png +0 -0
  238. data/node_modules/govuk-frontend/govuk/assets/images/govuk-apple-touch-icon-167x167.png +0 -0
  239. data/node_modules/govuk-frontend/govuk/assets/images/govuk-apple-touch-icon-180x180.png +0 -0
  240. data/node_modules/govuk-frontend/govuk/assets/images/govuk-apple-touch-icon.png +0 -0
  241. data/node_modules/govuk-frontend/govuk/assets/images/govuk-crest-2x.png +0 -0
  242. data/node_modules/govuk-frontend/govuk/assets/images/govuk-crest.png +0 -0
  243. data/node_modules/govuk-frontend/govuk/assets/images/govuk-logotype-crown.png +0 -0
  244. data/node_modules/govuk-frontend/govuk/assets/images/govuk-mask-icon.svg +0 -7
  245. data/node_modules/govuk-frontend/govuk/assets/images/govuk-opengraph-image.png +0 -0
  246. data/node_modules/govuk-frontend/govuk/common/closest-attribute-value.js +0 -75
  247. data/node_modules/govuk-frontend/govuk/common/govuk-frontend-version.js +0 -17
  248. data/node_modules/govuk-frontend/govuk/common/index.js +0 -192
  249. data/node_modules/govuk-frontend/govuk/common/normalise-dataset.js +0 -386
  250. data/node_modules/govuk-frontend/govuk/common.js +0 -194
  251. data/node_modules/govuk-frontend/govuk/components/accordion/_accordion.scss +0 -2
  252. data/node_modules/govuk-frontend/govuk/components/accordion/accordion.js +0 -2386
  253. data/node_modules/govuk-frontend/govuk/components/back-link/_back-link.scss +0 -2
  254. data/node_modules/govuk-frontend/govuk/components/breadcrumbs/_breadcrumbs.scss +0 -2
  255. data/node_modules/govuk-frontend/govuk/components/button/_button.scss +0 -2
  256. data/node_modules/govuk-frontend/govuk/components/button/button.js +0 -1027
  257. data/node_modules/govuk-frontend/govuk/components/character-count/_character-count.scss +0 -2
  258. data/node_modules/govuk-frontend/govuk/components/character-count/character-count.js +0 -2281
  259. data/node_modules/govuk-frontend/govuk/components/checkboxes/_checkboxes.scss +0 -2
  260. data/node_modules/govuk-frontend/govuk/components/checkboxes/checkboxes.js +0 -1277
  261. data/node_modules/govuk-frontend/govuk/components/cookie-banner/_cookie-banner.scss +0 -2
  262. data/node_modules/govuk-frontend/govuk/components/date-input/_date-input.scss +0 -2
  263. data/node_modules/govuk-frontend/govuk/components/details/_details.scss +0 -2
  264. data/node_modules/govuk-frontend/govuk/components/details/_index.scss +0 -88
  265. data/node_modules/govuk-frontend/govuk/components/details/details.js +0 -873
  266. data/node_modules/govuk-frontend/govuk/components/error-message/_error-message.scss +0 -2
  267. data/node_modules/govuk-frontend/govuk/components/error-summary/_error-summary.scss +0 -2
  268. data/node_modules/govuk-frontend/govuk/components/error-summary/error-summary.js +0 -1189
  269. data/node_modules/govuk-frontend/govuk/components/exit-this-page/_exit-this-page.scss +0 -2
  270. data/node_modules/govuk-frontend/govuk/components/exit-this-page/exit-this-page.js +0 -2120
  271. data/node_modules/govuk-frontend/govuk/components/fieldset/_fieldset.scss +0 -2
  272. data/node_modules/govuk-frontend/govuk/components/file-upload/_file-upload.scss +0 -2
  273. data/node_modules/govuk-frontend/govuk/components/footer/_footer.scss +0 -2
  274. data/node_modules/govuk-frontend/govuk/components/header/_header.scss +0 -2
  275. data/node_modules/govuk-frontend/govuk/components/header/header.js +0 -794
  276. data/node_modules/govuk-frontend/govuk/components/inset-text/_inset-text.scss +0 -2
  277. data/node_modules/govuk-frontend/govuk/components/notification-banner/_notification-banner.scss +0 -2
  278. data/node_modules/govuk-frontend/govuk/components/notification-banner/notification-banner.js +0 -843
  279. data/node_modules/govuk-frontend/govuk/components/pagination/_pagination.scss +0 -2
  280. data/node_modules/govuk-frontend/govuk/components/panel/_index.scss +0 -56
  281. data/node_modules/govuk-frontend/govuk/components/phase-banner/_phase-banner.scss +0 -2
  282. data/node_modules/govuk-frontend/govuk/components/radios/_radios.scss +0 -2
  283. data/node_modules/govuk-frontend/govuk/components/radios/radios.js +0 -1222
  284. data/node_modules/govuk-frontend/govuk/components/select/_select.scss +0 -2
  285. data/node_modules/govuk-frontend/govuk/components/skip-link/_skip-link.scss +0 -2
  286. data/node_modules/govuk-frontend/govuk/components/skip-link/skip-link.js +0 -1145
  287. data/node_modules/govuk-frontend/govuk/components/summary-list/_summary-list.scss +0 -2
  288. data/node_modules/govuk-frontend/govuk/components/tabs/tabs.js +0 -1621
  289. data/node_modules/govuk-frontend/govuk/components/tag/_index.scss +0 -81
  290. data/node_modules/govuk-frontend/govuk/components/textarea/_textarea.scss +0 -2
  291. data/node_modules/govuk-frontend/govuk/components/warning-text/_warning-text.scss +0 -2
  292. data/node_modules/govuk-frontend/govuk/core/_govuk-frontend-version.scss +0 -5
  293. data/node_modules/govuk-frontend/govuk/helpers/_colour.scss +0 -98
  294. data/node_modules/govuk-frontend/govuk/helpers/_font-faces.scss +0 -41
  295. data/node_modules/govuk-frontend/govuk/helpers/_typography.scss +0 -214
  296. data/node_modules/govuk-frontend/govuk/i18n.js +0 -397
  297. data/node_modules/govuk-frontend/govuk/overrides/_typography.scss +0 -21
  298. data/node_modules/govuk-frontend/govuk/settings/_colours-organisations.scss +0 -146
  299. data/node_modules/govuk-frontend/govuk/settings/_colours-palette.scss +0 -120
  300. data/node_modules/govuk-frontend/govuk/settings/_compatibility.scss +0 -100
  301. data/node_modules/govuk-frontend/govuk/settings/_ie8.scss +0 -34
  302. data/node_modules/govuk-frontend/govuk/settings/_typography-font-families.scss +0 -32
  303. data/node_modules/govuk-frontend/govuk/settings/_typography-font.scss +0 -112
  304. data/node_modules/govuk-frontend/govuk/settings/_typography-responsive.scss +0 -195
  305. data/node_modules/govuk-frontend/govuk/tools/_compatibility.scss +0 -50
  306. data/node_modules/govuk-frontend/govuk/tools/_ie8.scss +0 -87
  307. data/node_modules/govuk-frontend/govuk/vendor/polyfills/DOMTokenList.js +0 -274
  308. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Date/now.js +0 -23
  309. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Document.js +0 -36
  310. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Element/prototype/classList.js +0 -597
  311. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Element/prototype/closest.js +0 -58
  312. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Element/prototype/dataset.js +0 -307
  313. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Element/prototype/matches.js +0 -33
  314. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Element/prototype/nextElementSibling.js +0 -261
  315. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Element/prototype/previousElementSibling.js +0 -261
  316. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Element.js +0 -151
  317. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Event.js +0 -512
  318. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Function/prototype/bind.js +0 -256
  319. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Object/defineProperty.js +0 -96
  320. data/node_modules/govuk-frontend/govuk/vendor/polyfills/String/prototype/trim.js +0 -23
  321. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Window.js +0 -30
  322. data/node_modules/govuk-frontend/govuk-prototype-kit/init.js +0 -8
  323. /data/node_modules/govuk-frontend/{govuk → dist/govuk}/assets/fonts/bold-affa96571d-v2.woff +0 -0
  324. /data/node_modules/govuk-frontend/{govuk → dist/govuk}/assets/fonts/bold-b542beb274-v2.woff2 +0 -0
  325. /data/node_modules/govuk-frontend/{govuk → dist/govuk}/assets/fonts/light-94a07e06a1-v2.woff2 +0 -0
  326. /data/node_modules/govuk-frontend/{govuk → dist/govuk}/assets/fonts/light-f591b13f7d-v2.woff +0 -0
  327. /data/{lib/source → node_modules/govuk-frontend/dist/govuk/assets/images}/favicon.ico +0 -0
@@ -0,0 +1,2554 @@
1
+ (function (global, factory) {
2
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
3
+ typeof define === 'function' && define.amd ? define(['exports'], factory) :
4
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.GOVUKFrontend = {}));
5
+ })(this, (function (exports) { 'use strict';
6
+
7
+ const version = '5.7.1';
8
+
9
+ function normaliseString(value, property) {
10
+ const trimmedValue = value ? value.trim() : '';
11
+ let output;
12
+ let outputType = property == null ? void 0 : property.type;
13
+ if (!outputType) {
14
+ if (['true', 'false'].includes(trimmedValue)) {
15
+ outputType = 'boolean';
16
+ }
17
+ if (trimmedValue.length > 0 && isFinite(Number(trimmedValue))) {
18
+ outputType = 'number';
19
+ }
20
+ }
21
+ switch (outputType) {
22
+ case 'boolean':
23
+ output = trimmedValue === 'true';
24
+ break;
25
+ case 'number':
26
+ output = Number(trimmedValue);
27
+ break;
28
+ default:
29
+ output = value;
30
+ }
31
+ return output;
32
+ }
33
+
34
+ /**
35
+ * @typedef {import('./index.mjs').SchemaProperty} SchemaProperty
36
+ */
37
+
38
+ function mergeConfigs(...configObjects) {
39
+ const formattedConfigObject = {};
40
+ for (const configObject of configObjects) {
41
+ for (const key of Object.keys(configObject)) {
42
+ const option = formattedConfigObject[key];
43
+ const override = configObject[key];
44
+ if (isObject(option) && isObject(override)) {
45
+ formattedConfigObject[key] = mergeConfigs(option, override);
46
+ } else {
47
+ formattedConfigObject[key] = override;
48
+ }
49
+ }
50
+ }
51
+ return formattedConfigObject;
52
+ }
53
+ function extractConfigByNamespace(Component, dataset, namespace) {
54
+ const property = Component.schema.properties[namespace];
55
+ if ((property == null ? void 0 : property.type) !== 'object') {
56
+ return;
57
+ }
58
+ const newObject = {
59
+ [namespace]: ({})
60
+ };
61
+ for (const [key, value] of Object.entries(dataset)) {
62
+ let current = newObject;
63
+ const keyParts = key.split('.');
64
+ for (const [index, name] of keyParts.entries()) {
65
+ if (typeof current === 'object') {
66
+ if (index < keyParts.length - 1) {
67
+ if (!isObject(current[name])) {
68
+ current[name] = {};
69
+ }
70
+ current = current[name];
71
+ } else if (key !== namespace) {
72
+ current[name] = normaliseString(value);
73
+ }
74
+ }
75
+ }
76
+ }
77
+ return newObject[namespace];
78
+ }
79
+ function getFragmentFromUrl(url) {
80
+ if (!url.includes('#')) {
81
+ return undefined;
82
+ }
83
+ return url.split('#').pop();
84
+ }
85
+ function getBreakpoint(name) {
86
+ const property = `--govuk-frontend-breakpoint-${name}`;
87
+ const value = window.getComputedStyle(document.documentElement).getPropertyValue(property);
88
+ return {
89
+ property,
90
+ value: value || undefined
91
+ };
92
+ }
93
+ function setFocus($element, options = {}) {
94
+ var _options$onBeforeFocu;
95
+ const isFocusable = $element.getAttribute('tabindex');
96
+ if (!isFocusable) {
97
+ $element.setAttribute('tabindex', '-1');
98
+ }
99
+ function onFocus() {
100
+ $element.addEventListener('blur', onBlur, {
101
+ once: true
102
+ });
103
+ }
104
+ function onBlur() {
105
+ var _options$onBlur;
106
+ (_options$onBlur = options.onBlur) == null || _options$onBlur.call($element);
107
+ if (!isFocusable) {
108
+ $element.removeAttribute('tabindex');
109
+ }
110
+ }
111
+ $element.addEventListener('focus', onFocus, {
112
+ once: true
113
+ });
114
+ (_options$onBeforeFocu = options.onBeforeFocus) == null || _options$onBeforeFocu.call($element);
115
+ $element.focus();
116
+ }
117
+ function isInitialised($root, moduleName) {
118
+ return $root instanceof HTMLElement && $root.hasAttribute(`data-${moduleName}-init`);
119
+ }
120
+
121
+ /**
122
+ * Checks if GOV.UK Frontend is supported on this page
123
+ *
124
+ * Some browsers will load and run our JavaScript but GOV.UK Frontend
125
+ * won't be supported.
126
+ *
127
+ * @param {HTMLElement | null} [$scope] - (internal) `<body>` HTML element checked for browser support
128
+ * @returns {boolean} Whether GOV.UK Frontend is supported on this page
129
+ */
130
+ function isSupported($scope = document.body) {
131
+ if (!$scope) {
132
+ return false;
133
+ }
134
+ return $scope.classList.contains('govuk-frontend-supported');
135
+ }
136
+ function validateConfig(schema, config) {
137
+ const validationErrors = [];
138
+ for (const [name, conditions] of Object.entries(schema)) {
139
+ const errors = [];
140
+ if (Array.isArray(conditions)) {
141
+ for (const {
142
+ required,
143
+ errorMessage
144
+ } of conditions) {
145
+ if (!required.every(key => !!config[key])) {
146
+ errors.push(errorMessage);
147
+ }
148
+ }
149
+ if (name === 'anyOf' && !(conditions.length - errors.length >= 1)) {
150
+ validationErrors.push(...errors);
151
+ }
152
+ }
153
+ }
154
+ return validationErrors;
155
+ }
156
+ function isArray(option) {
157
+ return Array.isArray(option);
158
+ }
159
+ function isObject(option) {
160
+ return !!option && typeof option === 'object' && !isArray(option);
161
+ }
162
+ function formatErrorMessage(Component, message) {
163
+ return `${Component.moduleName}: ${message}`;
164
+ }
165
+
166
+ /**
167
+ * Schema for component config
168
+ *
169
+ * @typedef {object} Schema
170
+ * @property {{ [field: string]: SchemaProperty | undefined }} properties - Schema properties
171
+ * @property {SchemaCondition[]} [anyOf] - List of schema conditions
172
+ */
173
+
174
+ /**
175
+ * Schema property for component config
176
+ *
177
+ * @typedef {object} SchemaProperty
178
+ * @property {'string' | 'boolean' | 'number' | 'object'} type - Property type
179
+ */
180
+
181
+ /**
182
+ * Schema condition for component config
183
+ *
184
+ * @typedef {object} SchemaCondition
185
+ * @property {string[]} required - List of required config fields
186
+ * @property {string} errorMessage - Error message when required config fields not provided
187
+ */
188
+ /**
189
+ * @typedef ComponentWithModuleName
190
+ * @property {string} moduleName - Name of the component
191
+ */
192
+
193
+ function normaliseDataset(Component, dataset) {
194
+ const out = {};
195
+ for (const [field, property] of Object.entries(Component.schema.properties)) {
196
+ if (field in dataset) {
197
+ out[field] = normaliseString(dataset[field], property);
198
+ }
199
+ if ((property == null ? void 0 : property.type) === 'object') {
200
+ out[field] = extractConfigByNamespace(Component, dataset, field);
201
+ }
202
+ }
203
+ return out;
204
+ }
205
+
206
+ class GOVUKFrontendError extends Error {
207
+ constructor(...args) {
208
+ super(...args);
209
+ this.name = 'GOVUKFrontendError';
210
+ }
211
+ }
212
+ class SupportError extends GOVUKFrontendError {
213
+ /**
214
+ * Checks if GOV.UK Frontend is supported on this page
215
+ *
216
+ * @param {HTMLElement | null} [$scope] - HTML element `<body>` checked for browser support
217
+ */
218
+ constructor($scope = document.body) {
219
+ const supportMessage = 'noModule' in HTMLScriptElement.prototype ? 'GOV.UK Frontend initialised without `<body class="govuk-frontend-supported">` from template `<script>` snippet' : 'GOV.UK Frontend is not supported in this browser';
220
+ super($scope ? supportMessage : 'GOV.UK Frontend initialised without `<script type="module">`');
221
+ this.name = 'SupportError';
222
+ }
223
+ }
224
+ class ConfigError extends GOVUKFrontendError {
225
+ constructor(...args) {
226
+ super(...args);
227
+ this.name = 'ConfigError';
228
+ }
229
+ }
230
+ class ElementError extends GOVUKFrontendError {
231
+ constructor(messageOrOptions) {
232
+ let message = typeof messageOrOptions === 'string' ? messageOrOptions : '';
233
+ if (typeof messageOrOptions === 'object') {
234
+ const {
235
+ component,
236
+ identifier,
237
+ element,
238
+ expectedType
239
+ } = messageOrOptions;
240
+ message = identifier;
241
+ message += element ? ` is not of type ${expectedType != null ? expectedType : 'HTMLElement'}` : ' not found';
242
+ message = formatErrorMessage(component, message);
243
+ }
244
+ super(message);
245
+ this.name = 'ElementError';
246
+ }
247
+ }
248
+ class InitError extends GOVUKFrontendError {
249
+ constructor(componentOrMessage) {
250
+ const message = typeof componentOrMessage === 'string' ? componentOrMessage : formatErrorMessage(componentOrMessage, `Root element (\`$root\`) already initialised`);
251
+ super(message);
252
+ this.name = 'InitError';
253
+ }
254
+ }
255
+ /**
256
+ * @typedef {import('../common/index.mjs').ComponentWithModuleName} ComponentWithModuleName
257
+ */
258
+
259
+ class GOVUKFrontendComponent {
260
+ /**
261
+ * Returns the root element of the component
262
+ *
263
+ * @protected
264
+ * @returns {RootElementType} - the root element of component
265
+ */
266
+ get $root() {
267
+ return this._$root;
268
+ }
269
+ constructor($root) {
270
+ this._$root = void 0;
271
+ const childConstructor = this.constructor;
272
+ if (typeof childConstructor.moduleName !== 'string') {
273
+ throw new InitError(`\`moduleName\` not defined in component`);
274
+ }
275
+ if (!($root instanceof childConstructor.elementType)) {
276
+ throw new ElementError({
277
+ element: $root,
278
+ component: childConstructor,
279
+ identifier: 'Root element (`$root`)',
280
+ expectedType: childConstructor.elementType.name
281
+ });
282
+ } else {
283
+ this._$root = $root;
284
+ }
285
+ childConstructor.checkSupport();
286
+ this.checkInitialised();
287
+ const moduleName = childConstructor.moduleName;
288
+ this.$root.setAttribute(`data-${moduleName}-init`, '');
289
+ }
290
+ checkInitialised() {
291
+ const constructor = this.constructor;
292
+ const moduleName = constructor.moduleName;
293
+ if (moduleName && isInitialised(this.$root, moduleName)) {
294
+ throw new InitError(constructor);
295
+ }
296
+ }
297
+ static checkSupport() {
298
+ if (!isSupported()) {
299
+ throw new SupportError();
300
+ }
301
+ }
302
+ }
303
+
304
+ /**
305
+ * @typedef ChildClass
306
+ * @property {string} moduleName - The module name that'll be looked for in the DOM when initialising the component
307
+ */
308
+
309
+ /**
310
+ * @typedef {typeof GOVUKFrontendComponent & ChildClass} ChildClassConstructor
311
+ */
312
+ GOVUKFrontendComponent.elementType = HTMLElement;
313
+
314
+ class I18n {
315
+ constructor(translations = {}, config = {}) {
316
+ var _config$locale;
317
+ this.translations = void 0;
318
+ this.locale = void 0;
319
+ this.translations = translations;
320
+ this.locale = (_config$locale = config.locale) != null ? _config$locale : document.documentElement.lang || 'en';
321
+ }
322
+ t(lookupKey, options) {
323
+ if (!lookupKey) {
324
+ throw new Error('i18n: lookup key missing');
325
+ }
326
+ let translation = this.translations[lookupKey];
327
+ if (typeof (options == null ? void 0 : options.count) === 'number' && typeof translation === 'object') {
328
+ const translationPluralForm = translation[this.getPluralSuffix(lookupKey, options.count)];
329
+ if (translationPluralForm) {
330
+ translation = translationPluralForm;
331
+ }
332
+ }
333
+ if (typeof translation === 'string') {
334
+ if (translation.match(/%{(.\S+)}/)) {
335
+ if (!options) {
336
+ throw new Error('i18n: cannot replace placeholders in string if no option data provided');
337
+ }
338
+ return this.replacePlaceholders(translation, options);
339
+ }
340
+ return translation;
341
+ }
342
+ return lookupKey;
343
+ }
344
+ replacePlaceholders(translationString, options) {
345
+ const formatter = Intl.NumberFormat.supportedLocalesOf(this.locale).length ? new Intl.NumberFormat(this.locale) : undefined;
346
+ return translationString.replace(/%{(.\S+)}/g, function (placeholderWithBraces, placeholderKey) {
347
+ if (Object.prototype.hasOwnProperty.call(options, placeholderKey)) {
348
+ const placeholderValue = options[placeholderKey];
349
+ if (placeholderValue === false || typeof placeholderValue !== 'number' && typeof placeholderValue !== 'string') {
350
+ return '';
351
+ }
352
+ if (typeof placeholderValue === 'number') {
353
+ return formatter ? formatter.format(placeholderValue) : `${placeholderValue}`;
354
+ }
355
+ return placeholderValue;
356
+ }
357
+ throw new Error(`i18n: no data found to replace ${placeholderWithBraces} placeholder in string`);
358
+ });
359
+ }
360
+ hasIntlPluralRulesSupport() {
361
+ return Boolean('PluralRules' in window.Intl && Intl.PluralRules.supportedLocalesOf(this.locale).length);
362
+ }
363
+ getPluralSuffix(lookupKey, count) {
364
+ count = Number(count);
365
+ if (!isFinite(count)) {
366
+ return 'other';
367
+ }
368
+ const translation = this.translations[lookupKey];
369
+ const preferredForm = this.hasIntlPluralRulesSupport() ? new Intl.PluralRules(this.locale).select(count) : this.selectPluralFormUsingFallbackRules(count);
370
+ if (typeof translation === 'object') {
371
+ if (preferredForm in translation) {
372
+ return preferredForm;
373
+ } else if ('other' in translation) {
374
+ console.warn(`i18n: Missing plural form ".${preferredForm}" for "${this.locale}" locale. Falling back to ".other".`);
375
+ return 'other';
376
+ }
377
+ }
378
+ throw new Error(`i18n: Plural form ".other" is required for "${this.locale}" locale`);
379
+ }
380
+ selectPluralFormUsingFallbackRules(count) {
381
+ count = Math.abs(Math.floor(count));
382
+ const ruleset = this.getPluralRulesForLocale();
383
+ if (ruleset) {
384
+ return I18n.pluralRules[ruleset](count);
385
+ }
386
+ return 'other';
387
+ }
388
+ getPluralRulesForLocale() {
389
+ const localeShort = this.locale.split('-')[0];
390
+ for (const pluralRule in I18n.pluralRulesMap) {
391
+ const languages = I18n.pluralRulesMap[pluralRule];
392
+ if (languages.includes(this.locale) || languages.includes(localeShort)) {
393
+ return pluralRule;
394
+ }
395
+ }
396
+ }
397
+ }
398
+ I18n.pluralRulesMap = {
399
+ arabic: ['ar'],
400
+ chinese: ['my', 'zh', 'id', 'ja', 'jv', 'ko', 'ms', 'th', 'vi'],
401
+ french: ['hy', 'bn', 'fr', 'gu', 'hi', 'fa', 'pa', 'zu'],
402
+ german: ['af', 'sq', 'az', 'eu', 'bg', 'ca', 'da', 'nl', 'en', 'et', 'fi', 'ka', 'de', 'el', 'hu', 'lb', 'no', 'so', 'sw', 'sv', 'ta', 'te', 'tr', 'ur'],
403
+ irish: ['ga'],
404
+ russian: ['ru', 'uk'],
405
+ scottish: ['gd'],
406
+ spanish: ['pt-PT', 'it', 'es'],
407
+ welsh: ['cy']
408
+ };
409
+ I18n.pluralRules = {
410
+ arabic(n) {
411
+ if (n === 0) {
412
+ return 'zero';
413
+ }
414
+ if (n === 1) {
415
+ return 'one';
416
+ }
417
+ if (n === 2) {
418
+ return 'two';
419
+ }
420
+ if (n % 100 >= 3 && n % 100 <= 10) {
421
+ return 'few';
422
+ }
423
+ if (n % 100 >= 11 && n % 100 <= 99) {
424
+ return 'many';
425
+ }
426
+ return 'other';
427
+ },
428
+ chinese() {
429
+ return 'other';
430
+ },
431
+ french(n) {
432
+ return n === 0 || n === 1 ? 'one' : 'other';
433
+ },
434
+ german(n) {
435
+ return n === 1 ? 'one' : 'other';
436
+ },
437
+ irish(n) {
438
+ if (n === 1) {
439
+ return 'one';
440
+ }
441
+ if (n === 2) {
442
+ return 'two';
443
+ }
444
+ if (n >= 3 && n <= 6) {
445
+ return 'few';
446
+ }
447
+ if (n >= 7 && n <= 10) {
448
+ return 'many';
449
+ }
450
+ return 'other';
451
+ },
452
+ russian(n) {
453
+ const lastTwo = n % 100;
454
+ const last = lastTwo % 10;
455
+ if (last === 1 && lastTwo !== 11) {
456
+ return 'one';
457
+ }
458
+ if (last >= 2 && last <= 4 && !(lastTwo >= 12 && lastTwo <= 14)) {
459
+ return 'few';
460
+ }
461
+ if (last === 0 || last >= 5 && last <= 9 || lastTwo >= 11 && lastTwo <= 14) {
462
+ return 'many';
463
+ }
464
+ return 'other';
465
+ },
466
+ scottish(n) {
467
+ if (n === 1 || n === 11) {
468
+ return 'one';
469
+ }
470
+ if (n === 2 || n === 12) {
471
+ return 'two';
472
+ }
473
+ if (n >= 3 && n <= 10 || n >= 13 && n <= 19) {
474
+ return 'few';
475
+ }
476
+ return 'other';
477
+ },
478
+ spanish(n) {
479
+ if (n === 1) {
480
+ return 'one';
481
+ }
482
+ if (n % 1000000 === 0 && n !== 0) {
483
+ return 'many';
484
+ }
485
+ return 'other';
486
+ },
487
+ welsh(n) {
488
+ if (n === 0) {
489
+ return 'zero';
490
+ }
491
+ if (n === 1) {
492
+ return 'one';
493
+ }
494
+ if (n === 2) {
495
+ return 'two';
496
+ }
497
+ if (n === 3) {
498
+ return 'few';
499
+ }
500
+ if (n === 6) {
501
+ return 'many';
502
+ }
503
+ return 'other';
504
+ }
505
+ };
506
+
507
+ /**
508
+ * Accordion component
509
+ *
510
+ * This allows a collection of sections to be collapsed by default, showing only
511
+ * their headers. Sections can be expanded or collapsed individually by clicking
512
+ * their headers. A "Show all sections" button is also added to the top of the
513
+ * accordion, which switches to "Hide all sections" when all the sections are
514
+ * expanded.
515
+ *
516
+ * The state of each section is saved to the DOM via the `aria-expanded`
517
+ * attribute, which also provides accessibility.
518
+ *
519
+ * @preserve
520
+ */
521
+ class Accordion extends GOVUKFrontendComponent {
522
+ /**
523
+ * @param {Element | null} $root - HTML element to use for accordion
524
+ * @param {AccordionConfig} [config] - Accordion config
525
+ */
526
+ constructor($root, config = {}) {
527
+ super($root);
528
+ this.config = void 0;
529
+ this.i18n = void 0;
530
+ this.controlsClass = 'govuk-accordion__controls';
531
+ this.showAllClass = 'govuk-accordion__show-all';
532
+ this.showAllTextClass = 'govuk-accordion__show-all-text';
533
+ this.sectionClass = 'govuk-accordion__section';
534
+ this.sectionExpandedClass = 'govuk-accordion__section--expanded';
535
+ this.sectionButtonClass = 'govuk-accordion__section-button';
536
+ this.sectionHeaderClass = 'govuk-accordion__section-header';
537
+ this.sectionHeadingClass = 'govuk-accordion__section-heading';
538
+ this.sectionHeadingDividerClass = 'govuk-accordion__section-heading-divider';
539
+ this.sectionHeadingTextClass = 'govuk-accordion__section-heading-text';
540
+ this.sectionHeadingTextFocusClass = 'govuk-accordion__section-heading-text-focus';
541
+ this.sectionShowHideToggleClass = 'govuk-accordion__section-toggle';
542
+ this.sectionShowHideToggleFocusClass = 'govuk-accordion__section-toggle-focus';
543
+ this.sectionShowHideTextClass = 'govuk-accordion__section-toggle-text';
544
+ this.upChevronIconClass = 'govuk-accordion-nav__chevron';
545
+ this.downChevronIconClass = 'govuk-accordion-nav__chevron--down';
546
+ this.sectionSummaryClass = 'govuk-accordion__section-summary';
547
+ this.sectionSummaryFocusClass = 'govuk-accordion__section-summary-focus';
548
+ this.sectionContentClass = 'govuk-accordion__section-content';
549
+ this.$sections = void 0;
550
+ this.$showAllButton = null;
551
+ this.$showAllIcon = null;
552
+ this.$showAllText = null;
553
+ this.config = mergeConfigs(Accordion.defaults, config, normaliseDataset(Accordion, this.$root.dataset));
554
+ this.i18n = new I18n(this.config.i18n);
555
+ const $sections = this.$root.querySelectorAll(`.${this.sectionClass}`);
556
+ if (!$sections.length) {
557
+ throw new ElementError({
558
+ component: Accordion,
559
+ identifier: `Sections (\`<div class="${this.sectionClass}">\`)`
560
+ });
561
+ }
562
+ this.$sections = $sections;
563
+ this.initControls();
564
+ this.initSectionHeaders();
565
+ this.updateShowAllButton(this.areAllSectionsOpen());
566
+ }
567
+ initControls() {
568
+ this.$showAllButton = document.createElement('button');
569
+ this.$showAllButton.setAttribute('type', 'button');
570
+ this.$showAllButton.setAttribute('class', this.showAllClass);
571
+ this.$showAllButton.setAttribute('aria-expanded', 'false');
572
+ this.$showAllIcon = document.createElement('span');
573
+ this.$showAllIcon.classList.add(this.upChevronIconClass);
574
+ this.$showAllButton.appendChild(this.$showAllIcon);
575
+ const $accordionControls = document.createElement('div');
576
+ $accordionControls.setAttribute('class', this.controlsClass);
577
+ $accordionControls.appendChild(this.$showAllButton);
578
+ this.$root.insertBefore($accordionControls, this.$root.firstChild);
579
+ this.$showAllText = document.createElement('span');
580
+ this.$showAllText.classList.add(this.showAllTextClass);
581
+ this.$showAllButton.appendChild(this.$showAllText);
582
+ this.$showAllButton.addEventListener('click', () => this.onShowOrHideAllToggle());
583
+ if ('onbeforematch' in document) {
584
+ document.addEventListener('beforematch', event => this.onBeforeMatch(event));
585
+ }
586
+ }
587
+ initSectionHeaders() {
588
+ this.$sections.forEach(($section, i) => {
589
+ const $header = $section.querySelector(`.${this.sectionHeaderClass}`);
590
+ if (!$header) {
591
+ throw new ElementError({
592
+ component: Accordion,
593
+ identifier: `Section headers (\`<div class="${this.sectionHeaderClass}">\`)`
594
+ });
595
+ }
596
+ this.constructHeaderMarkup($header, i);
597
+ this.setExpanded(this.isExpanded($section), $section);
598
+ $header.addEventListener('click', () => this.onSectionToggle($section));
599
+ this.setInitialState($section);
600
+ });
601
+ }
602
+ constructHeaderMarkup($header, index) {
603
+ const $span = $header.querySelector(`.${this.sectionButtonClass}`);
604
+ const $heading = $header.querySelector(`.${this.sectionHeadingClass}`);
605
+ const $summary = $header.querySelector(`.${this.sectionSummaryClass}`);
606
+ if (!$heading) {
607
+ throw new ElementError({
608
+ component: Accordion,
609
+ identifier: `Section heading (\`.${this.sectionHeadingClass}\`)`
610
+ });
611
+ }
612
+ if (!$span) {
613
+ throw new ElementError({
614
+ component: Accordion,
615
+ identifier: `Section button placeholder (\`<span class="${this.sectionButtonClass}">\`)`
616
+ });
617
+ }
618
+ const $button = document.createElement('button');
619
+ $button.setAttribute('type', 'button');
620
+ $button.setAttribute('aria-controls', `${this.$root.id}-content-${index + 1}`);
621
+ for (const attr of Array.from($span.attributes)) {
622
+ if (attr.name !== 'id') {
623
+ $button.setAttribute(attr.name, attr.value);
624
+ }
625
+ }
626
+ const $headingText = document.createElement('span');
627
+ $headingText.classList.add(this.sectionHeadingTextClass);
628
+ $headingText.id = $span.id;
629
+ const $headingTextFocus = document.createElement('span');
630
+ $headingTextFocus.classList.add(this.sectionHeadingTextFocusClass);
631
+ $headingText.appendChild($headingTextFocus);
632
+ Array.from($span.childNodes).forEach($child => $headingTextFocus.appendChild($child));
633
+ const $showHideToggle = document.createElement('span');
634
+ $showHideToggle.classList.add(this.sectionShowHideToggleClass);
635
+ $showHideToggle.setAttribute('data-nosnippet', '');
636
+ const $showHideToggleFocus = document.createElement('span');
637
+ $showHideToggleFocus.classList.add(this.sectionShowHideToggleFocusClass);
638
+ $showHideToggle.appendChild($showHideToggleFocus);
639
+ const $showHideText = document.createElement('span');
640
+ const $showHideIcon = document.createElement('span');
641
+ $showHideIcon.classList.add(this.upChevronIconClass);
642
+ $showHideToggleFocus.appendChild($showHideIcon);
643
+ $showHideText.classList.add(this.sectionShowHideTextClass);
644
+ $showHideToggleFocus.appendChild($showHideText);
645
+ $button.appendChild($headingText);
646
+ $button.appendChild(this.getButtonPunctuationEl());
647
+ if ($summary) {
648
+ const $summarySpan = document.createElement('span');
649
+ const $summarySpanFocus = document.createElement('span');
650
+ $summarySpanFocus.classList.add(this.sectionSummaryFocusClass);
651
+ $summarySpan.appendChild($summarySpanFocus);
652
+ for (const attr of Array.from($summary.attributes)) {
653
+ $summarySpan.setAttribute(attr.name, attr.value);
654
+ }
655
+ Array.from($summary.childNodes).forEach($child => $summarySpanFocus.appendChild($child));
656
+ $summary.remove();
657
+ $button.appendChild($summarySpan);
658
+ $button.appendChild(this.getButtonPunctuationEl());
659
+ }
660
+ $button.appendChild($showHideToggle);
661
+ $heading.removeChild($span);
662
+ $heading.appendChild($button);
663
+ }
664
+ onBeforeMatch(event) {
665
+ const $fragment = event.target;
666
+ if (!($fragment instanceof Element)) {
667
+ return;
668
+ }
669
+ const $section = $fragment.closest(`.${this.sectionClass}`);
670
+ if ($section) {
671
+ this.setExpanded(true, $section);
672
+ }
673
+ }
674
+ onSectionToggle($section) {
675
+ const nowExpanded = !this.isExpanded($section);
676
+ this.setExpanded(nowExpanded, $section);
677
+ this.storeState($section, nowExpanded);
678
+ }
679
+ onShowOrHideAllToggle() {
680
+ const nowExpanded = !this.areAllSectionsOpen();
681
+ this.$sections.forEach($section => {
682
+ this.setExpanded(nowExpanded, $section);
683
+ this.storeState($section, nowExpanded);
684
+ });
685
+ this.updateShowAllButton(nowExpanded);
686
+ }
687
+ setExpanded(expanded, $section) {
688
+ const $showHideIcon = $section.querySelector(`.${this.upChevronIconClass}`);
689
+ const $showHideText = $section.querySelector(`.${this.sectionShowHideTextClass}`);
690
+ const $button = $section.querySelector(`.${this.sectionButtonClass}`);
691
+ const $content = $section.querySelector(`.${this.sectionContentClass}`);
692
+ if (!$content) {
693
+ throw new ElementError({
694
+ component: Accordion,
695
+ identifier: `Section content (\`<div class="${this.sectionContentClass}">\`)`
696
+ });
697
+ }
698
+ if (!$showHideIcon || !$showHideText || !$button) {
699
+ return;
700
+ }
701
+ const newButtonText = expanded ? this.i18n.t('hideSection') : this.i18n.t('showSection');
702
+ $showHideText.textContent = newButtonText;
703
+ $button.setAttribute('aria-expanded', `${expanded}`);
704
+ const ariaLabelParts = [];
705
+ const $headingText = $section.querySelector(`.${this.sectionHeadingTextClass}`);
706
+ if ($headingText) {
707
+ ariaLabelParts.push(`${$headingText.textContent}`.trim());
708
+ }
709
+ const $summary = $section.querySelector(`.${this.sectionSummaryClass}`);
710
+ if ($summary) {
711
+ ariaLabelParts.push(`${$summary.textContent}`.trim());
712
+ }
713
+ const ariaLabelMessage = expanded ? this.i18n.t('hideSectionAriaLabel') : this.i18n.t('showSectionAriaLabel');
714
+ ariaLabelParts.push(ariaLabelMessage);
715
+ $button.setAttribute('aria-label', ariaLabelParts.join(' , '));
716
+ if (expanded) {
717
+ $content.removeAttribute('hidden');
718
+ $section.classList.add(this.sectionExpandedClass);
719
+ $showHideIcon.classList.remove(this.downChevronIconClass);
720
+ } else {
721
+ $content.setAttribute('hidden', 'until-found');
722
+ $section.classList.remove(this.sectionExpandedClass);
723
+ $showHideIcon.classList.add(this.downChevronIconClass);
724
+ }
725
+ this.updateShowAllButton(this.areAllSectionsOpen());
726
+ }
727
+ isExpanded($section) {
728
+ return $section.classList.contains(this.sectionExpandedClass);
729
+ }
730
+ areAllSectionsOpen() {
731
+ return Array.from(this.$sections).every($section => this.isExpanded($section));
732
+ }
733
+ updateShowAllButton(expanded) {
734
+ if (!this.$showAllButton || !this.$showAllText || !this.$showAllIcon) {
735
+ return;
736
+ }
737
+ this.$showAllButton.setAttribute('aria-expanded', expanded.toString());
738
+ this.$showAllText.textContent = expanded ? this.i18n.t('hideAllSections') : this.i18n.t('showAllSections');
739
+ this.$showAllIcon.classList.toggle(this.downChevronIconClass, !expanded);
740
+ }
741
+
742
+ /**
743
+ * Get the identifier for a section
744
+ *
745
+ * We need a unique way of identifying each content in the Accordion.
746
+ * Since an `#id` should be unique and an `id` is required for `aria-`
747
+ * attributes `id` can be safely used.
748
+ *
749
+ * @param {Element} $section - Section element
750
+ * @returns {string | undefined | null} Identifier for section
751
+ */
752
+ getIdentifier($section) {
753
+ const $button = $section.querySelector(`.${this.sectionButtonClass}`);
754
+ return $button == null ? void 0 : $button.getAttribute('aria-controls');
755
+ }
756
+ storeState($section, isExpanded) {
757
+ if (!this.config.rememberExpanded) {
758
+ return;
759
+ }
760
+ const id = this.getIdentifier($section);
761
+ if (id) {
762
+ try {
763
+ window.sessionStorage.setItem(id, isExpanded.toString());
764
+ } catch (exception) {}
765
+ }
766
+ }
767
+ setInitialState($section) {
768
+ if (!this.config.rememberExpanded) {
769
+ return;
770
+ }
771
+ const id = this.getIdentifier($section);
772
+ if (id) {
773
+ try {
774
+ const state = window.sessionStorage.getItem(id);
775
+ if (state !== null) {
776
+ this.setExpanded(state === 'true', $section);
777
+ }
778
+ } catch (exception) {}
779
+ }
780
+ }
781
+ getButtonPunctuationEl() {
782
+ const $punctuationEl = document.createElement('span');
783
+ $punctuationEl.classList.add('govuk-visually-hidden', this.sectionHeadingDividerClass);
784
+ $punctuationEl.textContent = ', ';
785
+ return $punctuationEl;
786
+ }
787
+ }
788
+
789
+ /**
790
+ * Accordion config
791
+ *
792
+ * @see {@link Accordion.defaults}
793
+ * @typedef {object} AccordionConfig
794
+ * @property {AccordionTranslations} [i18n=Accordion.defaults.i18n] - Accordion translations
795
+ * @property {boolean} [rememberExpanded] - Whether the expanded and collapsed
796
+ * state of each section is remembered and restored when navigating.
797
+ */
798
+
799
+ /**
800
+ * Accordion translations
801
+ *
802
+ * @see {@link Accordion.defaults.i18n}
803
+ * @typedef {object} AccordionTranslations
804
+ *
805
+ * Messages used by the component for the labels of its buttons. This includes
806
+ * the visible text shown on screen, and text to help assistive technology users
807
+ * for the buttons toggling each section.
808
+ * @property {string} [hideAllSections] - The text content for the 'Hide all
809
+ * sections' button, used when at least one section is expanded.
810
+ * @property {string} [hideSection] - The text content for the 'Hide'
811
+ * button, used when a section is expanded.
812
+ * @property {string} [hideSectionAriaLabel] - The text content appended to the
813
+ * 'Hide' button's accessible name when a section is expanded.
814
+ * @property {string} [showAllSections] - The text content for the 'Show all
815
+ * sections' button, used when all sections are collapsed.
816
+ * @property {string} [showSection] - The text content for the 'Show'
817
+ * button, used when a section is collapsed.
818
+ * @property {string} [showSectionAriaLabel] - The text content appended to the
819
+ * 'Show' button's accessible name when a section is expanded.
820
+ */
821
+
822
+ /**
823
+ * @typedef {import('../../common/index.mjs').Schema} Schema
824
+ */
825
+ Accordion.moduleName = 'govuk-accordion';
826
+ Accordion.defaults = Object.freeze({
827
+ i18n: {
828
+ hideAllSections: 'Hide all sections',
829
+ hideSection: 'Hide',
830
+ hideSectionAriaLabel: 'Hide this section',
831
+ showAllSections: 'Show all sections',
832
+ showSection: 'Show',
833
+ showSectionAriaLabel: 'Show this section'
834
+ },
835
+ rememberExpanded: true
836
+ });
837
+ Accordion.schema = Object.freeze({
838
+ properties: {
839
+ i18n: {
840
+ type: 'object'
841
+ },
842
+ rememberExpanded: {
843
+ type: 'boolean'
844
+ }
845
+ }
846
+ });
847
+
848
+ const DEBOUNCE_TIMEOUT_IN_SECONDS = 1;
849
+
850
+ /**
851
+ * JavaScript enhancements for the Button component
852
+ *
853
+ * @preserve
854
+ */
855
+ class Button extends GOVUKFrontendComponent {
856
+ /**
857
+ * @param {Element | null} $root - HTML element to use for button
858
+ * @param {ButtonConfig} [config] - Button config
859
+ */
860
+ constructor($root, config = {}) {
861
+ super($root);
862
+ this.config = void 0;
863
+ this.debounceFormSubmitTimer = null;
864
+ this.config = mergeConfigs(Button.defaults, config, normaliseDataset(Button, this.$root.dataset));
865
+ this.$root.addEventListener('keydown', event => this.handleKeyDown(event));
866
+ this.$root.addEventListener('click', event => this.debounce(event));
867
+ }
868
+ handleKeyDown(event) {
869
+ const $target = event.target;
870
+ if (event.key !== ' ') {
871
+ return;
872
+ }
873
+ if ($target instanceof HTMLElement && $target.getAttribute('role') === 'button') {
874
+ event.preventDefault();
875
+ $target.click();
876
+ }
877
+ }
878
+ debounce(event) {
879
+ if (!this.config.preventDoubleClick) {
880
+ return;
881
+ }
882
+ if (this.debounceFormSubmitTimer) {
883
+ event.preventDefault();
884
+ return false;
885
+ }
886
+ this.debounceFormSubmitTimer = window.setTimeout(() => {
887
+ this.debounceFormSubmitTimer = null;
888
+ }, DEBOUNCE_TIMEOUT_IN_SECONDS * 1000);
889
+ }
890
+ }
891
+
892
+ /**
893
+ * Button config
894
+ *
895
+ * @typedef {object} ButtonConfig
896
+ * @property {boolean} [preventDoubleClick=false] - Prevent accidental double
897
+ * clicks on submit buttons from submitting forms multiple times.
898
+ */
899
+
900
+ /**
901
+ * @typedef {import('../../common/index.mjs').Schema} Schema
902
+ */
903
+ Button.moduleName = 'govuk-button';
904
+ Button.defaults = Object.freeze({
905
+ preventDoubleClick: false
906
+ });
907
+ Button.schema = Object.freeze({
908
+ properties: {
909
+ preventDoubleClick: {
910
+ type: 'boolean'
911
+ }
912
+ }
913
+ });
914
+
915
+ function closestAttributeValue($element, attributeName) {
916
+ const $closestElementWithAttribute = $element.closest(`[${attributeName}]`);
917
+ return $closestElementWithAttribute ? $closestElementWithAttribute.getAttribute(attributeName) : null;
918
+ }
919
+
920
+ /**
921
+ * Character count component
922
+ *
923
+ * Tracks the number of characters or words in the `.govuk-js-character-count`
924
+ * `<textarea>` inside the element. Displays a message with the remaining number
925
+ * of characters/words available, or the number of characters/words in excess.
926
+ *
927
+ * You can configure the message to only appear after a certain percentage
928
+ * of the available characters/words has been entered.
929
+ *
930
+ * @preserve
931
+ */
932
+ class CharacterCount extends GOVUKFrontendComponent {
933
+ /**
934
+ * @param {Element | null} $root - HTML element to use for character count
935
+ * @param {CharacterCountConfig} [config] - Character count config
936
+ */
937
+ constructor($root, config = {}) {
938
+ var _ref, _this$config$maxwords;
939
+ super($root);
940
+ this.$textarea = void 0;
941
+ this.$visibleCountMessage = void 0;
942
+ this.$screenReaderCountMessage = void 0;
943
+ this.lastInputTimestamp = null;
944
+ this.lastInputValue = '';
945
+ this.valueChecker = null;
946
+ this.config = void 0;
947
+ this.i18n = void 0;
948
+ this.maxLength = void 0;
949
+ const $textarea = this.$root.querySelector('.govuk-js-character-count');
950
+ if (!($textarea instanceof HTMLTextAreaElement || $textarea instanceof HTMLInputElement)) {
951
+ throw new ElementError({
952
+ component: CharacterCount,
953
+ element: $textarea,
954
+ expectedType: 'HTMLTextareaElement or HTMLInputElement',
955
+ identifier: 'Form field (`.govuk-js-character-count`)'
956
+ });
957
+ }
958
+ const datasetConfig = normaliseDataset(CharacterCount, this.$root.dataset);
959
+ let configOverrides = {};
960
+ if ('maxwords' in datasetConfig || 'maxlength' in datasetConfig) {
961
+ configOverrides = {
962
+ maxlength: undefined,
963
+ maxwords: undefined
964
+ };
965
+ }
966
+ this.config = mergeConfigs(CharacterCount.defaults, config, configOverrides, datasetConfig);
967
+ const errors = validateConfig(CharacterCount.schema, this.config);
968
+ if (errors[0]) {
969
+ throw new ConfigError(formatErrorMessage(CharacterCount, errors[0]));
970
+ }
971
+ this.i18n = new I18n(this.config.i18n, {
972
+ locale: closestAttributeValue(this.$root, 'lang')
973
+ });
974
+ this.maxLength = (_ref = (_this$config$maxwords = this.config.maxwords) != null ? _this$config$maxwords : this.config.maxlength) != null ? _ref : Infinity;
975
+ this.$textarea = $textarea;
976
+ const textareaDescriptionId = `${this.$textarea.id}-info`;
977
+ const $textareaDescription = document.getElementById(textareaDescriptionId);
978
+ if (!$textareaDescription) {
979
+ throw new ElementError({
980
+ component: CharacterCount,
981
+ element: $textareaDescription,
982
+ identifier: `Count message (\`id="${textareaDescriptionId}"\`)`
983
+ });
984
+ }
985
+ if (`${$textareaDescription.textContent}`.match(/^\s*$/)) {
986
+ $textareaDescription.textContent = this.i18n.t('textareaDescription', {
987
+ count: this.maxLength
988
+ });
989
+ }
990
+ this.$textarea.insertAdjacentElement('afterend', $textareaDescription);
991
+ const $screenReaderCountMessage = document.createElement('div');
992
+ $screenReaderCountMessage.className = 'govuk-character-count__sr-status govuk-visually-hidden';
993
+ $screenReaderCountMessage.setAttribute('aria-live', 'polite');
994
+ this.$screenReaderCountMessage = $screenReaderCountMessage;
995
+ $textareaDescription.insertAdjacentElement('afterend', $screenReaderCountMessage);
996
+ const $visibleCountMessage = document.createElement('div');
997
+ $visibleCountMessage.className = $textareaDescription.className;
998
+ $visibleCountMessage.classList.add('govuk-character-count__status');
999
+ $visibleCountMessage.setAttribute('aria-hidden', 'true');
1000
+ this.$visibleCountMessage = $visibleCountMessage;
1001
+ $textareaDescription.insertAdjacentElement('afterend', $visibleCountMessage);
1002
+ $textareaDescription.classList.add('govuk-visually-hidden');
1003
+ this.$textarea.removeAttribute('maxlength');
1004
+ this.bindChangeEvents();
1005
+ window.addEventListener('pageshow', () => this.updateCountMessage());
1006
+ this.updateCountMessage();
1007
+ }
1008
+ bindChangeEvents() {
1009
+ this.$textarea.addEventListener('keyup', () => this.handleKeyUp());
1010
+ this.$textarea.addEventListener('focus', () => this.handleFocus());
1011
+ this.$textarea.addEventListener('blur', () => this.handleBlur());
1012
+ }
1013
+ handleKeyUp() {
1014
+ this.updateVisibleCountMessage();
1015
+ this.lastInputTimestamp = Date.now();
1016
+ }
1017
+ handleFocus() {
1018
+ this.valueChecker = window.setInterval(() => {
1019
+ if (!this.lastInputTimestamp || Date.now() - 500 >= this.lastInputTimestamp) {
1020
+ this.updateIfValueChanged();
1021
+ }
1022
+ }, 1000);
1023
+ }
1024
+ handleBlur() {
1025
+ if (this.valueChecker) {
1026
+ window.clearInterval(this.valueChecker);
1027
+ }
1028
+ }
1029
+ updateIfValueChanged() {
1030
+ if (this.$textarea.value !== this.lastInputValue) {
1031
+ this.lastInputValue = this.$textarea.value;
1032
+ this.updateCountMessage();
1033
+ }
1034
+ }
1035
+ updateCountMessage() {
1036
+ this.updateVisibleCountMessage();
1037
+ this.updateScreenReaderCountMessage();
1038
+ }
1039
+ updateVisibleCountMessage() {
1040
+ const remainingNumber = this.maxLength - this.count(this.$textarea.value);
1041
+ const isError = remainingNumber < 0;
1042
+ this.$visibleCountMessage.classList.toggle('govuk-character-count__message--disabled', !this.isOverThreshold());
1043
+ this.$textarea.classList.toggle('govuk-textarea--error', isError);
1044
+ this.$visibleCountMessage.classList.toggle('govuk-error-message', isError);
1045
+ this.$visibleCountMessage.classList.toggle('govuk-hint', !isError);
1046
+ this.$visibleCountMessage.textContent = this.getCountMessage();
1047
+ }
1048
+ updateScreenReaderCountMessage() {
1049
+ if (this.isOverThreshold()) {
1050
+ this.$screenReaderCountMessage.removeAttribute('aria-hidden');
1051
+ } else {
1052
+ this.$screenReaderCountMessage.setAttribute('aria-hidden', 'true');
1053
+ }
1054
+ this.$screenReaderCountMessage.textContent = this.getCountMessage();
1055
+ }
1056
+ count(text) {
1057
+ if (this.config.maxwords) {
1058
+ var _text$match;
1059
+ const tokens = (_text$match = text.match(/\S+/g)) != null ? _text$match : [];
1060
+ return tokens.length;
1061
+ }
1062
+ return text.length;
1063
+ }
1064
+ getCountMessage() {
1065
+ const remainingNumber = this.maxLength - this.count(this.$textarea.value);
1066
+ const countType = this.config.maxwords ? 'words' : 'characters';
1067
+ return this.formatCountMessage(remainingNumber, countType);
1068
+ }
1069
+ formatCountMessage(remainingNumber, countType) {
1070
+ if (remainingNumber === 0) {
1071
+ return this.i18n.t(`${countType}AtLimit`);
1072
+ }
1073
+ const translationKeySuffix = remainingNumber < 0 ? 'OverLimit' : 'UnderLimit';
1074
+ return this.i18n.t(`${countType}${translationKeySuffix}`, {
1075
+ count: Math.abs(remainingNumber)
1076
+ });
1077
+ }
1078
+ isOverThreshold() {
1079
+ if (!this.config.threshold) {
1080
+ return true;
1081
+ }
1082
+ const currentLength = this.count(this.$textarea.value);
1083
+ const maxLength = this.maxLength;
1084
+ const thresholdValue = maxLength * this.config.threshold / 100;
1085
+ return thresholdValue <= currentLength;
1086
+ }
1087
+ }
1088
+
1089
+ /**
1090
+ * Character count config
1091
+ *
1092
+ * @see {@link CharacterCount.defaults}
1093
+ * @typedef {object} CharacterCountConfig
1094
+ * @property {number} [maxlength] - The maximum number of characters.
1095
+ * If maxwords is provided, the maxlength option will be ignored.
1096
+ * @property {number} [maxwords] - The maximum number of words. If maxwords is
1097
+ * provided, the maxlength option will be ignored.
1098
+ * @property {number} [threshold=0] - The percentage value of the limit at
1099
+ * which point the count message is displayed. If this attribute is set, the
1100
+ * count message will be hidden by default.
1101
+ * @property {CharacterCountTranslations} [i18n=CharacterCount.defaults.i18n] - Character count translations
1102
+ */
1103
+
1104
+ /**
1105
+ * Character count translations
1106
+ *
1107
+ * @see {@link CharacterCount.defaults.i18n}
1108
+ * @typedef {object} CharacterCountTranslations
1109
+ *
1110
+ * Messages shown to users as they type. It provides feedback on how many words
1111
+ * or characters they have remaining or if they are over the limit. This also
1112
+ * includes a message used as an accessible description for the textarea.
1113
+ * @property {TranslationPluralForms} [charactersUnderLimit] - Message displayed
1114
+ * when the number of characters is under the configured maximum, `maxlength`.
1115
+ * This message is displayed visually and through assistive technologies. The
1116
+ * component will replace the `%{count}` placeholder with the number of
1117
+ * remaining characters. This is a [pluralised list of
1118
+ * messages](https://frontend.design-system.service.gov.uk/localise-govuk-frontend).
1119
+ * @property {string} [charactersAtLimit] - Message displayed when the number of
1120
+ * characters reaches the configured maximum, `maxlength`. This message is
1121
+ * displayed visually and through assistive technologies.
1122
+ * @property {TranslationPluralForms} [charactersOverLimit] - Message displayed
1123
+ * when the number of characters is over the configured maximum, `maxlength`.
1124
+ * This message is displayed visually and through assistive technologies. The
1125
+ * component will replace the `%{count}` placeholder with the number of
1126
+ * remaining characters. This is a [pluralised list of
1127
+ * messages](https://frontend.design-system.service.gov.uk/localise-govuk-frontend).
1128
+ * @property {TranslationPluralForms} [wordsUnderLimit] - Message displayed when
1129
+ * the number of words is under the configured maximum, `maxlength`. This
1130
+ * message is displayed visually and through assistive technologies. The
1131
+ * component will replace the `%{count}` placeholder with the number of
1132
+ * remaining words. This is a [pluralised list of
1133
+ * messages](https://frontend.design-system.service.gov.uk/localise-govuk-frontend).
1134
+ * @property {string} [wordsAtLimit] - Message displayed when the number of
1135
+ * words reaches the configured maximum, `maxlength`. This message is
1136
+ * displayed visually and through assistive technologies.
1137
+ * @property {TranslationPluralForms} [wordsOverLimit] - Message displayed when
1138
+ * the number of words is over the configured maximum, `maxlength`. This
1139
+ * message is displayed visually and through assistive technologies. The
1140
+ * component will replace the `%{count}` placeholder with the number of
1141
+ * remaining words. This is a [pluralised list of
1142
+ * messages](https://frontend.design-system.service.gov.uk/localise-govuk-frontend).
1143
+ * @property {TranslationPluralForms} [textareaDescription] - Message made
1144
+ * available to assistive technologies, if none is already present in the
1145
+ * HTML, to describe that the component accepts only a limited amount of
1146
+ * content. It is visible on the page when JavaScript is unavailable. The
1147
+ * component will replace the `%{count}` placeholder with the value of the
1148
+ * `maxlength` or `maxwords` parameter.
1149
+ */
1150
+
1151
+ /**
1152
+ * @typedef {import('../../common/index.mjs').Schema} Schema
1153
+ * @typedef {import('../../i18n.mjs').TranslationPluralForms} TranslationPluralForms
1154
+ */
1155
+ CharacterCount.moduleName = 'govuk-character-count';
1156
+ CharacterCount.defaults = Object.freeze({
1157
+ threshold: 0,
1158
+ i18n: {
1159
+ charactersUnderLimit: {
1160
+ one: 'You have %{count} character remaining',
1161
+ other: 'You have %{count} characters remaining'
1162
+ },
1163
+ charactersAtLimit: 'You have 0 characters remaining',
1164
+ charactersOverLimit: {
1165
+ one: 'You have %{count} character too many',
1166
+ other: 'You have %{count} characters too many'
1167
+ },
1168
+ wordsUnderLimit: {
1169
+ one: 'You have %{count} word remaining',
1170
+ other: 'You have %{count} words remaining'
1171
+ },
1172
+ wordsAtLimit: 'You have 0 words remaining',
1173
+ wordsOverLimit: {
1174
+ one: 'You have %{count} word too many',
1175
+ other: 'You have %{count} words too many'
1176
+ },
1177
+ textareaDescription: {
1178
+ other: ''
1179
+ }
1180
+ }
1181
+ });
1182
+ CharacterCount.schema = Object.freeze({
1183
+ properties: {
1184
+ i18n: {
1185
+ type: 'object'
1186
+ },
1187
+ maxwords: {
1188
+ type: 'number'
1189
+ },
1190
+ maxlength: {
1191
+ type: 'number'
1192
+ },
1193
+ threshold: {
1194
+ type: 'number'
1195
+ }
1196
+ },
1197
+ anyOf: [{
1198
+ required: ['maxwords'],
1199
+ errorMessage: 'Either "maxlength" or "maxwords" must be provided'
1200
+ }, {
1201
+ required: ['maxlength'],
1202
+ errorMessage: 'Either "maxlength" or "maxwords" must be provided'
1203
+ }]
1204
+ });
1205
+
1206
+ /**
1207
+ * Checkboxes component
1208
+ *
1209
+ * @preserve
1210
+ */
1211
+ class Checkboxes extends GOVUKFrontendComponent {
1212
+ /**
1213
+ * Checkboxes can be associated with a 'conditionally revealed' content block
1214
+ * – for example, a checkbox for 'Phone' could reveal an additional form field
1215
+ * for the user to enter their phone number.
1216
+ *
1217
+ * These associations are made using a `data-aria-controls` attribute, which
1218
+ * is promoted to an aria-controls attribute during initialisation.
1219
+ *
1220
+ * We also need to restore the state of any conditional reveals on the page
1221
+ * (for example if the user has navigated back), and set up event handlers to
1222
+ * keep the reveal in sync with the checkbox state.
1223
+ *
1224
+ * @param {Element | null} $root - HTML element to use for checkboxes
1225
+ */
1226
+ constructor($root) {
1227
+ super($root);
1228
+ this.$inputs = void 0;
1229
+ const $inputs = this.$root.querySelectorAll('input[type="checkbox"]');
1230
+ if (!$inputs.length) {
1231
+ throw new ElementError({
1232
+ component: Checkboxes,
1233
+ identifier: 'Form inputs (`<input type="checkbox">`)'
1234
+ });
1235
+ }
1236
+ this.$inputs = $inputs;
1237
+ this.$inputs.forEach($input => {
1238
+ const targetId = $input.getAttribute('data-aria-controls');
1239
+ if (!targetId) {
1240
+ return;
1241
+ }
1242
+ if (!document.getElementById(targetId)) {
1243
+ throw new ElementError({
1244
+ component: Checkboxes,
1245
+ identifier: `Conditional reveal (\`id="${targetId}"\`)`
1246
+ });
1247
+ }
1248
+ $input.setAttribute('aria-controls', targetId);
1249
+ $input.removeAttribute('data-aria-controls');
1250
+ });
1251
+ window.addEventListener('pageshow', () => this.syncAllConditionalReveals());
1252
+ this.syncAllConditionalReveals();
1253
+ this.$root.addEventListener('click', event => this.handleClick(event));
1254
+ }
1255
+ syncAllConditionalReveals() {
1256
+ this.$inputs.forEach($input => this.syncConditionalRevealWithInputState($input));
1257
+ }
1258
+ syncConditionalRevealWithInputState($input) {
1259
+ const targetId = $input.getAttribute('aria-controls');
1260
+ if (!targetId) {
1261
+ return;
1262
+ }
1263
+ const $target = document.getElementById(targetId);
1264
+ if ($target != null && $target.classList.contains('govuk-checkboxes__conditional')) {
1265
+ const inputIsChecked = $input.checked;
1266
+ $input.setAttribute('aria-expanded', inputIsChecked.toString());
1267
+ $target.classList.toggle('govuk-checkboxes__conditional--hidden', !inputIsChecked);
1268
+ }
1269
+ }
1270
+ unCheckAllInputsExcept($input) {
1271
+ const allInputsWithSameName = document.querySelectorAll(`input[type="checkbox"][name="${$input.name}"]`);
1272
+ allInputsWithSameName.forEach($inputWithSameName => {
1273
+ const hasSameFormOwner = $input.form === $inputWithSameName.form;
1274
+ if (hasSameFormOwner && $inputWithSameName !== $input) {
1275
+ $inputWithSameName.checked = false;
1276
+ this.syncConditionalRevealWithInputState($inputWithSameName);
1277
+ }
1278
+ });
1279
+ }
1280
+ unCheckExclusiveInputs($input) {
1281
+ const allInputsWithSameNameAndExclusiveBehaviour = document.querySelectorAll(`input[data-behaviour="exclusive"][type="checkbox"][name="${$input.name}"]`);
1282
+ allInputsWithSameNameAndExclusiveBehaviour.forEach($exclusiveInput => {
1283
+ const hasSameFormOwner = $input.form === $exclusiveInput.form;
1284
+ if (hasSameFormOwner) {
1285
+ $exclusiveInput.checked = false;
1286
+ this.syncConditionalRevealWithInputState($exclusiveInput);
1287
+ }
1288
+ });
1289
+ }
1290
+ handleClick(event) {
1291
+ const $clickedInput = event.target;
1292
+ if (!($clickedInput instanceof HTMLInputElement) || $clickedInput.type !== 'checkbox') {
1293
+ return;
1294
+ }
1295
+ const hasAriaControls = $clickedInput.getAttribute('aria-controls');
1296
+ if (hasAriaControls) {
1297
+ this.syncConditionalRevealWithInputState($clickedInput);
1298
+ }
1299
+ if (!$clickedInput.checked) {
1300
+ return;
1301
+ }
1302
+ const hasBehaviourExclusive = $clickedInput.getAttribute('data-behaviour') === 'exclusive';
1303
+ if (hasBehaviourExclusive) {
1304
+ this.unCheckAllInputsExcept($clickedInput);
1305
+ } else {
1306
+ this.unCheckExclusiveInputs($clickedInput);
1307
+ }
1308
+ }
1309
+ }
1310
+ Checkboxes.moduleName = 'govuk-checkboxes';
1311
+
1312
+ /**
1313
+ * Error summary component
1314
+ *
1315
+ * Takes focus on initialisation for accessible announcement, unless disabled in
1316
+ * configuration.
1317
+ *
1318
+ * @preserve
1319
+ */
1320
+ class ErrorSummary extends GOVUKFrontendComponent {
1321
+ /**
1322
+ * @param {Element | null} $root - HTML element to use for error summary
1323
+ * @param {ErrorSummaryConfig} [config] - Error summary config
1324
+ */
1325
+ constructor($root, config = {}) {
1326
+ super($root);
1327
+ this.config = void 0;
1328
+ this.config = mergeConfigs(ErrorSummary.defaults, config, normaliseDataset(ErrorSummary, this.$root.dataset));
1329
+ if (!this.config.disableAutoFocus) {
1330
+ setFocus(this.$root);
1331
+ }
1332
+ this.$root.addEventListener('click', event => this.handleClick(event));
1333
+ }
1334
+ handleClick(event) {
1335
+ const $target = event.target;
1336
+ if ($target && this.focusTarget($target)) {
1337
+ event.preventDefault();
1338
+ }
1339
+ }
1340
+ focusTarget($target) {
1341
+ if (!($target instanceof HTMLAnchorElement)) {
1342
+ return false;
1343
+ }
1344
+ const inputId = getFragmentFromUrl($target.href);
1345
+ if (!inputId) {
1346
+ return false;
1347
+ }
1348
+ const $input = document.getElementById(inputId);
1349
+ if (!$input) {
1350
+ return false;
1351
+ }
1352
+ const $legendOrLabel = this.getAssociatedLegendOrLabel($input);
1353
+ if (!$legendOrLabel) {
1354
+ return false;
1355
+ }
1356
+ $legendOrLabel.scrollIntoView();
1357
+ $input.focus({
1358
+ preventScroll: true
1359
+ });
1360
+ return true;
1361
+ }
1362
+ getAssociatedLegendOrLabel($input) {
1363
+ var _document$querySelect;
1364
+ const $fieldset = $input.closest('fieldset');
1365
+ if ($fieldset) {
1366
+ const $legends = $fieldset.getElementsByTagName('legend');
1367
+ if ($legends.length) {
1368
+ const $candidateLegend = $legends[0];
1369
+ if ($input instanceof HTMLInputElement && ($input.type === 'checkbox' || $input.type === 'radio')) {
1370
+ return $candidateLegend;
1371
+ }
1372
+ const legendTop = $candidateLegend.getBoundingClientRect().top;
1373
+ const inputRect = $input.getBoundingClientRect();
1374
+ if (inputRect.height && window.innerHeight) {
1375
+ const inputBottom = inputRect.top + inputRect.height;
1376
+ if (inputBottom - legendTop < window.innerHeight / 2) {
1377
+ return $candidateLegend;
1378
+ }
1379
+ }
1380
+ }
1381
+ }
1382
+ return (_document$querySelect = document.querySelector(`label[for='${$input.getAttribute('id')}']`)) != null ? _document$querySelect : $input.closest('label');
1383
+ }
1384
+ }
1385
+
1386
+ /**
1387
+ * Error summary config
1388
+ *
1389
+ * @typedef {object} ErrorSummaryConfig
1390
+ * @property {boolean} [disableAutoFocus=false] - If set to `true` the error
1391
+ * summary will not be focussed when the page loads.
1392
+ */
1393
+
1394
+ /**
1395
+ * @typedef {import('../../common/index.mjs').Schema} Schema
1396
+ */
1397
+ ErrorSummary.moduleName = 'govuk-error-summary';
1398
+ ErrorSummary.defaults = Object.freeze({
1399
+ disableAutoFocus: false
1400
+ });
1401
+ ErrorSummary.schema = Object.freeze({
1402
+ properties: {
1403
+ disableAutoFocus: {
1404
+ type: 'boolean'
1405
+ }
1406
+ }
1407
+ });
1408
+
1409
+ /**
1410
+ * Exit this page component
1411
+ *
1412
+ * @preserve
1413
+ */
1414
+ class ExitThisPage extends GOVUKFrontendComponent {
1415
+ /**
1416
+ * @param {Element | null} $root - HTML element that wraps the Exit This Page button
1417
+ * @param {ExitThisPageConfig} [config] - Exit This Page config
1418
+ */
1419
+ constructor($root, config = {}) {
1420
+ super($root);
1421
+ this.config = void 0;
1422
+ this.i18n = void 0;
1423
+ this.$button = void 0;
1424
+ this.$skiplinkButton = null;
1425
+ this.$updateSpan = null;
1426
+ this.$indicatorContainer = null;
1427
+ this.$overlay = null;
1428
+ this.keypressCounter = 0;
1429
+ this.lastKeyWasModified = false;
1430
+ this.timeoutTime = 5000;
1431
+ this.keypressTimeoutId = null;
1432
+ this.timeoutMessageId = null;
1433
+ const $button = this.$root.querySelector('.govuk-exit-this-page__button');
1434
+ if (!($button instanceof HTMLAnchorElement)) {
1435
+ throw new ElementError({
1436
+ component: ExitThisPage,
1437
+ element: $button,
1438
+ expectedType: 'HTMLAnchorElement',
1439
+ identifier: 'Button (`.govuk-exit-this-page__button`)'
1440
+ });
1441
+ }
1442
+ this.config = mergeConfigs(ExitThisPage.defaults, config, normaliseDataset(ExitThisPage, this.$root.dataset));
1443
+ this.i18n = new I18n(this.config.i18n);
1444
+ this.$button = $button;
1445
+ const $skiplinkButton = document.querySelector('.govuk-js-exit-this-page-skiplink');
1446
+ if ($skiplinkButton instanceof HTMLAnchorElement) {
1447
+ this.$skiplinkButton = $skiplinkButton;
1448
+ }
1449
+ this.buildIndicator();
1450
+ this.initUpdateSpan();
1451
+ this.initButtonClickHandler();
1452
+ if (!('govukFrontendExitThisPageKeypress' in document.body.dataset)) {
1453
+ document.addEventListener('keyup', this.handleKeypress.bind(this), true);
1454
+ document.body.dataset.govukFrontendExitThisPageKeypress = 'true';
1455
+ }
1456
+ window.addEventListener('pageshow', this.resetPage.bind(this));
1457
+ }
1458
+ initUpdateSpan() {
1459
+ this.$updateSpan = document.createElement('span');
1460
+ this.$updateSpan.setAttribute('role', 'status');
1461
+ this.$updateSpan.className = 'govuk-visually-hidden';
1462
+ this.$root.appendChild(this.$updateSpan);
1463
+ }
1464
+ initButtonClickHandler() {
1465
+ this.$button.addEventListener('click', this.handleClick.bind(this));
1466
+ if (this.$skiplinkButton) {
1467
+ this.$skiplinkButton.addEventListener('click', this.handleClick.bind(this));
1468
+ }
1469
+ }
1470
+ buildIndicator() {
1471
+ this.$indicatorContainer = document.createElement('div');
1472
+ this.$indicatorContainer.className = 'govuk-exit-this-page__indicator';
1473
+ this.$indicatorContainer.setAttribute('aria-hidden', 'true');
1474
+ for (let i = 0; i < 3; i++) {
1475
+ const $indicator = document.createElement('div');
1476
+ $indicator.className = 'govuk-exit-this-page__indicator-light';
1477
+ this.$indicatorContainer.appendChild($indicator);
1478
+ }
1479
+ this.$button.appendChild(this.$indicatorContainer);
1480
+ }
1481
+ updateIndicator() {
1482
+ if (!this.$indicatorContainer) {
1483
+ return;
1484
+ }
1485
+ this.$indicatorContainer.classList.toggle('govuk-exit-this-page__indicator--visible', this.keypressCounter > 0);
1486
+ const $indicators = this.$indicatorContainer.querySelectorAll('.govuk-exit-this-page__indicator-light');
1487
+ $indicators.forEach(($indicator, index) => {
1488
+ $indicator.classList.toggle('govuk-exit-this-page__indicator-light--on', index < this.keypressCounter);
1489
+ });
1490
+ }
1491
+ exitPage() {
1492
+ if (!this.$updateSpan) {
1493
+ return;
1494
+ }
1495
+ this.$updateSpan.textContent = '';
1496
+ document.body.classList.add('govuk-exit-this-page-hide-content');
1497
+ this.$overlay = document.createElement('div');
1498
+ this.$overlay.className = 'govuk-exit-this-page-overlay';
1499
+ this.$overlay.setAttribute('role', 'alert');
1500
+ document.body.appendChild(this.$overlay);
1501
+ this.$overlay.textContent = this.i18n.t('activated');
1502
+ window.location.href = this.$button.href;
1503
+ }
1504
+ handleClick(event) {
1505
+ event.preventDefault();
1506
+ this.exitPage();
1507
+ }
1508
+ handleKeypress(event) {
1509
+ if (!this.$updateSpan) {
1510
+ return;
1511
+ }
1512
+ if (event.key === 'Shift' && !this.lastKeyWasModified) {
1513
+ this.keypressCounter += 1;
1514
+ this.updateIndicator();
1515
+ if (this.timeoutMessageId) {
1516
+ window.clearTimeout(this.timeoutMessageId);
1517
+ this.timeoutMessageId = null;
1518
+ }
1519
+ if (this.keypressCounter >= 3) {
1520
+ this.keypressCounter = 0;
1521
+ if (this.keypressTimeoutId) {
1522
+ window.clearTimeout(this.keypressTimeoutId);
1523
+ this.keypressTimeoutId = null;
1524
+ }
1525
+ this.exitPage();
1526
+ } else {
1527
+ if (this.keypressCounter === 1) {
1528
+ this.$updateSpan.textContent = this.i18n.t('pressTwoMoreTimes');
1529
+ } else {
1530
+ this.$updateSpan.textContent = this.i18n.t('pressOneMoreTime');
1531
+ }
1532
+ }
1533
+ this.setKeypressTimer();
1534
+ } else if (this.keypressTimeoutId) {
1535
+ this.resetKeypressTimer();
1536
+ }
1537
+ this.lastKeyWasModified = event.shiftKey;
1538
+ }
1539
+ setKeypressTimer() {
1540
+ if (this.keypressTimeoutId) {
1541
+ window.clearTimeout(this.keypressTimeoutId);
1542
+ }
1543
+ this.keypressTimeoutId = window.setTimeout(this.resetKeypressTimer.bind(this), this.timeoutTime);
1544
+ }
1545
+ resetKeypressTimer() {
1546
+ if (!this.$updateSpan) {
1547
+ return;
1548
+ }
1549
+ if (this.keypressTimeoutId) {
1550
+ window.clearTimeout(this.keypressTimeoutId);
1551
+ this.keypressTimeoutId = null;
1552
+ }
1553
+ const $updateSpan = this.$updateSpan;
1554
+ this.keypressCounter = 0;
1555
+ $updateSpan.textContent = this.i18n.t('timedOut');
1556
+ this.timeoutMessageId = window.setTimeout(() => {
1557
+ $updateSpan.textContent = '';
1558
+ }, this.timeoutTime);
1559
+ this.updateIndicator();
1560
+ }
1561
+ resetPage() {
1562
+ document.body.classList.remove('govuk-exit-this-page-hide-content');
1563
+ if (this.$overlay) {
1564
+ this.$overlay.remove();
1565
+ this.$overlay = null;
1566
+ }
1567
+ if (this.$updateSpan) {
1568
+ this.$updateSpan.setAttribute('role', 'status');
1569
+ this.$updateSpan.textContent = '';
1570
+ }
1571
+ this.updateIndicator();
1572
+ if (this.keypressTimeoutId) {
1573
+ window.clearTimeout(this.keypressTimeoutId);
1574
+ }
1575
+ if (this.timeoutMessageId) {
1576
+ window.clearTimeout(this.timeoutMessageId);
1577
+ }
1578
+ }
1579
+ }
1580
+
1581
+ /**
1582
+ * Exit this Page config
1583
+ *
1584
+ * @see {@link ExitThisPage.defaults}
1585
+ * @typedef {object} ExitThisPageConfig
1586
+ * @property {ExitThisPageTranslations} [i18n=ExitThisPage.defaults.i18n] - Exit this page translations
1587
+ */
1588
+
1589
+ /**
1590
+ * Exit this Page translations
1591
+ *
1592
+ * @see {@link ExitThisPage.defaults.i18n}
1593
+ * @typedef {object} ExitThisPageTranslations
1594
+ *
1595
+ * Messages used by the component programatically inserted text, including
1596
+ * overlay text and screen reader announcements.
1597
+ * @property {string} [activated] - Screen reader announcement for when EtP
1598
+ * keypress functionality has been successfully activated.
1599
+ * @property {string} [timedOut] - Screen reader announcement for when the EtP
1600
+ * keypress functionality has timed out.
1601
+ * @property {string} [pressTwoMoreTimes] - Screen reader announcement informing
1602
+ * the user they must press the activation key two more times.
1603
+ * @property {string} [pressOneMoreTime] - Screen reader announcement informing
1604
+ * the user they must press the activation key one more time.
1605
+ */
1606
+
1607
+ /**
1608
+ * @typedef {import('../../common/index.mjs').Schema} Schema
1609
+ */
1610
+ ExitThisPage.moduleName = 'govuk-exit-this-page';
1611
+ ExitThisPage.defaults = Object.freeze({
1612
+ i18n: {
1613
+ activated: 'Loading.',
1614
+ timedOut: 'Exit this page expired.',
1615
+ pressTwoMoreTimes: 'Shift, press 2 more times to exit.',
1616
+ pressOneMoreTime: 'Shift, press 1 more time to exit.'
1617
+ }
1618
+ });
1619
+ ExitThisPage.schema = Object.freeze({
1620
+ properties: {
1621
+ i18n: {
1622
+ type: 'object'
1623
+ }
1624
+ }
1625
+ });
1626
+
1627
+ /**
1628
+ * Header component
1629
+ *
1630
+ * @preserve
1631
+ */
1632
+ class Header extends GOVUKFrontendComponent {
1633
+ /**
1634
+ * Apply a matchMedia for desktop which will trigger a state sync if the
1635
+ * browser viewport moves between states.
1636
+ *
1637
+ * @param {Element | null} $root - HTML element to use for header
1638
+ */
1639
+ constructor($root) {
1640
+ super($root);
1641
+ this.$menuButton = void 0;
1642
+ this.$menu = void 0;
1643
+ this.menuIsOpen = false;
1644
+ this.mql = null;
1645
+ const $menuButton = this.$root.querySelector('.govuk-js-header-toggle');
1646
+ if (!$menuButton) {
1647
+ return this;
1648
+ }
1649
+ const menuId = $menuButton.getAttribute('aria-controls');
1650
+ if (!menuId) {
1651
+ throw new ElementError({
1652
+ component: Header,
1653
+ identifier: 'Navigation button (`<button class="govuk-js-header-toggle">`) attribute (`aria-controls`)'
1654
+ });
1655
+ }
1656
+ const $menu = document.getElementById(menuId);
1657
+ if (!$menu) {
1658
+ throw new ElementError({
1659
+ component: Header,
1660
+ element: $menu,
1661
+ identifier: `Navigation (\`<ul id="${menuId}">\`)`
1662
+ });
1663
+ }
1664
+ this.$menu = $menu;
1665
+ this.$menuButton = $menuButton;
1666
+ this.setupResponsiveChecks();
1667
+ this.$menuButton.addEventListener('click', () => this.handleMenuButtonClick());
1668
+ }
1669
+ setupResponsiveChecks() {
1670
+ const breakpoint = getBreakpoint('desktop');
1671
+ if (!breakpoint.value) {
1672
+ throw new ElementError({
1673
+ component: Header,
1674
+ identifier: `CSS custom property (\`${breakpoint.property}\`) on pseudo-class \`:root\``
1675
+ });
1676
+ }
1677
+ this.mql = window.matchMedia(`(min-width: ${breakpoint.value})`);
1678
+ if ('addEventListener' in this.mql) {
1679
+ this.mql.addEventListener('change', () => this.checkMode());
1680
+ } else {
1681
+ this.mql.addListener(() => this.checkMode());
1682
+ }
1683
+ this.checkMode();
1684
+ }
1685
+ checkMode() {
1686
+ if (!this.mql || !this.$menu || !this.$menuButton) {
1687
+ return;
1688
+ }
1689
+ if (this.mql.matches) {
1690
+ this.$menu.removeAttribute('hidden');
1691
+ this.$menuButton.setAttribute('hidden', '');
1692
+ } else {
1693
+ this.$menuButton.removeAttribute('hidden');
1694
+ this.$menuButton.setAttribute('aria-expanded', this.menuIsOpen.toString());
1695
+ if (this.menuIsOpen) {
1696
+ this.$menu.removeAttribute('hidden');
1697
+ } else {
1698
+ this.$menu.setAttribute('hidden', '');
1699
+ }
1700
+ }
1701
+ }
1702
+ handleMenuButtonClick() {
1703
+ this.menuIsOpen = !this.menuIsOpen;
1704
+ this.checkMode();
1705
+ }
1706
+ }
1707
+ Header.moduleName = 'govuk-header';
1708
+
1709
+ /**
1710
+ * Notification Banner component
1711
+ *
1712
+ * @preserve
1713
+ */
1714
+ class NotificationBanner extends GOVUKFrontendComponent {
1715
+ /**
1716
+ * @param {Element | null} $root - HTML element to use for notification banner
1717
+ * @param {NotificationBannerConfig} [config] - Notification banner config
1718
+ */
1719
+ constructor($root, config = {}) {
1720
+ super($root);
1721
+ this.config = void 0;
1722
+ this.config = mergeConfigs(NotificationBanner.defaults, config, normaliseDataset(NotificationBanner, this.$root.dataset));
1723
+ if (this.$root.getAttribute('role') === 'alert' && !this.config.disableAutoFocus) {
1724
+ setFocus(this.$root);
1725
+ }
1726
+ }
1727
+ }
1728
+
1729
+ /**
1730
+ * Notification banner config
1731
+ *
1732
+ * @typedef {object} NotificationBannerConfig
1733
+ * @property {boolean} [disableAutoFocus=false] - If set to `true` the
1734
+ * notification banner will not be focussed when the page loads. This only
1735
+ * applies if the component has a `role` of `alert` – in other cases the
1736
+ * component will not be focused on page load, regardless of this option.
1737
+ */
1738
+
1739
+ /**
1740
+ * @typedef {import('../../common/index.mjs').Schema} Schema
1741
+ */
1742
+ NotificationBanner.moduleName = 'govuk-notification-banner';
1743
+ NotificationBanner.defaults = Object.freeze({
1744
+ disableAutoFocus: false
1745
+ });
1746
+ NotificationBanner.schema = Object.freeze({
1747
+ properties: {
1748
+ disableAutoFocus: {
1749
+ type: 'boolean'
1750
+ }
1751
+ }
1752
+ });
1753
+
1754
+ /**
1755
+ * Password input component
1756
+ *
1757
+ * @preserve
1758
+ */
1759
+ class PasswordInput extends GOVUKFrontendComponent {
1760
+ /**
1761
+ * @param {Element | null} $root - HTML element to use for password input
1762
+ * @param {PasswordInputConfig} [config] - Password input config
1763
+ */
1764
+ constructor($root, config = {}) {
1765
+ super($root);
1766
+ this.config = void 0;
1767
+ this.i18n = void 0;
1768
+ this.$input = void 0;
1769
+ this.$showHideButton = void 0;
1770
+ this.$screenReaderStatusMessage = void 0;
1771
+ const $input = this.$root.querySelector('.govuk-js-password-input-input');
1772
+ if (!($input instanceof HTMLInputElement)) {
1773
+ throw new ElementError({
1774
+ component: PasswordInput,
1775
+ element: $input,
1776
+ expectedType: 'HTMLInputElement',
1777
+ identifier: 'Form field (`.govuk-js-password-input-input`)'
1778
+ });
1779
+ }
1780
+ if ($input.type !== 'password') {
1781
+ throw new ElementError('Password input: Form field (`.govuk-js-password-input-input`) must be of type `password`.');
1782
+ }
1783
+ const $showHideButton = this.$root.querySelector('.govuk-js-password-input-toggle');
1784
+ if (!($showHideButton instanceof HTMLButtonElement)) {
1785
+ throw new ElementError({
1786
+ component: PasswordInput,
1787
+ element: $showHideButton,
1788
+ expectedType: 'HTMLButtonElement',
1789
+ identifier: 'Button (`.govuk-js-password-input-toggle`)'
1790
+ });
1791
+ }
1792
+ if ($showHideButton.type !== 'button') {
1793
+ throw new ElementError('Password input: Button (`.govuk-js-password-input-toggle`) must be of type `button`.');
1794
+ }
1795
+ this.$input = $input;
1796
+ this.$showHideButton = $showHideButton;
1797
+ this.config = mergeConfigs(PasswordInput.defaults, config, normaliseDataset(PasswordInput, this.$root.dataset));
1798
+ this.i18n = new I18n(this.config.i18n, {
1799
+ locale: closestAttributeValue(this.$root, 'lang')
1800
+ });
1801
+ this.$showHideButton.removeAttribute('hidden');
1802
+ const $screenReaderStatusMessage = document.createElement('div');
1803
+ $screenReaderStatusMessage.className = 'govuk-password-input__sr-status govuk-visually-hidden';
1804
+ $screenReaderStatusMessage.setAttribute('aria-live', 'polite');
1805
+ this.$screenReaderStatusMessage = $screenReaderStatusMessage;
1806
+ this.$input.insertAdjacentElement('afterend', $screenReaderStatusMessage);
1807
+ this.$showHideButton.addEventListener('click', this.toggle.bind(this));
1808
+ if (this.$input.form) {
1809
+ this.$input.form.addEventListener('submit', () => this.hide());
1810
+ }
1811
+ window.addEventListener('pageshow', event => {
1812
+ if (event.persisted && this.$input.type !== 'password') {
1813
+ this.hide();
1814
+ }
1815
+ });
1816
+ this.hide();
1817
+ }
1818
+ toggle(event) {
1819
+ event.preventDefault();
1820
+ if (this.$input.type === 'password') {
1821
+ this.show();
1822
+ return;
1823
+ }
1824
+ this.hide();
1825
+ }
1826
+ show() {
1827
+ this.setType('text');
1828
+ }
1829
+ hide() {
1830
+ this.setType('password');
1831
+ }
1832
+ setType(type) {
1833
+ if (type === this.$input.type) {
1834
+ return;
1835
+ }
1836
+ this.$input.setAttribute('type', type);
1837
+ const isHidden = type === 'password';
1838
+ const prefixButton = isHidden ? 'show' : 'hide';
1839
+ const prefixStatus = isHidden ? 'passwordHidden' : 'passwordShown';
1840
+ this.$showHideButton.innerText = this.i18n.t(`${prefixButton}Password`);
1841
+ this.$showHideButton.setAttribute('aria-label', this.i18n.t(`${prefixButton}PasswordAriaLabel`));
1842
+ this.$screenReaderStatusMessage.innerText = this.i18n.t(`${prefixStatus}Announcement`);
1843
+ }
1844
+ }
1845
+
1846
+ /**
1847
+ * Password input config
1848
+ *
1849
+ * @typedef {object} PasswordInputConfig
1850
+ * @property {PasswordInputTranslations} [i18n=PasswordInput.defaults.i18n] - Password input translations
1851
+ */
1852
+
1853
+ /**
1854
+ * Password input translations
1855
+ *
1856
+ * @see {@link PasswordInput.defaults.i18n}
1857
+ * @typedef {object} PasswordInputTranslations
1858
+ *
1859
+ * Messages displayed to the user indicating the state of the show/hide toggle.
1860
+ * @property {string} [showPassword] - Visible text of the button when the
1861
+ * password is currently hidden. Plain text only.
1862
+ * @property {string} [hidePassword] - Visible text of the button when the
1863
+ * password is currently visible. Plain text only.
1864
+ * @property {string} [showPasswordAriaLabel] - aria-label of the button when
1865
+ * the password is currently hidden. Plain text only.
1866
+ * @property {string} [hidePasswordAriaLabel] - aria-label of the button when
1867
+ * the password is currently visible. Plain text only.
1868
+ * @property {string} [passwordShownAnnouncement] - Screen reader
1869
+ * announcement to make when the password has just become visible.
1870
+ * Plain text only.
1871
+ * @property {string} [passwordHiddenAnnouncement] - Screen reader
1872
+ * announcement to make when the password has just been hidden.
1873
+ * Plain text only.
1874
+ */
1875
+
1876
+ /**
1877
+ * @typedef {import('../../common/index.mjs').Schema} Schema
1878
+ * @typedef {import('../../i18n.mjs').TranslationPluralForms} TranslationPluralForms
1879
+ */
1880
+ PasswordInput.moduleName = 'govuk-password-input';
1881
+ PasswordInput.defaults = Object.freeze({
1882
+ i18n: {
1883
+ showPassword: 'Show',
1884
+ hidePassword: 'Hide',
1885
+ showPasswordAriaLabel: 'Show password',
1886
+ hidePasswordAriaLabel: 'Hide password',
1887
+ passwordShownAnnouncement: 'Your password is visible',
1888
+ passwordHiddenAnnouncement: 'Your password is hidden'
1889
+ }
1890
+ });
1891
+ PasswordInput.schema = Object.freeze({
1892
+ properties: {
1893
+ i18n: {
1894
+ type: 'object'
1895
+ }
1896
+ }
1897
+ });
1898
+
1899
+ /**
1900
+ * Radios component
1901
+ *
1902
+ * @preserve
1903
+ */
1904
+ class Radios extends GOVUKFrontendComponent {
1905
+ /**
1906
+ * Radios can be associated with a 'conditionally revealed' content block –
1907
+ * for example, a radio for 'Phone' could reveal an additional form field for
1908
+ * the user to enter their phone number.
1909
+ *
1910
+ * These associations are made using a `data-aria-controls` attribute, which
1911
+ * is promoted to an aria-controls attribute during initialisation.
1912
+ *
1913
+ * We also need to restore the state of any conditional reveals on the page
1914
+ * (for example if the user has navigated back), and set up event handlers to
1915
+ * keep the reveal in sync with the radio state.
1916
+ *
1917
+ * @param {Element | null} $root - HTML element to use for radios
1918
+ */
1919
+ constructor($root) {
1920
+ super($root);
1921
+ this.$inputs = void 0;
1922
+ const $inputs = this.$root.querySelectorAll('input[type="radio"]');
1923
+ if (!$inputs.length) {
1924
+ throw new ElementError({
1925
+ component: Radios,
1926
+ identifier: 'Form inputs (`<input type="radio">`)'
1927
+ });
1928
+ }
1929
+ this.$inputs = $inputs;
1930
+ this.$inputs.forEach($input => {
1931
+ const targetId = $input.getAttribute('data-aria-controls');
1932
+ if (!targetId) {
1933
+ return;
1934
+ }
1935
+ if (!document.getElementById(targetId)) {
1936
+ throw new ElementError({
1937
+ component: Radios,
1938
+ identifier: `Conditional reveal (\`id="${targetId}"\`)`
1939
+ });
1940
+ }
1941
+ $input.setAttribute('aria-controls', targetId);
1942
+ $input.removeAttribute('data-aria-controls');
1943
+ });
1944
+ window.addEventListener('pageshow', () => this.syncAllConditionalReveals());
1945
+ this.syncAllConditionalReveals();
1946
+ this.$root.addEventListener('click', event => this.handleClick(event));
1947
+ }
1948
+ syncAllConditionalReveals() {
1949
+ this.$inputs.forEach($input => this.syncConditionalRevealWithInputState($input));
1950
+ }
1951
+ syncConditionalRevealWithInputState($input) {
1952
+ const targetId = $input.getAttribute('aria-controls');
1953
+ if (!targetId) {
1954
+ return;
1955
+ }
1956
+ const $target = document.getElementById(targetId);
1957
+ if ($target != null && $target.classList.contains('govuk-radios__conditional')) {
1958
+ const inputIsChecked = $input.checked;
1959
+ $input.setAttribute('aria-expanded', inputIsChecked.toString());
1960
+ $target.classList.toggle('govuk-radios__conditional--hidden', !inputIsChecked);
1961
+ }
1962
+ }
1963
+ handleClick(event) {
1964
+ const $clickedInput = event.target;
1965
+ if (!($clickedInput instanceof HTMLInputElement) || $clickedInput.type !== 'radio') {
1966
+ return;
1967
+ }
1968
+ const $allInputs = document.querySelectorAll('input[type="radio"][aria-controls]');
1969
+ const $clickedInputForm = $clickedInput.form;
1970
+ const $clickedInputName = $clickedInput.name;
1971
+ $allInputs.forEach($input => {
1972
+ const hasSameFormOwner = $input.form === $clickedInputForm;
1973
+ const hasSameName = $input.name === $clickedInputName;
1974
+ if (hasSameName && hasSameFormOwner) {
1975
+ this.syncConditionalRevealWithInputState($input);
1976
+ }
1977
+ });
1978
+ }
1979
+ }
1980
+ Radios.moduleName = 'govuk-radios';
1981
+
1982
+ /**
1983
+ * Service Navigation component
1984
+ *
1985
+ * @preserve
1986
+ */
1987
+ class ServiceNavigation extends GOVUKFrontendComponent {
1988
+ /**
1989
+ * @param {Element | null} $root - HTML element to use for header
1990
+ */
1991
+ constructor($root) {
1992
+ super($root);
1993
+ this.$menuButton = void 0;
1994
+ this.$menu = void 0;
1995
+ this.menuIsOpen = false;
1996
+ this.mql = null;
1997
+ const $menuButton = this.$root.querySelector('.govuk-js-service-navigation-toggle');
1998
+ if (!$menuButton) {
1999
+ return this;
2000
+ }
2001
+ const menuId = $menuButton.getAttribute('aria-controls');
2002
+ if (!menuId) {
2003
+ throw new ElementError({
2004
+ component: ServiceNavigation,
2005
+ identifier: 'Navigation button (`<button class="govuk-js-service-navigation-toggle">`) attribute (`aria-controls`)'
2006
+ });
2007
+ }
2008
+ const $menu = document.getElementById(menuId);
2009
+ if (!$menu) {
2010
+ throw new ElementError({
2011
+ component: ServiceNavigation,
2012
+ element: $menu,
2013
+ identifier: `Navigation (\`<ul id="${menuId}">\`)`
2014
+ });
2015
+ }
2016
+ this.$menu = $menu;
2017
+ this.$menuButton = $menuButton;
2018
+ this.setupResponsiveChecks();
2019
+ this.$menuButton.addEventListener('click', () => this.handleMenuButtonClick());
2020
+ }
2021
+ setupResponsiveChecks() {
2022
+ const breakpoint = getBreakpoint('tablet');
2023
+ if (!breakpoint.value) {
2024
+ throw new ElementError({
2025
+ component: ServiceNavigation,
2026
+ identifier: `CSS custom property (\`${breakpoint.property}\`) on pseudo-class \`:root\``
2027
+ });
2028
+ }
2029
+ this.mql = window.matchMedia(`(min-width: ${breakpoint.value})`);
2030
+ if ('addEventListener' in this.mql) {
2031
+ this.mql.addEventListener('change', () => this.checkMode());
2032
+ } else {
2033
+ this.mql.addListener(() => this.checkMode());
2034
+ }
2035
+ this.checkMode();
2036
+ }
2037
+ checkMode() {
2038
+ if (!this.mql || !this.$menu || !this.$menuButton) {
2039
+ return;
2040
+ }
2041
+ if (this.mql.matches) {
2042
+ this.$menu.removeAttribute('hidden');
2043
+ this.$menuButton.setAttribute('hidden', '');
2044
+ } else {
2045
+ this.$menuButton.removeAttribute('hidden');
2046
+ this.$menuButton.setAttribute('aria-expanded', this.menuIsOpen.toString());
2047
+ if (this.menuIsOpen) {
2048
+ this.$menu.removeAttribute('hidden');
2049
+ } else {
2050
+ this.$menu.setAttribute('hidden', '');
2051
+ }
2052
+ }
2053
+ }
2054
+ handleMenuButtonClick() {
2055
+ this.menuIsOpen = !this.menuIsOpen;
2056
+ this.checkMode();
2057
+ }
2058
+ }
2059
+ ServiceNavigation.moduleName = 'govuk-service-navigation';
2060
+
2061
+ /**
2062
+ * Skip link component
2063
+ *
2064
+ * @preserve
2065
+ * @augments GOVUKFrontendComponent<HTMLAnchorElement>
2066
+ */
2067
+ class SkipLink extends GOVUKFrontendComponent {
2068
+ /**
2069
+ * @param {Element | null} $root - HTML element to use for skip link
2070
+ * @throws {ElementError} when $root is not set or the wrong type
2071
+ * @throws {ElementError} when $root.hash does not contain a hash
2072
+ * @throws {ElementError} when the linked element is missing or the wrong type
2073
+ */
2074
+ constructor($root) {
2075
+ var _this$$root$getAttrib;
2076
+ super($root);
2077
+ const hash = this.$root.hash;
2078
+ const href = (_this$$root$getAttrib = this.$root.getAttribute('href')) != null ? _this$$root$getAttrib : '';
2079
+ let url;
2080
+ try {
2081
+ url = new window.URL(this.$root.href);
2082
+ } catch (error) {
2083
+ throw new ElementError(`Skip link: Target link (\`href="${href}"\`) is invalid`);
2084
+ }
2085
+ if (url.origin !== window.location.origin || url.pathname !== window.location.pathname) {
2086
+ return;
2087
+ }
2088
+ const linkedElementId = getFragmentFromUrl(hash);
2089
+ if (!linkedElementId) {
2090
+ throw new ElementError(`Skip link: Target link (\`href="${href}"\`) has no hash fragment`);
2091
+ }
2092
+ const $linkedElement = document.getElementById(linkedElementId);
2093
+ if (!$linkedElement) {
2094
+ throw new ElementError({
2095
+ component: SkipLink,
2096
+ element: $linkedElement,
2097
+ identifier: `Target content (\`id="${linkedElementId}"\`)`
2098
+ });
2099
+ }
2100
+ this.$root.addEventListener('click', () => setFocus($linkedElement, {
2101
+ onBeforeFocus() {
2102
+ $linkedElement.classList.add('govuk-skip-link-focused-element');
2103
+ },
2104
+ onBlur() {
2105
+ $linkedElement.classList.remove('govuk-skip-link-focused-element');
2106
+ }
2107
+ }));
2108
+ }
2109
+ }
2110
+ SkipLink.elementType = HTMLAnchorElement;
2111
+ SkipLink.moduleName = 'govuk-skip-link';
2112
+
2113
+ /**
2114
+ * Tabs component
2115
+ *
2116
+ * @preserve
2117
+ */
2118
+ class Tabs extends GOVUKFrontendComponent {
2119
+ /**
2120
+ * @param {Element | null} $root - HTML element to use for tabs
2121
+ */
2122
+ constructor($root) {
2123
+ super($root);
2124
+ this.$tabs = void 0;
2125
+ this.$tabList = void 0;
2126
+ this.$tabListItems = void 0;
2127
+ this.jsHiddenClass = 'govuk-tabs__panel--hidden';
2128
+ this.changingHash = false;
2129
+ this.boundTabClick = void 0;
2130
+ this.boundTabKeydown = void 0;
2131
+ this.boundOnHashChange = void 0;
2132
+ this.mql = null;
2133
+ const $tabs = this.$root.querySelectorAll('a.govuk-tabs__tab');
2134
+ if (!$tabs.length) {
2135
+ throw new ElementError({
2136
+ component: Tabs,
2137
+ identifier: 'Links (`<a class="govuk-tabs__tab">`)'
2138
+ });
2139
+ }
2140
+ this.$tabs = $tabs;
2141
+ this.boundTabClick = this.onTabClick.bind(this);
2142
+ this.boundTabKeydown = this.onTabKeydown.bind(this);
2143
+ this.boundOnHashChange = this.onHashChange.bind(this);
2144
+ const $tabList = this.$root.querySelector('.govuk-tabs__list');
2145
+ const $tabListItems = this.$root.querySelectorAll('li.govuk-tabs__list-item');
2146
+ if (!$tabList) {
2147
+ throw new ElementError({
2148
+ component: Tabs,
2149
+ identifier: 'List (`<ul class="govuk-tabs__list">`)'
2150
+ });
2151
+ }
2152
+ if (!$tabListItems.length) {
2153
+ throw new ElementError({
2154
+ component: Tabs,
2155
+ identifier: 'List items (`<li class="govuk-tabs__list-item">`)'
2156
+ });
2157
+ }
2158
+ this.$tabList = $tabList;
2159
+ this.$tabListItems = $tabListItems;
2160
+ this.setupResponsiveChecks();
2161
+ }
2162
+ setupResponsiveChecks() {
2163
+ const breakpoint = getBreakpoint('tablet');
2164
+ if (!breakpoint.value) {
2165
+ throw new ElementError({
2166
+ component: Tabs,
2167
+ identifier: `CSS custom property (\`${breakpoint.property}\`) on pseudo-class \`:root\``
2168
+ });
2169
+ }
2170
+ this.mql = window.matchMedia(`(min-width: ${breakpoint.value})`);
2171
+ if ('addEventListener' in this.mql) {
2172
+ this.mql.addEventListener('change', () => this.checkMode());
2173
+ } else {
2174
+ this.mql.addListener(() => this.checkMode());
2175
+ }
2176
+ this.checkMode();
2177
+ }
2178
+ checkMode() {
2179
+ var _this$mql;
2180
+ if ((_this$mql = this.mql) != null && _this$mql.matches) {
2181
+ this.setup();
2182
+ } else {
2183
+ this.teardown();
2184
+ }
2185
+ }
2186
+ setup() {
2187
+ var _this$getTab;
2188
+ this.$tabList.setAttribute('role', 'tablist');
2189
+ this.$tabListItems.forEach($item => {
2190
+ $item.setAttribute('role', 'presentation');
2191
+ });
2192
+ this.$tabs.forEach($tab => {
2193
+ this.setAttributes($tab);
2194
+ $tab.addEventListener('click', this.boundTabClick, true);
2195
+ $tab.addEventListener('keydown', this.boundTabKeydown, true);
2196
+ this.hideTab($tab);
2197
+ });
2198
+ const $activeTab = (_this$getTab = this.getTab(window.location.hash)) != null ? _this$getTab : this.$tabs[0];
2199
+ this.showTab($activeTab);
2200
+ window.addEventListener('hashchange', this.boundOnHashChange, true);
2201
+ }
2202
+ teardown() {
2203
+ this.$tabList.removeAttribute('role');
2204
+ this.$tabListItems.forEach($item => {
2205
+ $item.removeAttribute('role');
2206
+ });
2207
+ this.$tabs.forEach($tab => {
2208
+ $tab.removeEventListener('click', this.boundTabClick, true);
2209
+ $tab.removeEventListener('keydown', this.boundTabKeydown, true);
2210
+ this.unsetAttributes($tab);
2211
+ });
2212
+ window.removeEventListener('hashchange', this.boundOnHashChange, true);
2213
+ }
2214
+ onHashChange() {
2215
+ const hash = window.location.hash;
2216
+ const $tabWithHash = this.getTab(hash);
2217
+ if (!$tabWithHash) {
2218
+ return;
2219
+ }
2220
+ if (this.changingHash) {
2221
+ this.changingHash = false;
2222
+ return;
2223
+ }
2224
+ const $previousTab = this.getCurrentTab();
2225
+ if (!$previousTab) {
2226
+ return;
2227
+ }
2228
+ this.hideTab($previousTab);
2229
+ this.showTab($tabWithHash);
2230
+ $tabWithHash.focus();
2231
+ }
2232
+ hideTab($tab) {
2233
+ this.unhighlightTab($tab);
2234
+ this.hidePanel($tab);
2235
+ }
2236
+ showTab($tab) {
2237
+ this.highlightTab($tab);
2238
+ this.showPanel($tab);
2239
+ }
2240
+ getTab(hash) {
2241
+ return this.$root.querySelector(`a.govuk-tabs__tab[href="${hash}"]`);
2242
+ }
2243
+ setAttributes($tab) {
2244
+ const panelId = getFragmentFromUrl($tab.href);
2245
+ if (!panelId) {
2246
+ return;
2247
+ }
2248
+ $tab.setAttribute('id', `tab_${panelId}`);
2249
+ $tab.setAttribute('role', 'tab');
2250
+ $tab.setAttribute('aria-controls', panelId);
2251
+ $tab.setAttribute('aria-selected', 'false');
2252
+ $tab.setAttribute('tabindex', '-1');
2253
+ const $panel = this.getPanel($tab);
2254
+ if (!$panel) {
2255
+ return;
2256
+ }
2257
+ $panel.setAttribute('role', 'tabpanel');
2258
+ $panel.setAttribute('aria-labelledby', $tab.id);
2259
+ $panel.classList.add(this.jsHiddenClass);
2260
+ }
2261
+ unsetAttributes($tab) {
2262
+ $tab.removeAttribute('id');
2263
+ $tab.removeAttribute('role');
2264
+ $tab.removeAttribute('aria-controls');
2265
+ $tab.removeAttribute('aria-selected');
2266
+ $tab.removeAttribute('tabindex');
2267
+ const $panel = this.getPanel($tab);
2268
+ if (!$panel) {
2269
+ return;
2270
+ }
2271
+ $panel.removeAttribute('role');
2272
+ $panel.removeAttribute('aria-labelledby');
2273
+ $panel.classList.remove(this.jsHiddenClass);
2274
+ }
2275
+ onTabClick(event) {
2276
+ const $currentTab = this.getCurrentTab();
2277
+ const $nextTab = event.currentTarget;
2278
+ if (!$currentTab || !($nextTab instanceof HTMLAnchorElement)) {
2279
+ return;
2280
+ }
2281
+ event.preventDefault();
2282
+ this.hideTab($currentTab);
2283
+ this.showTab($nextTab);
2284
+ this.createHistoryEntry($nextTab);
2285
+ }
2286
+ createHistoryEntry($tab) {
2287
+ const $panel = this.getPanel($tab);
2288
+ if (!$panel) {
2289
+ return;
2290
+ }
2291
+ const panelId = $panel.id;
2292
+ $panel.id = '';
2293
+ this.changingHash = true;
2294
+ window.location.hash = panelId;
2295
+ $panel.id = panelId;
2296
+ }
2297
+ onTabKeydown(event) {
2298
+ switch (event.key) {
2299
+ case 'ArrowLeft':
2300
+ case 'Left':
2301
+ this.activatePreviousTab();
2302
+ event.preventDefault();
2303
+ break;
2304
+ case 'ArrowRight':
2305
+ case 'Right':
2306
+ this.activateNextTab();
2307
+ event.preventDefault();
2308
+ break;
2309
+ }
2310
+ }
2311
+ activateNextTab() {
2312
+ const $currentTab = this.getCurrentTab();
2313
+ if (!($currentTab != null && $currentTab.parentElement)) {
2314
+ return;
2315
+ }
2316
+ const $nextTabListItem = $currentTab.parentElement.nextElementSibling;
2317
+ if (!$nextTabListItem) {
2318
+ return;
2319
+ }
2320
+ const $nextTab = $nextTabListItem.querySelector('a.govuk-tabs__tab');
2321
+ if (!$nextTab) {
2322
+ return;
2323
+ }
2324
+ this.hideTab($currentTab);
2325
+ this.showTab($nextTab);
2326
+ $nextTab.focus();
2327
+ this.createHistoryEntry($nextTab);
2328
+ }
2329
+ activatePreviousTab() {
2330
+ const $currentTab = this.getCurrentTab();
2331
+ if (!($currentTab != null && $currentTab.parentElement)) {
2332
+ return;
2333
+ }
2334
+ const $previousTabListItem = $currentTab.parentElement.previousElementSibling;
2335
+ if (!$previousTabListItem) {
2336
+ return;
2337
+ }
2338
+ const $previousTab = $previousTabListItem.querySelector('a.govuk-tabs__tab');
2339
+ if (!$previousTab) {
2340
+ return;
2341
+ }
2342
+ this.hideTab($currentTab);
2343
+ this.showTab($previousTab);
2344
+ $previousTab.focus();
2345
+ this.createHistoryEntry($previousTab);
2346
+ }
2347
+ getPanel($tab) {
2348
+ const panelId = getFragmentFromUrl($tab.href);
2349
+ if (!panelId) {
2350
+ return null;
2351
+ }
2352
+ return this.$root.querySelector(`#${panelId}`);
2353
+ }
2354
+ showPanel($tab) {
2355
+ const $panel = this.getPanel($tab);
2356
+ if (!$panel) {
2357
+ return;
2358
+ }
2359
+ $panel.classList.remove(this.jsHiddenClass);
2360
+ }
2361
+ hidePanel($tab) {
2362
+ const $panel = this.getPanel($tab);
2363
+ if (!$panel) {
2364
+ return;
2365
+ }
2366
+ $panel.classList.add(this.jsHiddenClass);
2367
+ }
2368
+ unhighlightTab($tab) {
2369
+ if (!$tab.parentElement) {
2370
+ return;
2371
+ }
2372
+ $tab.setAttribute('aria-selected', 'false');
2373
+ $tab.parentElement.classList.remove('govuk-tabs__list-item--selected');
2374
+ $tab.setAttribute('tabindex', '-1');
2375
+ }
2376
+ highlightTab($tab) {
2377
+ if (!$tab.parentElement) {
2378
+ return;
2379
+ }
2380
+ $tab.setAttribute('aria-selected', 'true');
2381
+ $tab.parentElement.classList.add('govuk-tabs__list-item--selected');
2382
+ $tab.setAttribute('tabindex', '0');
2383
+ }
2384
+ getCurrentTab() {
2385
+ return this.$root.querySelector('.govuk-tabs__list-item--selected a.govuk-tabs__tab');
2386
+ }
2387
+ }
2388
+ Tabs.moduleName = 'govuk-tabs';
2389
+
2390
+ /**
2391
+ * Initialise all components
2392
+ *
2393
+ * Use the `data-module` attributes to find, instantiate and init all of the
2394
+ * components provided as part of GOV.UK Frontend.
2395
+ *
2396
+ * @param {Config & { scope?: Element, onError?: OnErrorCallback<CompatibleClass> }} [config] - Config for all components (with optional scope)
2397
+ */
2398
+ function initAll(config) {
2399
+ var _config$scope;
2400
+ config = typeof config !== 'undefined' ? config : {};
2401
+ if (!isSupported()) {
2402
+ if (config.onError) {
2403
+ config.onError(new SupportError(), {
2404
+ config
2405
+ });
2406
+ } else {
2407
+ console.log(new SupportError());
2408
+ }
2409
+ return;
2410
+ }
2411
+ const components = [[Accordion, config.accordion], [Button, config.button], [CharacterCount, config.characterCount], [Checkboxes], [ErrorSummary, config.errorSummary], [ExitThisPage, config.exitThisPage], [Header], [NotificationBanner, config.notificationBanner], [PasswordInput, config.passwordInput], [Radios], [ServiceNavigation], [SkipLink], [Tabs]];
2412
+ const options = {
2413
+ scope: (_config$scope = config.scope) != null ? _config$scope : document,
2414
+ onError: config.onError
2415
+ };
2416
+ components.forEach(([Component, config]) => {
2417
+ createAll(Component, config, options);
2418
+ });
2419
+ }
2420
+
2421
+ /**
2422
+ * Create all instances of a specific component on the page
2423
+ *
2424
+ * Uses the `data-module` attribute to find all elements matching the specified
2425
+ * component on the page, creating instances of the component object for each
2426
+ * of them.
2427
+ *
2428
+ * Any component errors will be caught and logged to the console.
2429
+ *
2430
+ * @template {CompatibleClass} T
2431
+ * @param {T} Component - class of the component to create
2432
+ * @param {T["defaults"]} [config] - Config supplied to component
2433
+ * @param {OnErrorCallback<T> | Element | Document | CreateAllOptions<T> } [createAllOptions] - options for createAll including scope of the document to search within and callback function if error throw by component on init
2434
+ * @returns {Array<InstanceType<T>>} - array of instantiated components
2435
+ */
2436
+ function createAll(Component, config, createAllOptions) {
2437
+ let $scope = document;
2438
+ let onError;
2439
+ if (typeof createAllOptions === 'object') {
2440
+ var _createAllOptions$sco;
2441
+ createAllOptions = createAllOptions;
2442
+ $scope = (_createAllOptions$sco = createAllOptions.scope) != null ? _createAllOptions$sco : $scope;
2443
+ onError = createAllOptions.onError;
2444
+ }
2445
+ if (typeof createAllOptions === 'function') {
2446
+ onError = createAllOptions;
2447
+ }
2448
+ if (createAllOptions instanceof HTMLElement) {
2449
+ $scope = createAllOptions;
2450
+ }
2451
+ const $elements = $scope.querySelectorAll(`[data-module="${Component.moduleName}"]`);
2452
+ if (!isSupported()) {
2453
+ if (onError) {
2454
+ onError(new SupportError(), {
2455
+ component: Component,
2456
+ config
2457
+ });
2458
+ } else {
2459
+ console.log(new SupportError());
2460
+ }
2461
+ return [];
2462
+ }
2463
+ return Array.from($elements).map($element => {
2464
+ try {
2465
+ return typeof config !== 'undefined' ? new Component($element, config) : new Component($element);
2466
+ } catch (error) {
2467
+ if (onError) {
2468
+ onError(error, {
2469
+ element: $element,
2470
+ component: Component,
2471
+ config
2472
+ });
2473
+ } else {
2474
+ console.log(error);
2475
+ }
2476
+ return null;
2477
+ }
2478
+ }).filter(Boolean);
2479
+ }
2480
+ /**
2481
+ * @typedef {{new (...args: any[]): any, defaults?: object, moduleName: string}} CompatibleClass
2482
+ */
2483
+ /**
2484
+ * Config for all components via `initAll()`
2485
+ *
2486
+ * @typedef {object} Config
2487
+ * @property {AccordionConfig} [accordion] - Accordion config
2488
+ * @property {ButtonConfig} [button] - Button config
2489
+ * @property {CharacterCountConfig} [characterCount] - Character Count config
2490
+ * @property {ErrorSummaryConfig} [errorSummary] - Error Summary config
2491
+ * @property {ExitThisPageConfig} [exitThisPage] - Exit This Page config
2492
+ * @property {NotificationBannerConfig} [notificationBanner] - Notification Banner config
2493
+ * @property {PasswordInputConfig} [passwordInput] - Password input config
2494
+ */
2495
+ /**
2496
+ * Config for individual components
2497
+ *
2498
+ * @typedef {import('./components/accordion/accordion.mjs').AccordionConfig} AccordionConfig
2499
+ * @typedef {import('./components/accordion/accordion.mjs').AccordionTranslations} AccordionTranslations
2500
+ * @typedef {import('./components/button/button.mjs').ButtonConfig} ButtonConfig
2501
+ * @typedef {import('./components/character-count/character-count.mjs').CharacterCountConfig} CharacterCountConfig
2502
+ * @typedef {import('./components/character-count/character-count.mjs').CharacterCountTranslations} CharacterCountTranslations
2503
+ * @typedef {import('./components/error-summary/error-summary.mjs').ErrorSummaryConfig} ErrorSummaryConfig
2504
+ * @typedef {import('./components/exit-this-page/exit-this-page.mjs').ExitThisPageConfig} ExitThisPageConfig
2505
+ * @typedef {import('./components/exit-this-page/exit-this-page.mjs').ExitThisPageTranslations} ExitThisPageTranslations
2506
+ * @typedef {import('./components/notification-banner/notification-banner.mjs').NotificationBannerConfig} NotificationBannerConfig
2507
+ * @typedef {import('./components/password-input/password-input.mjs').PasswordInputConfig} PasswordInputConfig
2508
+ */
2509
+ /**
2510
+ * Component config keys, e.g. `accordion` and `characterCount`
2511
+ *
2512
+ * @typedef {keyof Config} ConfigKey
2513
+ */
2514
+ /**
2515
+ * @template {CompatibleClass} T
2516
+ * @typedef {object} ErrorContext
2517
+ * @property {Element} [element] - Element used for component module initialisation
2518
+ * @property {T} [component] - Class of component
2519
+ * @property {T["defaults"]} config - Config supplied to component
2520
+ */
2521
+ /**
2522
+ * @template {CompatibleClass} T
2523
+ * @callback OnErrorCallback
2524
+ * @param {unknown} error - Thrown error
2525
+ * @param {ErrorContext<T>} context - Object containing the element, component class and configuration
2526
+ */
2527
+ /**
2528
+ * @template {CompatibleClass} T
2529
+ * @typedef {object} CreateAllOptions
2530
+ * @property {Element | Document} [scope] - scope of the document to search within
2531
+ * @property {OnErrorCallback<T>} [onError] - callback function if error throw by component on init
2532
+ */
2533
+
2534
+ exports.Accordion = Accordion;
2535
+ exports.Button = Button;
2536
+ exports.CharacterCount = CharacterCount;
2537
+ exports.Checkboxes = Checkboxes;
2538
+ exports.Component = GOVUKFrontendComponent;
2539
+ exports.ErrorSummary = ErrorSummary;
2540
+ exports.ExitThisPage = ExitThisPage;
2541
+ exports.Header = Header;
2542
+ exports.NotificationBanner = NotificationBanner;
2543
+ exports.PasswordInput = PasswordInput;
2544
+ exports.Radios = Radios;
2545
+ exports.ServiceNavigation = ServiceNavigation;
2546
+ exports.SkipLink = SkipLink;
2547
+ exports.Tabs = Tabs;
2548
+ exports.createAll = createAll;
2549
+ exports.initAll = initAll;
2550
+ exports.isSupported = isSupported;
2551
+ exports.version = version;
2552
+
2553
+ }));
2554
+ //# sourceMappingURL=all.bundle.js.map