govuk_tech_docs 3.5.0 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
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 +21 -2
  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