defra_ruby_template 5.4.0 → 5.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (428) hide show
  1. checksums.yaml +4 -4
  2. data/app/views/layouts/defra_ruby_template.html.erb +43 -23
  3. data/lib/defra_ruby_template/version.rb +1 -1
  4. data/lib/defra_ruby_template.rb +3 -8
  5. data/node_modules/govuk-frontend/dist/govuk/all.bundle.js +882 -492
  6. data/node_modules/govuk-frontend/dist/govuk/all.bundle.js.map +1 -1
  7. data/node_modules/govuk-frontend/dist/govuk/all.bundle.mjs +877 -492
  8. data/node_modules/govuk-frontend/dist/govuk/all.bundle.mjs.map +1 -1
  9. data/node_modules/govuk-frontend/dist/govuk/all.mjs +5 -0
  10. data/node_modules/govuk-frontend/dist/govuk/all.mjs.map +1 -1
  11. data/node_modules/govuk-frontend/dist/govuk/all.scss +6 -0
  12. data/node_modules/govuk-frontend/dist/govuk/all.scss.map +1 -1
  13. data/node_modules/govuk-frontend/dist/govuk/assets/images/govuk-crest.svg +1 -0
  14. data/node_modules/govuk-frontend/dist/govuk/assets/rebrand/images/favicon.ico +0 -0
  15. data/node_modules/govuk-frontend/dist/govuk/assets/rebrand/images/favicon.svg +1 -0
  16. data/node_modules/govuk-frontend/dist/govuk/assets/rebrand/images/govuk-crest.svg +1 -0
  17. data/node_modules/govuk-frontend/dist/govuk/assets/rebrand/images/govuk-icon-180.png +0 -0
  18. data/node_modules/govuk-frontend/dist/govuk/assets/rebrand/images/govuk-icon-192.png +0 -0
  19. data/node_modules/govuk-frontend/dist/govuk/assets/rebrand/images/govuk-icon-512.png +0 -0
  20. data/node_modules/govuk-frontend/dist/govuk/assets/rebrand/images/govuk-icon-mask.svg +1 -0
  21. data/node_modules/govuk-frontend/dist/govuk/assets/rebrand/images/govuk-opengraph-image.png +0 -0
  22. data/node_modules/govuk-frontend/dist/govuk/assets/rebrand/manifest.json +39 -0
  23. data/node_modules/govuk-frontend/dist/govuk/common/closest-attribute-value.mjs.map +1 -1
  24. data/node_modules/govuk-frontend/dist/govuk/common/configuration.mjs +169 -0
  25. data/node_modules/govuk-frontend/dist/govuk/common/configuration.mjs.map +1 -0
  26. data/node_modules/govuk-frontend/dist/govuk/common/govuk-frontend-version.mjs +1 -1
  27. data/node_modules/govuk-frontend/dist/govuk/common/index.mjs +20 -83
  28. data/node_modules/govuk-frontend/dist/govuk/common/index.mjs.map +1 -1
  29. data/node_modules/govuk-frontend/dist/govuk/component.mjs +60 -0
  30. data/node_modules/govuk-frontend/dist/govuk/component.mjs.map +1 -0
  31. data/node_modules/govuk-frontend/dist/govuk/components/_index.scss +1 -0
  32. data/node_modules/govuk-frontend/dist/govuk/components/_index.scss.map +1 -1
  33. data/node_modules/govuk-frontend/dist/govuk/components/accordion/_index.scss +35 -31
  34. data/node_modules/govuk-frontend/dist/govuk/components/accordion/_index.scss.map +1 -1
  35. data/node_modules/govuk-frontend/dist/govuk/components/accordion/accordion.bundle.js +296 -195
  36. data/node_modules/govuk-frontend/dist/govuk/components/accordion/accordion.bundle.js.map +1 -1
  37. data/node_modules/govuk-frontend/dist/govuk/components/accordion/accordion.bundle.mjs +295 -194
  38. data/node_modules/govuk-frontend/dist/govuk/components/accordion/accordion.bundle.mjs.map +1 -1
  39. data/node_modules/govuk-frontend/dist/govuk/components/accordion/accordion.mjs +88 -110
  40. data/node_modules/govuk-frontend/dist/govuk/components/accordion/accordion.mjs.map +1 -1
  41. data/node_modules/govuk-frontend/dist/govuk/components/accordion/fixtures.json +16 -3
  42. data/node_modules/govuk-frontend/dist/govuk/components/back-link/fixtures.json +25 -15
  43. data/node_modules/govuk-frontend/dist/govuk/components/back-link/template-with-custom-link.html +1 -0
  44. data/node_modules/govuk-frontend/dist/govuk/components/back-link/template.njk +2 -2
  45. data/node_modules/govuk-frontend/dist/govuk/components/breadcrumbs/fixtures.json +53 -15
  46. data/node_modules/govuk-frontend/dist/govuk/components/breadcrumbs/macro-options.json +6 -0
  47. data/node_modules/govuk-frontend/dist/govuk/components/breadcrumbs/template-default.html +2 -2
  48. data/node_modules/govuk-frontend/dist/govuk/components/breadcrumbs/template-inverse.html +2 -2
  49. data/node_modules/govuk-frontend/dist/govuk/components/breadcrumbs/template-with-collapse-on-mobile.html +2 -2
  50. data/node_modules/govuk-frontend/dist/govuk/components/breadcrumbs/template-with-last-breadcrumb-as-current-page.html +2 -2
  51. data/node_modules/govuk-frontend/dist/govuk/components/breadcrumbs/template-with-multiple-levels.html +2 -2
  52. data/node_modules/govuk-frontend/dist/govuk/components/breadcrumbs/template-with-one-level.html +2 -2
  53. data/node_modules/govuk-frontend/dist/govuk/components/breadcrumbs/template-without-the-home-section.html +2 -2
  54. data/node_modules/govuk-frontend/dist/govuk/components/breadcrumbs/template.njk +2 -2
  55. data/node_modules/govuk-frontend/dist/govuk/components/button/button.bundle.js +217 -106
  56. data/node_modules/govuk-frontend/dist/govuk/components/button/button.bundle.js.map +1 -1
  57. data/node_modules/govuk-frontend/dist/govuk/components/button/button.bundle.mjs +216 -105
  58. data/node_modules/govuk-frontend/dist/govuk/components/button/button.bundle.mjs.map +1 -1
  59. data/node_modules/govuk-frontend/dist/govuk/components/button/button.mjs +9 -22
  60. data/node_modules/govuk-frontend/dist/govuk/components/button/button.mjs.map +1 -1
  61. data/node_modules/govuk-frontend/dist/govuk/components/button/fixtures.json +38 -0
  62. data/node_modules/govuk-frontend/dist/govuk/components/button/macro-options.json +2 -1
  63. data/node_modules/govuk-frontend/dist/govuk/components/character-count/_index.scss +12 -4
  64. data/node_modules/govuk-frontend/dist/govuk/components/character-count/_index.scss.map +1 -1
  65. data/node_modules/govuk-frontend/dist/govuk/components/character-count/character-count.bundle.js +256 -145
  66. data/node_modules/govuk-frontend/dist/govuk/components/character-count/character-count.bundle.js.map +1 -1
  67. data/node_modules/govuk-frontend/dist/govuk/components/character-count/character-count.bundle.mjs +255 -144
  68. data/node_modules/govuk-frontend/dist/govuk/components/character-count/character-count.bundle.mjs.map +1 -1
  69. data/node_modules/govuk-frontend/dist/govuk/components/character-count/character-count.mjs +29 -34
  70. data/node_modules/govuk-frontend/dist/govuk/components/character-count/character-count.mjs.map +1 -1
  71. data/node_modules/govuk-frontend/dist/govuk/components/character-count/fixtures.json +80 -1
  72. data/node_modules/govuk-frontend/dist/govuk/components/character-count/macro-options.json +6 -6
  73. data/node_modules/govuk-frontend/dist/govuk/components/character-count/template-with-error.html +12 -0
  74. data/node_modules/govuk-frontend/dist/govuk/components/character-count/template-with-hint-and-error.html +15 -0
  75. data/node_modules/govuk-frontend/dist/govuk/components/character-count/template.njk +5 -4
  76. data/node_modules/govuk-frontend/dist/govuk/components/checkboxes/_index.scss +6 -1
  77. data/node_modules/govuk-frontend/dist/govuk/components/checkboxes/_index.scss.map +1 -1
  78. data/node_modules/govuk-frontend/dist/govuk/components/checkboxes/checkboxes.bundle.js +96 -50
  79. data/node_modules/govuk-frontend/dist/govuk/components/checkboxes/checkboxes.bundle.js.map +1 -1
  80. data/node_modules/govuk-frontend/dist/govuk/components/checkboxes/checkboxes.bundle.mjs +95 -49
  81. data/node_modules/govuk-frontend/dist/govuk/components/checkboxes/checkboxes.bundle.mjs.map +1 -1
  82. data/node_modules/govuk-frontend/dist/govuk/components/checkboxes/checkboxes.mjs +9 -18
  83. data/node_modules/govuk-frontend/dist/govuk/components/checkboxes/checkboxes.mjs.map +1 -1
  84. data/node_modules/govuk-frontend/dist/govuk/components/checkboxes/fixtures.json +88 -1
  85. data/node_modules/govuk-frontend/dist/govuk/components/checkboxes/template-small-with-divider-and-none.html +34 -0
  86. data/node_modules/govuk-frontend/dist/govuk/components/cookie-banner/_index.scss +23 -14
  87. data/node_modules/govuk-frontend/dist/govuk/components/cookie-banner/_index.scss.map +1 -1
  88. data/node_modules/govuk-frontend/dist/govuk/components/cookie-banner/fixtures.json +23 -0
  89. data/node_modules/govuk-frontend/dist/govuk/components/date-input/_index.scss +2 -2
  90. data/node_modules/govuk-frontend/dist/govuk/components/date-input/_index.scss.map +1 -1
  91. data/node_modules/govuk-frontend/dist/govuk/components/date-input/fixtures.json +27 -0
  92. data/node_modules/govuk-frontend/dist/govuk/components/date-input/macro-options.json +2 -2
  93. data/node_modules/govuk-frontend/dist/govuk/components/details/_index.scss +7 -2
  94. data/node_modules/govuk-frontend/dist/govuk/components/details/_index.scss.map +1 -1
  95. data/node_modules/govuk-frontend/dist/govuk/components/details/fixtures.json +10 -0
  96. data/node_modules/govuk-frontend/dist/govuk/components/error-message/fixtures.json +9 -0
  97. data/node_modules/govuk-frontend/dist/govuk/components/error-summary/_index.scss +4 -0
  98. data/node_modules/govuk-frontend/dist/govuk/components/error-summary/_index.scss.map +1 -1
  99. data/node_modules/govuk-frontend/dist/govuk/components/error-summary/error-summary.bundle.js +238 -127
  100. data/node_modules/govuk-frontend/dist/govuk/components/error-summary/error-summary.bundle.js.map +1 -1
  101. data/node_modules/govuk-frontend/dist/govuk/components/error-summary/error-summary.bundle.mjs +237 -126
  102. data/node_modules/govuk-frontend/dist/govuk/components/error-summary/error-summary.bundle.mjs.map +1 -1
  103. data/node_modules/govuk-frontend/dist/govuk/components/error-summary/error-summary.mjs +10 -22
  104. data/node_modules/govuk-frontend/dist/govuk/components/error-summary/error-summary.mjs.map +1 -1
  105. data/node_modules/govuk-frontend/dist/govuk/components/error-summary/fixtures.json +19 -0
  106. data/node_modules/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.bundle.js +218 -107
  107. data/node_modules/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.bundle.js.map +1 -1
  108. data/node_modules/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.bundle.mjs +217 -106
  109. data/node_modules/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.bundle.mjs.map +1 -1
  110. data/node_modules/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.mjs +10 -22
  111. data/node_modules/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.mjs.map +1 -1
  112. data/node_modules/govuk-frontend/dist/govuk/components/exit-this-page/fixtures.json +4 -0
  113. data/node_modules/govuk-frontend/dist/govuk/components/fieldset/_index.scss +3 -5
  114. data/node_modules/govuk-frontend/dist/govuk/components/fieldset/_index.scss.map +1 -1
  115. data/node_modules/govuk-frontend/dist/govuk/components/fieldset/fixtures.json +18 -0
  116. data/node_modules/govuk-frontend/dist/govuk/components/file-upload/_index.scss +175 -9
  117. data/node_modules/govuk-frontend/dist/govuk/components/file-upload/_index.scss.map +1 -1
  118. data/node_modules/govuk-frontend/dist/govuk/components/file-upload/file-upload.bundle.js +744 -0
  119. data/node_modules/govuk-frontend/dist/govuk/components/file-upload/file-upload.bundle.js.map +1 -0
  120. data/node_modules/govuk-frontend/dist/govuk/components/file-upload/file-upload.bundle.mjs +736 -0
  121. data/node_modules/govuk-frontend/dist/govuk/components/file-upload/file-upload.bundle.mjs.map +1 -0
  122. data/node_modules/govuk-frontend/dist/govuk/components/file-upload/file-upload.mjs +257 -0
  123. data/node_modules/govuk-frontend/dist/govuk/components/file-upload/file-upload.mjs.map +1 -0
  124. data/node_modules/govuk-frontend/dist/govuk/components/file-upload/fixtures.json +220 -16
  125. data/node_modules/govuk-frontend/dist/govuk/components/file-upload/macro-options.json +52 -3
  126. data/node_modules/govuk-frontend/dist/govuk/components/file-upload/template-allows-direct-media-capture.html +6 -0
  127. data/node_modules/govuk-frontend/dist/govuk/components/file-upload/template-allows-image-files-only.html +6 -0
  128. data/node_modules/govuk-frontend/dist/govuk/components/file-upload/template-allows-multiple-files.html +6 -0
  129. data/node_modules/govuk-frontend/dist/govuk/components/file-upload/template-disabled.html +6 -0
  130. data/node_modules/govuk-frontend/dist/govuk/components/file-upload/template-enhanced-disabled.html +13 -0
  131. data/node_modules/govuk-frontend/dist/govuk/components/file-upload/template-enhanced-with-error-message-and-hint.html +16 -0
  132. data/node_modules/govuk-frontend/dist/govuk/components/file-upload/template-enhanced.html +10 -0
  133. data/node_modules/govuk-frontend/dist/govuk/components/file-upload/template-translated.html +10 -0
  134. data/node_modules/govuk-frontend/dist/govuk/components/file-upload/template.njk +42 -5
  135. data/node_modules/govuk-frontend/dist/govuk/components/footer/_index.scss +61 -19
  136. data/node_modules/govuk-frontend/dist/govuk/components/footer/_index.scss.map +1 -1
  137. data/node_modules/govuk-frontend/dist/govuk/components/footer/fixtures.json +34 -0
  138. data/node_modules/govuk-frontend/dist/govuk/components/footer/macro-options.json +6 -0
  139. data/node_modules/govuk-frontend/dist/govuk/components/footer/template.njk +10 -0
  140. data/node_modules/govuk-frontend/dist/govuk/components/header/_index.scss +223 -21
  141. data/node_modules/govuk-frontend/dist/govuk/components/header/_index.scss.map +1 -1
  142. data/node_modules/govuk-frontend/dist/govuk/components/header/fixtures.json +78 -32
  143. data/node_modules/govuk-frontend/dist/govuk/components/header/header.bundle.js +90 -43
  144. data/node_modules/govuk-frontend/dist/govuk/components/header/header.bundle.js.map +1 -1
  145. data/node_modules/govuk-frontend/dist/govuk/components/header/header.bundle.mjs +89 -42
  146. data/node_modules/govuk-frontend/dist/govuk/components/header/header.bundle.mjs.map +1 -1
  147. data/node_modules/govuk-frontend/dist/govuk/components/header/header.mjs +10 -18
  148. data/node_modules/govuk-frontend/dist/govuk/components/header/header.mjs.map +1 -1
  149. data/node_modules/govuk-frontend/dist/govuk/components/header/macro-options.json +31 -12
  150. data/node_modules/govuk-frontend/dist/govuk/components/header/template-default.html +20 -12
  151. data/node_modules/govuk-frontend/dist/govuk/components/header/template-full-width-with-navigation.html +21 -15
  152. data/node_modules/govuk-frontend/dist/govuk/components/header/template-full-width.html +21 -15
  153. data/node_modules/govuk-frontend/dist/govuk/components/header/template-navigation-item-with-html.html +20 -12
  154. data/node_modules/govuk-frontend/dist/govuk/components/header/template-navigation-item-with-text-without-link.html +20 -12
  155. data/node_modules/govuk-frontend/dist/govuk/components/header/template-with-custom-menu-button-label.html +20 -12
  156. data/node_modules/govuk-frontend/dist/govuk/components/header/template-with-custom-menu-button-text.html +20 -12
  157. data/node_modules/govuk-frontend/dist/govuk/components/header/template-with-custom-navigation-label.html +20 -12
  158. data/node_modules/govuk-frontend/dist/govuk/components/header/template-with-full-width-border.html +30 -0
  159. data/node_modules/govuk-frontend/dist/govuk/components/header/template-with-large-navigation.html +20 -12
  160. data/node_modules/govuk-frontend/dist/govuk/components/header/template-with-navigation.html +20 -12
  161. data/node_modules/govuk-frontend/dist/govuk/components/header/template-with-product-name.html +21 -15
  162. data/node_modules/govuk-frontend/dist/govuk/components/header/template-with-service-name-and-navigation.html +20 -12
  163. data/node_modules/govuk-frontend/dist/govuk/components/header/template-with-service-name-but-no-service-url.html +20 -12
  164. data/node_modules/govuk-frontend/dist/govuk/components/header/template-with-service-name.html +20 -12
  165. data/node_modules/govuk-frontend/dist/govuk/components/header/template-with-st-edwards-crown.html +10 -12
  166. data/node_modules/govuk-frontend/dist/govuk/components/header/template.njk +11 -41
  167. data/node_modules/govuk-frontend/dist/govuk/components/hint/fixtures.json +6 -0
  168. data/node_modules/govuk-frontend/dist/govuk/components/input/_index.scss +7 -4
  169. data/node_modules/govuk-frontend/dist/govuk/components/input/_index.scss.map +1 -1
  170. data/node_modules/govuk-frontend/dist/govuk/components/input/fixtures.json +97 -27
  171. data/node_modules/govuk-frontend/dist/govuk/components/input/macro-options.json +6 -6
  172. data/node_modules/govuk-frontend/dist/govuk/components/input/template-default.html +2 -2
  173. data/node_modules/govuk-frontend/dist/govuk/components/input/template-disabled.html +6 -0
  174. data/node_modules/govuk-frontend/dist/govuk/components/input/template-with-error-and-hint.html +12 -0
  175. data/node_modules/govuk-frontend/dist/govuk/components/input/template-with-error-message.html +2 -5
  176. data/node_modules/govuk-frontend/dist/govuk/components/input/template.njk +5 -4
  177. data/node_modules/govuk-frontend/dist/govuk/components/inset-text/fixtures.json +6 -0
  178. data/node_modules/govuk-frontend/dist/govuk/components/label/fixtures.json +17 -0
  179. data/node_modules/govuk-frontend/dist/govuk/components/notification-banner/_index.scss +3 -2
  180. data/node_modules/govuk-frontend/dist/govuk/components/notification-banner/_index.scss.map +1 -1
  181. data/node_modules/govuk-frontend/dist/govuk/components/notification-banner/fixtures.json +24 -0
  182. data/node_modules/govuk-frontend/dist/govuk/components/notification-banner/notification-banner.bundle.js +238 -127
  183. data/node_modules/govuk-frontend/dist/govuk/components/notification-banner/notification-banner.bundle.js.map +1 -1
  184. data/node_modules/govuk-frontend/dist/govuk/components/notification-banner/notification-banner.bundle.mjs +237 -126
  185. data/node_modules/govuk-frontend/dist/govuk/components/notification-banner/notification-banner.bundle.mjs.map +1 -1
  186. data/node_modules/govuk-frontend/dist/govuk/components/notification-banner/notification-banner.mjs +10 -22
  187. data/node_modules/govuk-frontend/dist/govuk/components/notification-banner/notification-banner.mjs.map +1 -1
  188. data/node_modules/govuk-frontend/dist/govuk/components/pagination/_index.scss +24 -37
  189. data/node_modules/govuk-frontend/dist/govuk/components/pagination/_index.scss.map +1 -1
  190. data/node_modules/govuk-frontend/dist/govuk/components/pagination/fixtures.json +15 -0
  191. data/node_modules/govuk-frontend/dist/govuk/components/pagination/macro-options.json +4 -4
  192. data/node_modules/govuk-frontend/dist/govuk/components/panel/_index.scss +13 -9
  193. data/node_modules/govuk-frontend/dist/govuk/components/panel/_index.scss.map +1 -1
  194. data/node_modules/govuk-frontend/dist/govuk/components/panel/fixtures.json +9 -0
  195. data/node_modules/govuk-frontend/dist/govuk/components/password-input/_index.scss +12 -9
  196. data/node_modules/govuk-frontend/dist/govuk/components/password-input/_index.scss.map +1 -1
  197. data/node_modules/govuk-frontend/dist/govuk/components/password-input/fixtures.json +27 -2
  198. data/node_modules/govuk-frontend/dist/govuk/components/password-input/macro-options.json +3 -3
  199. data/node_modules/govuk-frontend/dist/govuk/components/password-input/password-input.bundle.js +220 -110
  200. data/node_modules/govuk-frontend/dist/govuk/components/password-input/password-input.bundle.js.map +1 -1
  201. data/node_modules/govuk-frontend/dist/govuk/components/password-input/password-input.bundle.mjs +219 -109
  202. data/node_modules/govuk-frontend/dist/govuk/components/password-input/password-input.bundle.mjs.map +1 -1
  203. data/node_modules/govuk-frontend/dist/govuk/components/password-input/password-input.mjs +12 -25
  204. data/node_modules/govuk-frontend/dist/govuk/components/password-input/password-input.mjs.map +1 -1
  205. data/node_modules/govuk-frontend/dist/govuk/components/password-input/template-default.html +3 -3
  206. data/node_modules/govuk-frontend/dist/govuk/components/password-input/template.njk +4 -2
  207. data/node_modules/govuk-frontend/dist/govuk/components/phase-banner/_index.scss +0 -8
  208. data/node_modules/govuk-frontend/dist/govuk/components/phase-banner/_index.scss.map +1 -1
  209. data/node_modules/govuk-frontend/dist/govuk/components/phase-banner/fixtures.json +7 -0
  210. data/node_modules/govuk-frontend/dist/govuk/components/radios/_index.scss +7 -5
  211. data/node_modules/govuk-frontend/dist/govuk/components/radios/_index.scss.map +1 -1
  212. data/node_modules/govuk-frontend/dist/govuk/components/radios/fixtures.json +51 -5
  213. data/node_modules/govuk-frontend/dist/govuk/components/radios/radios.bundle.js +96 -50
  214. data/node_modules/govuk-frontend/dist/govuk/components/radios/radios.bundle.js.map +1 -1
  215. data/node_modules/govuk-frontend/dist/govuk/components/radios/radios.bundle.mjs +95 -49
  216. data/node_modules/govuk-frontend/dist/govuk/components/radios/radios.bundle.mjs.map +1 -1
  217. data/node_modules/govuk-frontend/dist/govuk/components/radios/radios.mjs +9 -18
  218. data/node_modules/govuk-frontend/dist/govuk/components/radios/radios.mjs.map +1 -1
  219. data/node_modules/govuk-frontend/dist/govuk/components/radios/template-small-with-a-divider.html +1 -1
  220. data/node_modules/govuk-frontend/dist/govuk/components/radios/template-with-a-divider.html +1 -1
  221. data/node_modules/govuk-frontend/dist/govuk/components/select/_index.scss +5 -5
  222. data/node_modules/govuk-frontend/dist/govuk/components/select/_index.scss.map +1 -1
  223. data/node_modules/govuk-frontend/dist/govuk/components/select/fixtures.json +43 -9
  224. data/node_modules/govuk-frontend/dist/govuk/components/select/macro-options.json +2 -2
  225. data/node_modules/govuk-frontend/dist/govuk/components/select/template-id.html +7 -0
  226. data/node_modules/govuk-frontend/dist/govuk/components/select/template.njk +6 -4
  227. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/README.md +15 -0
  228. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/_index.scss +187 -0
  229. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/_index.scss.map +1 -0
  230. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/_service-navigation.scss +4 -0
  231. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/_service-navigation.scss.map +1 -0
  232. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/fixtures.json +515 -0
  233. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/macro-options.json +138 -0
  234. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/macro.njk +3 -0
  235. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/service-navigation.bundle.js +229 -0
  236. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/service-navigation.bundle.js.map +1 -0
  237. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/service-navigation.bundle.mjs +221 -0
  238. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/service-navigation.bundle.mjs.map +1 -0
  239. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/service-navigation.mjs +85 -0
  240. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/service-navigation.mjs.map +1 -0
  241. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/template-default.html +57 -0
  242. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/template-with-html-navigation-items.html +49 -0
  243. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/template-with-large-navigation.html +153 -0
  244. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/template-with-long-service-name.html +20 -0
  245. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/template-with-navigation-with-a-current-item.html +58 -0
  246. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/template-with-navigation-with-an-active-item.html +58 -0
  247. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/template-with-non-link-navigation-items.html +49 -0
  248. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/template-with-service-link.html +20 -0
  249. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/template-with-service-name-and-navigation.html +63 -0
  250. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/template-with-service-name.html +18 -0
  251. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/template.njk +103 -0
  252. data/node_modules/govuk-frontend/dist/govuk/components/skip-link/_index.scss +6 -4
  253. data/node_modules/govuk-frontend/dist/govuk/components/skip-link/_index.scss.map +1 -1
  254. data/node_modules/govuk-frontend/dist/govuk/components/skip-link/fixtures.json +9 -0
  255. data/node_modules/govuk-frontend/dist/govuk/components/skip-link/skip-link.bundle.js +95 -48
  256. data/node_modules/govuk-frontend/dist/govuk/components/skip-link/skip-link.bundle.js.map +1 -1
  257. data/node_modules/govuk-frontend/dist/govuk/components/skip-link/skip-link.bundle.mjs +94 -47
  258. data/node_modules/govuk-frontend/dist/govuk/components/skip-link/skip-link.bundle.mjs.map +1 -1
  259. data/node_modules/govuk-frontend/dist/govuk/components/skip-link/skip-link.mjs +15 -23
  260. data/node_modules/govuk-frontend/dist/govuk/components/skip-link/skip-link.mjs.map +1 -1
  261. data/node_modules/govuk-frontend/dist/govuk/components/summary-list/_index.scss +14 -22
  262. data/node_modules/govuk-frontend/dist/govuk/components/summary-list/_index.scss.map +1 -1
  263. data/node_modules/govuk-frontend/dist/govuk/components/summary-list/fixtures.json +174 -1
  264. data/node_modules/govuk-frontend/dist/govuk/components/summary-list/template-as-a-summary-card-extreme.html +106 -0
  265. data/node_modules/govuk-frontend/dist/govuk/components/table/fixtures.json +22 -1
  266. data/node_modules/govuk-frontend/dist/govuk/components/tabs/fixtures.json +15 -1
  267. data/node_modules/govuk-frontend/dist/govuk/components/tabs/tabs.bundle.js +95 -53
  268. data/node_modules/govuk-frontend/dist/govuk/components/tabs/tabs.bundle.js.map +1 -1
  269. data/node_modules/govuk-frontend/dist/govuk/components/tabs/tabs.bundle.mjs +94 -52
  270. data/node_modules/govuk-frontend/dist/govuk/components/tabs/tabs.bundle.mjs.map +1 -1
  271. data/node_modules/govuk-frontend/dist/govuk/components/tabs/tabs.mjs +15 -28
  272. data/node_modules/govuk-frontend/dist/govuk/components/tabs/tabs.mjs.map +1 -1
  273. data/node_modules/govuk-frontend/dist/govuk/components/tag/_index.scss +11 -8
  274. data/node_modules/govuk-frontend/dist/govuk/components/tag/_index.scss.map +1 -1
  275. data/node_modules/govuk-frontend/dist/govuk/components/tag/fixtures.json +14 -0
  276. data/node_modules/govuk-frontend/dist/govuk/components/task-list/_index.scss +12 -10
  277. data/node_modules/govuk-frontend/dist/govuk/components/task-list/_index.scss.map +1 -1
  278. data/node_modules/govuk-frontend/dist/govuk/components/task-list/fixtures.json +44 -0
  279. data/node_modules/govuk-frontend/dist/govuk/components/task-list/template-with-empty-values.html +27 -0
  280. data/node_modules/govuk-frontend/dist/govuk/components/task-list/template.njk +1 -1
  281. data/node_modules/govuk-frontend/dist/govuk/components/textarea/_index.scss +3 -3
  282. data/node_modules/govuk-frontend/dist/govuk/components/textarea/_index.scss.map +1 -1
  283. data/node_modules/govuk-frontend/dist/govuk/components/textarea/fixtures.json +32 -1
  284. data/node_modules/govuk-frontend/dist/govuk/components/textarea/macro-options.json +3 -3
  285. data/node_modules/govuk-frontend/dist/govuk/components/textarea/template.njk +6 -4
  286. data/node_modules/govuk-frontend/dist/govuk/components/warning-text/_index.scss +6 -5
  287. data/node_modules/govuk-frontend/dist/govuk/components/warning-text/_index.scss.map +1 -1
  288. data/node_modules/govuk-frontend/dist/govuk/components/warning-text/fixtures.json +8 -0
  289. data/node_modules/govuk-frontend/dist/govuk/core/_govuk-frontend-properties.scss +1 -1
  290. data/node_modules/govuk-frontend/dist/govuk/core/_govuk-frontend-properties.scss.map +1 -1
  291. data/node_modules/govuk-frontend/dist/govuk/errors/index.mjs +16 -3
  292. data/node_modules/govuk-frontend/dist/govuk/errors/index.mjs.map +1 -1
  293. data/node_modules/govuk-frontend/dist/govuk/govuk-frontend.min.css +2 -2
  294. data/node_modules/govuk-frontend/dist/govuk/govuk-frontend.min.css.map +1 -1
  295. data/node_modules/govuk-frontend/dist/govuk/govuk-frontend.min.js +1 -1
  296. data/node_modules/govuk-frontend/dist/govuk/govuk-frontend.min.js.map +1 -1
  297. data/node_modules/govuk-frontend/dist/govuk/helpers/_colour.scss +55 -8
  298. data/node_modules/govuk-frontend/dist/govuk/helpers/_colour.scss.map +1 -1
  299. data/node_modules/govuk-frontend/dist/govuk/helpers/_focused.scss +14 -4
  300. data/node_modules/govuk-frontend/dist/govuk/helpers/_focused.scss.map +1 -1
  301. data/node_modules/govuk-frontend/dist/govuk/helpers/_grid.scss +1 -1
  302. data/node_modules/govuk-frontend/dist/govuk/helpers/_grid.scss.map +1 -1
  303. data/node_modules/govuk-frontend/dist/govuk/helpers/_links.scss +2 -2
  304. data/node_modules/govuk-frontend/dist/govuk/helpers/_links.scss.map +1 -1
  305. data/node_modules/govuk-frontend/dist/govuk/helpers/_shape-arrow.scss +1 -1
  306. data/node_modules/govuk-frontend/dist/govuk/helpers/_shape-arrow.scss.map +1 -1
  307. data/node_modules/govuk-frontend/dist/govuk/helpers/_typography.scss +18 -1
  308. data/node_modules/govuk-frontend/dist/govuk/helpers/_typography.scss.map +1 -1
  309. data/node_modules/govuk-frontend/dist/govuk/helpers/_visually-hidden.scss +30 -62
  310. data/node_modules/govuk-frontend/dist/govuk/helpers/_visually-hidden.scss.map +1 -1
  311. data/node_modules/govuk-frontend/dist/govuk/i18n.mjs.map +1 -1
  312. data/node_modules/govuk-frontend/dist/govuk/init.mjs +90 -24
  313. data/node_modules/govuk-frontend/dist/govuk/init.mjs.map +1 -1
  314. data/node_modules/govuk-frontend/dist/govuk/macros/logo.njk +78 -0
  315. data/node_modules/govuk-frontend/dist/govuk/objects/_template.scss +5 -1
  316. data/node_modules/govuk-frontend/dist/govuk/objects/_template.scss.map +1 -1
  317. data/node_modules/govuk-frontend/dist/govuk/overrides/_typography.scss +5 -1
  318. data/node_modules/govuk-frontend/dist/govuk/overrides/_typography.scss.map +1 -1
  319. data/node_modules/govuk-frontend/dist/govuk/settings/_colours-applied.scss +39 -1
  320. data/node_modules/govuk-frontend/dist/govuk/settings/_colours-applied.scss.map +1 -1
  321. data/node_modules/govuk-frontend/dist/govuk/settings/_colours-organisations.scss +244 -10
  322. data/node_modules/govuk-frontend/dist/govuk/settings/_colours-organisations.scss.map +1 -1
  323. data/node_modules/govuk-frontend/dist/govuk/settings/_typography-responsive.scss +5 -10
  324. data/node_modules/govuk-frontend/dist/govuk/settings/_typography-responsive.scss.map +1 -1
  325. data/node_modules/govuk-frontend/dist/govuk/template.njk +20 -9
  326. data/node_modules/govuk-frontend/dist/govuk/tools/_index.scss +1 -0
  327. data/node_modules/govuk-frontend/dist/govuk/tools/_index.scss.map +1 -1
  328. data/node_modules/govuk-frontend/dist/govuk/tools/_rebrand.scss +65 -0
  329. data/node_modules/govuk-frontend/dist/govuk/tools/_rebrand.scss.map +1 -0
  330. data/node_modules/govuk-frontend/dist/govuk-prototype-kit/functions.js +25 -0
  331. data/node_modules/govuk-frontend/dist/govuk-prototype-kit/init.scss +1 -1
  332. data/node_modules/govuk-frontend/dist/govuk-prototype-kit/init.scss.map +1 -1
  333. data/node_modules/govuk-frontend/govuk-prototype-kit.config.json +8 -1
  334. data/node_modules/govuk-frontend/package.json +16 -16
  335. data/spec/spec_helper.rb +98 -0
  336. data/spec/tasks/assets_spec.rb +53 -0
  337. data/vendor/assets/assets/fonts/bold-affa96571d-v2.woff +0 -0
  338. data/vendor/assets/assets/fonts/bold-b542beb274-v2.woff2 +0 -0
  339. data/vendor/assets/assets/fonts/light-94a07e06a1-v2.woff2 +0 -0
  340. data/vendor/assets/assets/fonts/light-f591b13f7d-v2.woff +0 -0
  341. data/vendor/assets/assets/images/favicon.ico +0 -0
  342. data/vendor/assets/assets/images/favicon.svg +1 -0
  343. data/vendor/assets/assets/images/govuk-crest.svg +1 -0
  344. data/vendor/assets/assets/images/govuk-icon-180.png +0 -0
  345. data/vendor/assets/assets/images/govuk-icon-192.png +0 -0
  346. data/vendor/assets/assets/images/govuk-icon-512.png +0 -0
  347. data/vendor/assets/assets/images/govuk-icon-mask.svg +1 -0
  348. data/vendor/assets/assets/images/govuk-opengraph-image.png +0 -0
  349. data/vendor/assets/assets/manifest.json +39 -0
  350. data/vendor/assets/assets/rebrand/images/favicon.ico +0 -0
  351. data/vendor/assets/assets/rebrand/images/favicon.svg +1 -0
  352. data/vendor/assets/assets/rebrand/images/govuk-crest.svg +1 -0
  353. data/vendor/assets/assets/rebrand/images/govuk-icon-180.png +0 -0
  354. data/vendor/assets/assets/rebrand/images/govuk-icon-192.png +0 -0
  355. data/vendor/assets/assets/rebrand/images/govuk-icon-512.png +0 -0
  356. data/vendor/assets/assets/rebrand/images/govuk-icon-mask.svg +1 -0
  357. data/vendor/assets/assets/rebrand/images/govuk-opengraph-image.png +0 -0
  358. data/vendor/assets/assets/rebrand/manifest.json +39 -0
  359. data/vendor/assets/images/govuk-crest.svg +1 -0
  360. data/vendor/assets/javascripts/defra_ruby_template.js +882 -492
  361. data/vendor/assets/stylesheets/all.scss +6 -0
  362. data/vendor/assets/stylesheets/components/_index.scss +1 -0
  363. data/vendor/assets/stylesheets/components/accordion/_index.scss +35 -31
  364. data/vendor/assets/stylesheets/components/character-count/_index.scss +12 -4
  365. data/vendor/assets/stylesheets/components/checkboxes/_index.scss +6 -1
  366. data/vendor/assets/stylesheets/components/cookie-banner/_index.scss +23 -14
  367. data/vendor/assets/stylesheets/components/date-input/_index.scss +2 -2
  368. data/vendor/assets/stylesheets/components/details/_index.scss +7 -2
  369. data/vendor/assets/stylesheets/components/error-summary/_index.scss +4 -0
  370. data/vendor/assets/stylesheets/components/fieldset/_index.scss +3 -5
  371. data/vendor/assets/stylesheets/components/file-upload/_index.scss +175 -9
  372. data/vendor/assets/stylesheets/components/footer/_index.scss +61 -19
  373. data/vendor/assets/stylesheets/components/header/_index.scss +223 -21
  374. data/vendor/assets/stylesheets/components/input/_index.scss +7 -4
  375. data/vendor/assets/stylesheets/components/notification-banner/_index.scss +3 -2
  376. data/vendor/assets/stylesheets/components/pagination/_index.scss +24 -37
  377. data/vendor/assets/stylesheets/components/panel/_index.scss +13 -9
  378. data/vendor/assets/stylesheets/components/password-input/_index.scss +12 -9
  379. data/vendor/assets/stylesheets/components/phase-banner/_index.scss +0 -8
  380. data/vendor/assets/stylesheets/components/radios/_index.scss +7 -5
  381. data/vendor/assets/stylesheets/components/select/_index.scss +5 -5
  382. data/vendor/assets/stylesheets/components/service-navigation/_index.scss +187 -0
  383. data/vendor/assets/stylesheets/components/service-navigation/_service-navigation.scss +4 -0
  384. data/vendor/assets/stylesheets/components/skip-link/_index.scss +6 -4
  385. data/vendor/assets/stylesheets/components/summary-list/_index.scss +14 -22
  386. data/vendor/assets/stylesheets/components/tag/_index.scss +11 -8
  387. data/vendor/assets/stylesheets/components/task-list/_index.scss +12 -10
  388. data/vendor/assets/stylesheets/components/textarea/_index.scss +3 -3
  389. data/vendor/assets/stylesheets/components/warning-text/_index.scss +6 -5
  390. data/vendor/assets/stylesheets/core/_govuk-frontend-properties.scss +1 -1
  391. data/vendor/assets/stylesheets/defra_ruby_template.scss +6 -0
  392. data/vendor/assets/stylesheets/govuk-frontend.min.css +2 -2
  393. data/vendor/assets/stylesheets/helpers/_colour.scss +55 -8
  394. data/vendor/assets/stylesheets/helpers/_focused.scss +14 -4
  395. data/vendor/assets/stylesheets/helpers/_grid.scss +1 -1
  396. data/vendor/assets/stylesheets/helpers/_links.scss +2 -2
  397. data/vendor/assets/stylesheets/helpers/_shape-arrow.scss +1 -1
  398. data/vendor/assets/stylesheets/helpers/_typography.scss +18 -1
  399. data/vendor/assets/stylesheets/helpers/_visually-hidden.scss +30 -62
  400. data/vendor/assets/stylesheets/objects/_template.scss +5 -1
  401. data/vendor/assets/stylesheets/overrides/_typography.scss +5 -1
  402. data/vendor/assets/stylesheets/settings/_colours-applied.scss +39 -1
  403. data/vendor/assets/stylesheets/settings/_colours-organisations.scss +244 -10
  404. data/vendor/assets/stylesheets/settings/_typography-responsive.scss +5 -10
  405. data/vendor/assets/stylesheets/tools/_index.scss +1 -0
  406. data/vendor/assets/stylesheets/tools/_rebrand.scss +65 -0
  407. metadata +100 -24
  408. data/.github/dependabot.yml +0 -14
  409. data/.github/workflows/ci.yml +0 -31
  410. data/.gitignore +0 -8
  411. data/.rspec +0 -1
  412. data/.rubocop.yml +0 -3
  413. data/.ruby-version +0 -1
  414. data/Gemfile +0 -9
  415. data/Gemfile.lock +0 -58
  416. data/defra_ruby_template.gemspec +0 -35
  417. data/node_modules/.package-lock.json +0 -16
  418. data/node_modules/govuk-frontend/dist/govuk/assets/images/govuk-crest-2x.png +0 -0
  419. data/node_modules/govuk-frontend/dist/govuk/assets/images/govuk-crest.png +0 -0
  420. data/node_modules/govuk-frontend/dist/govuk/common/normalise-dataset.mjs +0 -18
  421. data/node_modules/govuk-frontend/dist/govuk/common/normalise-dataset.mjs.map +0 -1
  422. data/node_modules/govuk-frontend/dist/govuk/common/normalise-string.mjs +0 -31
  423. data/node_modules/govuk-frontend/dist/govuk/common/normalise-string.mjs.map +0 -1
  424. data/node_modules/govuk-frontend/dist/govuk/components/file-upload/template-with-value.html +0 -6
  425. data/node_modules/govuk-frontend/dist/govuk/govuk-frontend-component.mjs +0 -16
  426. data/node_modules/govuk-frontend/dist/govuk/govuk-frontend-component.mjs.map +0 -1
  427. data/package-lock.json +0 -24
  428. data/package.json +0 -29
@@ -1,75 +1,5 @@
1
- const version = '5.4.0';
1
+ const version = '5.10.2';
2
2
 
3
- function normaliseString(value, property) {
4
- const trimmedValue = value ? value.trim() : '';
5
- let output;
6
- let outputType = property == null ? void 0 : property.type;
7
- if (!outputType) {
8
- if (['true', 'false'].includes(trimmedValue)) {
9
- outputType = 'boolean';
10
- }
11
- if (trimmedValue.length > 0 && isFinite(Number(trimmedValue))) {
12
- outputType = 'number';
13
- }
14
- }
15
- switch (outputType) {
16
- case 'boolean':
17
- output = trimmedValue === 'true';
18
- break;
19
- case 'number':
20
- output = Number(trimmedValue);
21
- break;
22
- default:
23
- output = value;
24
- }
25
- return output;
26
- }
27
-
28
- /**
29
- * @typedef {import('./index.mjs').SchemaProperty} SchemaProperty
30
- */
31
-
32
- function mergeConfigs(...configObjects) {
33
- const formattedConfigObject = {};
34
- for (const configObject of configObjects) {
35
- for (const key of Object.keys(configObject)) {
36
- const option = formattedConfigObject[key];
37
- const override = configObject[key];
38
- if (isObject(option) && isObject(override)) {
39
- formattedConfigObject[key] = mergeConfigs(option, override);
40
- } else {
41
- formattedConfigObject[key] = override;
42
- }
43
- }
44
- }
45
- return formattedConfigObject;
46
- }
47
- function extractConfigByNamespace(Component, dataset, namespace) {
48
- const property = Component.schema.properties[namespace];
49
- if ((property == null ? void 0 : property.type) !== 'object') {
50
- return;
51
- }
52
- const newObject = {
53
- [namespace]: ({})
54
- };
55
- for (const [key, value] of Object.entries(dataset)) {
56
- let current = newObject;
57
- const keyParts = key.split('.');
58
- for (const [index, name] of keyParts.entries()) {
59
- if (typeof current === 'object') {
60
- if (index < keyParts.length - 1) {
61
- if (!isObject(current[name])) {
62
- current[name] = {};
63
- }
64
- current = current[name];
65
- } else if (key !== namespace) {
66
- current[name] = normaliseString(value);
67
- }
68
- }
69
- }
70
- }
71
- return newObject[namespace];
72
- }
73
3
  function getFragmentFromUrl(url) {
74
4
  if (!url.includes('#')) {
75
5
  return undefined;
@@ -108,75 +38,42 @@ function setFocus($element, options = {}) {
108
38
  (_options$onBeforeFocu = options.onBeforeFocus) == null || _options$onBeforeFocu.call($element);
109
39
  $element.focus();
110
40
  }
41
+ function isInitialised($root, moduleName) {
42
+ return $root instanceof HTMLElement && $root.hasAttribute(`data-${moduleName}-init`);
43
+ }
44
+
45
+ /**
46
+ * Checks if GOV.UK Frontend is supported on this page
47
+ *
48
+ * Some browsers will load and run our JavaScript but GOV.UK Frontend
49
+ * won't be supported.
50
+ *
51
+ * @param {HTMLElement | null} [$scope] - (internal) `<body>` HTML element checked for browser support
52
+ * @returns {boolean} Whether GOV.UK Frontend is supported on this page
53
+ */
111
54
  function isSupported($scope = document.body) {
112
55
  if (!$scope) {
113
56
  return false;
114
57
  }
115
58
  return $scope.classList.contains('govuk-frontend-supported');
116
59
  }
117
- function validateConfig(schema, config) {
118
- const validationErrors = [];
119
- for (const [name, conditions] of Object.entries(schema)) {
120
- const errors = [];
121
- if (Array.isArray(conditions)) {
122
- for (const {
123
- required,
124
- errorMessage
125
- } of conditions) {
126
- if (!required.every(key => !!config[key])) {
127
- errors.push(errorMessage);
128
- }
129
- }
130
- if (name === 'anyOf' && !(conditions.length - errors.length >= 1)) {
131
- validationErrors.push(...errors);
132
- }
133
- }
134
- }
135
- return validationErrors;
136
- }
137
60
  function isArray(option) {
138
61
  return Array.isArray(option);
139
62
  }
140
63
  function isObject(option) {
141
64
  return !!option && typeof option === 'object' && !isArray(option);
142
65
  }
143
-
144
- /**
145
- * Schema for component config
146
- *
147
- * @typedef {object} Schema
148
- * @property {{ [field: string]: SchemaProperty | undefined }} properties - Schema properties
149
- * @property {SchemaCondition[]} [anyOf] - List of schema conditions
150
- */
151
-
66
+ function formatErrorMessage(Component, message) {
67
+ return `${Component.moduleName}: ${message}`;
68
+ }
152
69
  /**
153
- * Schema property for component config
154
- *
155
- * @typedef {object} SchemaProperty
156
- * @property {'string' | 'boolean' | 'number' | 'object'} type - Property type
70
+ * @typedef ComponentWithModuleName
71
+ * @property {string} moduleName - Name of the component
157
72
  */
158
-
159
73
  /**
160
- * Schema condition for component config
161
- *
162
- * @typedef {object} SchemaCondition
163
- * @property {string[]} required - List of required config fields
164
- * @property {string} errorMessage - Error message when required config fields not provided
74
+ * @import { ObjectNested } from './configuration.mjs'
165
75
  */
166
76
 
167
- function normaliseDataset(Component, dataset) {
168
- const out = {};
169
- for (const [field, property] of Object.entries(Component.schema.properties)) {
170
- if (field in dataset) {
171
- out[field] = normaliseString(dataset[field], property);
172
- }
173
- if ((property == null ? void 0 : property.type) === 'object') {
174
- out[field] = extractConfigByNamespace(Component, dataset, field);
175
- }
176
- }
177
- return out;
178
- }
179
-
180
77
  class GOVUKFrontendError extends Error {
181
78
  constructor(...args) {
182
79
  super(...args);
@@ -206,30 +103,248 @@ class ElementError extends GOVUKFrontendError {
206
103
  let message = typeof messageOrOptions === 'string' ? messageOrOptions : '';
207
104
  if (typeof messageOrOptions === 'object') {
208
105
  const {
209
- componentName,
106
+ component,
210
107
  identifier,
211
108
  element,
212
109
  expectedType
213
110
  } = messageOrOptions;
214
- message = `${componentName}: ${identifier}`;
111
+ message = identifier;
215
112
  message += element ? ` is not of type ${expectedType != null ? expectedType : 'HTMLElement'}` : ' not found';
113
+ message = formatErrorMessage(component, message);
216
114
  }
217
115
  super(message);
218
116
  this.name = 'ElementError';
219
117
  }
220
118
  }
119
+ class InitError extends GOVUKFrontendError {
120
+ constructor(componentOrMessage) {
121
+ const message = typeof componentOrMessage === 'string' ? componentOrMessage : formatErrorMessage(componentOrMessage, `Root element (\`$root\`) already initialised`);
122
+ super(message);
123
+ this.name = 'InitError';
124
+ }
125
+ }
126
+ /**
127
+ * @import { ComponentWithModuleName } from '../common/index.mjs'
128
+ */
221
129
 
222
- class GOVUKFrontendComponent {
223
- constructor() {
224
- this.checkSupport();
130
+ class Component {
131
+ /**
132
+ * Returns the root element of the component
133
+ *
134
+ * @protected
135
+ * @returns {RootElementType} - the root element of component
136
+ */
137
+ get $root() {
138
+ return this._$root;
139
+ }
140
+ constructor($root) {
141
+ this._$root = void 0;
142
+ const childConstructor = this.constructor;
143
+ if (typeof childConstructor.moduleName !== 'string') {
144
+ throw new InitError(`\`moduleName\` not defined in component`);
145
+ }
146
+ if (!($root instanceof childConstructor.elementType)) {
147
+ throw new ElementError({
148
+ element: $root,
149
+ component: childConstructor,
150
+ identifier: 'Root element (`$root`)',
151
+ expectedType: childConstructor.elementType.name
152
+ });
153
+ } else {
154
+ this._$root = $root;
155
+ }
156
+ childConstructor.checkSupport();
157
+ this.checkInitialised();
158
+ const moduleName = childConstructor.moduleName;
159
+ this.$root.setAttribute(`data-${moduleName}-init`, '');
160
+ }
161
+ checkInitialised() {
162
+ const constructor = this.constructor;
163
+ const moduleName = constructor.moduleName;
164
+ if (moduleName && isInitialised(this.$root, moduleName)) {
165
+ throw new InitError(constructor);
166
+ }
225
167
  }
226
- checkSupport() {
168
+ static checkSupport() {
227
169
  if (!isSupported()) {
228
170
  throw new SupportError();
229
171
  }
230
172
  }
231
173
  }
232
174
 
175
+ /**
176
+ * @typedef ChildClass
177
+ * @property {string} moduleName - The module name that'll be looked for in the DOM when initialising the component
178
+ */
179
+
180
+ /**
181
+ * @typedef {typeof Component & ChildClass} ChildClassConstructor
182
+ */
183
+ Component.elementType = HTMLElement;
184
+
185
+ const configOverride = Symbol.for('configOverride');
186
+ class ConfigurableComponent extends Component {
187
+ [configOverride](param) {
188
+ return {};
189
+ }
190
+
191
+ /**
192
+ * Returns the root element of the component
193
+ *
194
+ * @protected
195
+ * @returns {ConfigurationType} - the root element of component
196
+ */
197
+ get config() {
198
+ return this._config;
199
+ }
200
+ constructor($root, config) {
201
+ super($root);
202
+ this._config = void 0;
203
+ const childConstructor = this.constructor;
204
+ if (!isObject(childConstructor.defaults)) {
205
+ throw new ConfigError(formatErrorMessage(childConstructor, 'Config passed as parameter into constructor but no defaults defined'));
206
+ }
207
+ const datasetConfig = normaliseDataset(childConstructor, this._$root.dataset);
208
+ this._config = mergeConfigs(childConstructor.defaults, config != null ? config : {}, this[configOverride](datasetConfig), datasetConfig);
209
+ }
210
+ }
211
+ function normaliseString(value, property) {
212
+ const trimmedValue = value ? value.trim() : '';
213
+ let output;
214
+ let outputType = property == null ? void 0 : property.type;
215
+ if (!outputType) {
216
+ if (['true', 'false'].includes(trimmedValue)) {
217
+ outputType = 'boolean';
218
+ }
219
+ if (trimmedValue.length > 0 && isFinite(Number(trimmedValue))) {
220
+ outputType = 'number';
221
+ }
222
+ }
223
+ switch (outputType) {
224
+ case 'boolean':
225
+ output = trimmedValue === 'true';
226
+ break;
227
+ case 'number':
228
+ output = Number(trimmedValue);
229
+ break;
230
+ default:
231
+ output = value;
232
+ }
233
+ return output;
234
+ }
235
+ function normaliseDataset(Component, dataset) {
236
+ if (!isObject(Component.schema)) {
237
+ throw new ConfigError(formatErrorMessage(Component, 'Config passed as parameter into constructor but no schema defined'));
238
+ }
239
+ const out = {};
240
+ const entries = Object.entries(Component.schema.properties);
241
+ for (const entry of entries) {
242
+ const [namespace, property] = entry;
243
+ const field = namespace.toString();
244
+ if (field in dataset) {
245
+ out[field] = normaliseString(dataset[field], property);
246
+ }
247
+ if ((property == null ? void 0 : property.type) === 'object') {
248
+ out[field] = extractConfigByNamespace(Component.schema, dataset, namespace);
249
+ }
250
+ }
251
+ return out;
252
+ }
253
+ function mergeConfigs(...configObjects) {
254
+ const formattedConfigObject = {};
255
+ for (const configObject of configObjects) {
256
+ for (const key of Object.keys(configObject)) {
257
+ const option = formattedConfigObject[key];
258
+ const override = configObject[key];
259
+ if (isObject(option) && isObject(override)) {
260
+ formattedConfigObject[key] = mergeConfigs(option, override);
261
+ } else {
262
+ formattedConfigObject[key] = override;
263
+ }
264
+ }
265
+ }
266
+ return formattedConfigObject;
267
+ }
268
+ function validateConfig(schema, config) {
269
+ const validationErrors = [];
270
+ for (const [name, conditions] of Object.entries(schema)) {
271
+ const errors = [];
272
+ if (Array.isArray(conditions)) {
273
+ for (const {
274
+ required,
275
+ errorMessage
276
+ } of conditions) {
277
+ if (!required.every(key => !!config[key])) {
278
+ errors.push(errorMessage);
279
+ }
280
+ }
281
+ if (name === 'anyOf' && !(conditions.length - errors.length >= 1)) {
282
+ validationErrors.push(...errors);
283
+ }
284
+ }
285
+ }
286
+ return validationErrors;
287
+ }
288
+ function extractConfigByNamespace(schema, dataset, namespace) {
289
+ const property = schema.properties[namespace];
290
+ if ((property == null ? void 0 : property.type) !== 'object') {
291
+ return;
292
+ }
293
+ const newObject = {
294
+ [namespace]: {}
295
+ };
296
+ for (const [key, value] of Object.entries(dataset)) {
297
+ let current = newObject;
298
+ const keyParts = key.split('.');
299
+ for (const [index, name] of keyParts.entries()) {
300
+ if (isObject(current)) {
301
+ if (index < keyParts.length - 1) {
302
+ if (!isObject(current[name])) {
303
+ current[name] = {};
304
+ }
305
+ current = current[name];
306
+ } else if (key !== namespace) {
307
+ current[name] = normaliseString(value);
308
+ }
309
+ }
310
+ }
311
+ }
312
+ return newObject[namespace];
313
+ }
314
+ /**
315
+ * Schema for component config
316
+ *
317
+ * @template {Partial<Record<keyof ConfigurationType, unknown>>} ConfigurationType
318
+ * @typedef {object} Schema
319
+ * @property {Record<keyof ConfigurationType, SchemaProperty | undefined>} properties - Schema properties
320
+ * @property {SchemaCondition<ConfigurationType>[]} [anyOf] - List of schema conditions
321
+ */
322
+ /**
323
+ * Schema property for component config
324
+ *
325
+ * @typedef {object} SchemaProperty
326
+ * @property {'string' | 'boolean' | 'number' | 'object'} type - Property type
327
+ */
328
+ /**
329
+ * Schema condition for component config
330
+ *
331
+ * @template {Partial<Record<keyof ConfigurationType, unknown>>} ConfigurationType
332
+ * @typedef {object} SchemaCondition
333
+ * @property {(keyof ConfigurationType)[]} required - List of required config fields
334
+ * @property {string} errorMessage - Error message when required config fields not provided
335
+ */
336
+ /**
337
+ * @template {Partial<Record<keyof ConfigurationType, unknown>>} [ConfigurationType=ObjectNested]
338
+ * @typedef ChildClass
339
+ * @property {string} moduleName - The module name that'll be looked for in the DOM when initialising the component
340
+ * @property {Schema<ConfigurationType>} [schema] - The schema of the component configuration
341
+ * @property {ConfigurationType} [defaults] - The default values of the configuration of the component
342
+ */
343
+ /**
344
+ * @template {Partial<Record<keyof ConfigurationType, unknown>>} [ConfigurationType=ObjectNested]
345
+ * @typedef {typeof Component & ChildClass<ConfigurationType>} ChildClassConstructor<ConfigurationType>
346
+ */
347
+
233
348
  class I18n {
234
349
  constructor(translations = {}, config = {}) {
235
350
  var _config$locale;
@@ -436,16 +551,15 @@ I18n.pluralRules = {
436
551
  * attribute, which also provides accessibility.
437
552
  *
438
553
  * @preserve
554
+ * @augments ConfigurableComponent<AccordionConfig>
439
555
  */
440
- class Accordion extends GOVUKFrontendComponent {
556
+ class Accordion extends ConfigurableComponent {
441
557
  /**
442
- * @param {Element | null} $module - HTML element to use for accordion
558
+ * @param {Element | null} $root - HTML element to use for accordion
443
559
  * @param {AccordionConfig} [config] - Accordion config
444
560
  */
445
- constructor($module, config = {}) {
446
- super();
447
- this.$module = void 0;
448
- this.config = void 0;
561
+ constructor($root, config = {}) {
562
+ super($root, config);
449
563
  this.i18n = void 0;
450
564
  this.controlsClass = 'govuk-accordion__controls';
451
565
  this.showAllClass = 'govuk-accordion__show-all';
@@ -467,33 +581,21 @@ class Accordion extends GOVUKFrontendComponent {
467
581
  this.sectionSummaryFocusClass = 'govuk-accordion__section-summary-focus';
468
582
  this.sectionContentClass = 'govuk-accordion__section-content';
469
583
  this.$sections = void 0;
470
- this.browserSupportsSessionStorage = false;
471
584
  this.$showAllButton = null;
472
585
  this.$showAllIcon = null;
473
586
  this.$showAllText = null;
474
- if (!($module instanceof HTMLElement)) {
475
- throw new ElementError({
476
- componentName: 'Accordion',
477
- element: $module,
478
- identifier: 'Root element (`$module`)'
479
- });
480
- }
481
- this.$module = $module;
482
- this.config = mergeConfigs(Accordion.defaults, config, normaliseDataset(Accordion, $module.dataset));
483
587
  this.i18n = new I18n(this.config.i18n);
484
- const $sections = this.$module.querySelectorAll(`.${this.sectionClass}`);
588
+ const $sections = this.$root.querySelectorAll(`.${this.sectionClass}`);
485
589
  if (!$sections.length) {
486
590
  throw new ElementError({
487
- componentName: 'Accordion',
591
+ component: Accordion,
488
592
  identifier: `Sections (\`<div class="${this.sectionClass}">\`)`
489
593
  });
490
594
  }
491
595
  this.$sections = $sections;
492
- this.browserSupportsSessionStorage = helper.checkForSessionStorage();
493
596
  this.initControls();
494
597
  this.initSectionHeaders();
495
- const areAllSectionsOpen = this.checkIfAllSectionsOpen();
496
- this.updateShowAllButton(areAllSectionsOpen);
598
+ this.updateShowAllButton(this.areAllSectionsOpen());
497
599
  }
498
600
  initControls() {
499
601
  this.$showAllButton = document.createElement('button');
@@ -506,7 +608,7 @@ class Accordion extends GOVUKFrontendComponent {
506
608
  const $accordionControls = document.createElement('div');
507
609
  $accordionControls.setAttribute('class', this.controlsClass);
508
610
  $accordionControls.appendChild(this.$showAllButton);
509
- this.$module.insertBefore($accordionControls, this.$module.firstChild);
611
+ this.$root.insertBefore($accordionControls, this.$root.firstChild);
510
612
  this.$showAllText = document.createElement('span');
511
613
  this.$showAllText.classList.add(this.showAllTextClass);
512
614
  this.$showAllButton.appendChild(this.$showAllText);
@@ -520,7 +622,7 @@ class Accordion extends GOVUKFrontendComponent {
520
622
  const $header = $section.querySelector(`.${this.sectionHeaderClass}`);
521
623
  if (!$header) {
522
624
  throw new ElementError({
523
- componentName: 'Accordion',
625
+ component: Accordion,
524
626
  identifier: `Section headers (\`<div class="${this.sectionHeaderClass}">\`)`
525
627
  });
526
628
  }
@@ -536,22 +638,22 @@ class Accordion extends GOVUKFrontendComponent {
536
638
  const $summary = $header.querySelector(`.${this.sectionSummaryClass}`);
537
639
  if (!$heading) {
538
640
  throw new ElementError({
539
- componentName: 'Accordion',
641
+ component: Accordion,
540
642
  identifier: `Section heading (\`.${this.sectionHeadingClass}\`)`
541
643
  });
542
644
  }
543
645
  if (!$span) {
544
646
  throw new ElementError({
545
- componentName: 'Accordion',
647
+ component: Accordion,
546
648
  identifier: `Section button placeholder (\`<span class="${this.sectionButtonClass}">\`)`
547
649
  });
548
650
  }
549
651
  const $button = document.createElement('button');
550
652
  $button.setAttribute('type', 'button');
551
- $button.setAttribute('aria-controls', `${this.$module.id}-content-${index + 1}`);
653
+ $button.setAttribute('aria-controls', `${this.$root.id}-content-${index + 1}`);
552
654
  for (const attr of Array.from($span.attributes)) {
553
- if (attr.nodeName !== 'id') {
554
- $button.setAttribute(attr.nodeName, `${attr.nodeValue}`);
655
+ if (attr.name !== 'id') {
656
+ $button.setAttribute(attr.name, attr.value);
555
657
  }
556
658
  }
557
659
  const $headingText = document.createElement('span');
@@ -560,7 +662,7 @@ class Accordion extends GOVUKFrontendComponent {
560
662
  const $headingTextFocus = document.createElement('span');
561
663
  $headingTextFocus.classList.add(this.sectionHeadingTextFocusClass);
562
664
  $headingText.appendChild($headingTextFocus);
563
- $headingTextFocus.innerHTML = $span.innerHTML;
665
+ Array.from($span.childNodes).forEach($child => $headingTextFocus.appendChild($child));
564
666
  const $showHideToggle = document.createElement('span');
565
667
  $showHideToggle.classList.add(this.sectionShowHideToggleClass);
566
668
  $showHideToggle.setAttribute('data-nosnippet', '');
@@ -575,16 +677,16 @@ class Accordion extends GOVUKFrontendComponent {
575
677
  $showHideToggleFocus.appendChild($showHideText);
576
678
  $button.appendChild($headingText);
577
679
  $button.appendChild(this.getButtonPunctuationEl());
578
- if ($summary != null && $summary.parentNode) {
680
+ if ($summary) {
579
681
  const $summarySpan = document.createElement('span');
580
682
  const $summarySpanFocus = document.createElement('span');
581
683
  $summarySpanFocus.classList.add(this.sectionSummaryFocusClass);
582
684
  $summarySpan.appendChild($summarySpanFocus);
583
685
  for (const attr of Array.from($summary.attributes)) {
584
- $summarySpan.setAttribute(attr.nodeName, `${attr.nodeValue}`);
686
+ $summarySpan.setAttribute(attr.name, attr.value);
585
687
  }
586
- $summarySpanFocus.innerHTML = $summary.innerHTML;
587
- $summary.parentNode.replaceChild($summarySpan, $summary);
688
+ Array.from($summary.childNodes).forEach($child => $summarySpanFocus.appendChild($child));
689
+ $summary.remove();
588
690
  $button.appendChild($summarySpan);
589
691
  $button.appendChild(this.getButtonPunctuationEl());
590
692
  }
@@ -603,15 +705,15 @@ class Accordion extends GOVUKFrontendComponent {
603
705
  }
604
706
  }
605
707
  onSectionToggle($section) {
606
- const expanded = this.isExpanded($section);
607
- this.setExpanded(!expanded, $section);
608
- this.storeState($section);
708
+ const nowExpanded = !this.isExpanded($section);
709
+ this.setExpanded(nowExpanded, $section);
710
+ this.storeState($section, nowExpanded);
609
711
  }
610
712
  onShowOrHideAllToggle() {
611
- const nowExpanded = !this.checkIfAllSectionsOpen();
713
+ const nowExpanded = !this.areAllSectionsOpen();
612
714
  this.$sections.forEach($section => {
613
715
  this.setExpanded(nowExpanded, $section);
614
- this.storeState($section);
716
+ this.storeState($section, nowExpanded);
615
717
  });
616
718
  this.updateShowAllButton(nowExpanded);
617
719
  }
@@ -622,7 +724,7 @@ class Accordion extends GOVUKFrontendComponent {
622
724
  const $content = $section.querySelector(`.${this.sectionContentClass}`);
623
725
  if (!$content) {
624
726
  throw new ElementError({
625
- componentName: 'Accordion',
727
+ component: Accordion,
626
728
  identifier: `Section content (\`<div class="${this.sectionContentClass}">\`)`
627
729
  });
628
730
  }
@@ -653,17 +755,13 @@ class Accordion extends GOVUKFrontendComponent {
653
755
  $section.classList.remove(this.sectionExpandedClass);
654
756
  $showHideIcon.classList.add(this.downChevronIconClass);
655
757
  }
656
- const areAllSectionsOpen = this.checkIfAllSectionsOpen();
657
- this.updateShowAllButton(areAllSectionsOpen);
758
+ this.updateShowAllButton(this.areAllSectionsOpen());
658
759
  }
659
760
  isExpanded($section) {
660
761
  return $section.classList.contains(this.sectionExpandedClass);
661
762
  }
662
- checkIfAllSectionsOpen() {
663
- const sectionsCount = this.$sections.length;
664
- const expandedSectionCount = this.$module.querySelectorAll(`.${this.sectionExpandedClass}`).length;
665
- const areAllSectionsOpen = sectionsCount === expandedSectionCount;
666
- return areAllSectionsOpen;
763
+ areAllSectionsOpen() {
764
+ return Array.from(this.$sections).every($section => this.isExpanded($section));
667
765
  }
668
766
  updateShowAllButton(expanded) {
669
767
  if (!this.$showAllButton || !this.$showAllText || !this.$showAllIcon) {
@@ -673,78 +771,53 @@ class Accordion extends GOVUKFrontendComponent {
673
771
  this.$showAllText.textContent = expanded ? this.i18n.t('hideAllSections') : this.i18n.t('showAllSections');
674
772
  this.$showAllIcon.classList.toggle(this.downChevronIconClass, !expanded);
675
773
  }
676
- storeState($section) {
677
- if (this.browserSupportsSessionStorage && this.config.rememberExpanded) {
678
- const $button = $section.querySelector(`.${this.sectionButtonClass}`);
679
- if ($button) {
680
- const contentId = $button.getAttribute('aria-controls');
681
- const contentState = $button.getAttribute('aria-expanded');
682
- if (contentId && contentState) {
683
- window.sessionStorage.setItem(contentId, contentState);
684
- }
685
- }
686
- }
774
+
775
+ /**
776
+ * Get the identifier for a section
777
+ *
778
+ * We need a unique way of identifying each content in the Accordion.
779
+ * Since an `#id` should be unique and an `id` is required for `aria-`
780
+ * attributes `id` can be safely used.
781
+ *
782
+ * @param {Element} $section - Section element
783
+ * @returns {string | undefined | null} Identifier for section
784
+ */
785
+ getIdentifier($section) {
786
+ const $button = $section.querySelector(`.${this.sectionButtonClass}`);
787
+ return $button == null ? void 0 : $button.getAttribute('aria-controls');
687
788
  }
688
- setInitialState($section) {
689
- if (this.browserSupportsSessionStorage && this.config.rememberExpanded) {
690
- const $button = $section.querySelector(`.${this.sectionButtonClass}`);
691
- if ($button) {
692
- const contentId = $button.getAttribute('aria-controls');
693
- const contentState = contentId ? window.sessionStorage.getItem(contentId) : null;
694
- if (contentState !== null) {
695
- this.setExpanded(contentState === 'true', $section);
696
- }
697
- }
789
+ storeState($section, isExpanded) {
790
+ if (!this.config.rememberExpanded) {
791
+ return;
698
792
  }
699
- }
700
- getButtonPunctuationEl() {
701
- const $punctuationEl = document.createElement('span');
702
- $punctuationEl.classList.add('govuk-visually-hidden', this.sectionHeadingDividerClass);
703
- $punctuationEl.innerHTML = ', ';
704
- return $punctuationEl;
705
- }
706
- }
707
- Accordion.moduleName = 'govuk-accordion';
708
- Accordion.defaults = Object.freeze({
709
- i18n: {
710
- hideAllSections: 'Hide all sections',
711
- hideSection: 'Hide',
712
- hideSectionAriaLabel: 'Hide this section',
713
- showAllSections: 'Show all sections',
714
- showSection: 'Show',
715
- showSectionAriaLabel: 'Show this section'
716
- },
717
- rememberExpanded: true
718
- });
719
- Accordion.schema = Object.freeze({
720
- properties: {
721
- i18n: {
722
- type: 'object'
723
- },
724
- rememberExpanded: {
725
- type: 'boolean'
793
+ const id = this.getIdentifier($section);
794
+ if (id) {
795
+ try {
796
+ window.sessionStorage.setItem(id, isExpanded.toString());
797
+ } catch (exception) {}
726
798
  }
727
799
  }
728
- });
729
- const helper = {
730
- /**
731
- * Check for `window.sessionStorage`, and that it actually works.
732
- *
733
- * @returns {boolean} True if session storage is available
734
- */
735
- checkForSessionStorage: function () {
736
- const testString = 'this is the test string';
737
- let result;
738
- try {
739
- window.sessionStorage.setItem(testString, testString);
740
- result = window.sessionStorage.getItem(testString) === testString.toString();
741
- window.sessionStorage.removeItem(testString);
742
- return result;
743
- } catch (exception) {
744
- return false;
800
+ setInitialState($section) {
801
+ if (!this.config.rememberExpanded) {
802
+ return;
803
+ }
804
+ const id = this.getIdentifier($section);
805
+ if (id) {
806
+ try {
807
+ const state = window.sessionStorage.getItem(id);
808
+ if (state !== null) {
809
+ this.setExpanded(state === 'true', $section);
810
+ }
811
+ } catch (exception) {}
745
812
  }
746
813
  }
747
- };
814
+ getButtonPunctuationEl() {
815
+ const $punctuationEl = document.createElement('span');
816
+ $punctuationEl.classList.add('govuk-visually-hidden', this.sectionHeadingDividerClass);
817
+ $punctuationEl.textContent = ', ';
818
+ return $punctuationEl;
819
+ }
820
+ }
748
821
 
749
822
  /**
750
823
  * Accordion config
@@ -780,8 +853,30 @@ const helper = {
780
853
  */
781
854
 
782
855
  /**
783
- * @typedef {import('../../common/index.mjs').Schema} Schema
856
+ * @import { Schema } from '../../common/configuration.mjs'
784
857
  */
858
+ Accordion.moduleName = 'govuk-accordion';
859
+ Accordion.defaults = Object.freeze({
860
+ i18n: {
861
+ hideAllSections: 'Hide all sections',
862
+ hideSection: 'Hide',
863
+ hideSectionAriaLabel: 'Hide this section',
864
+ showAllSections: 'Show all sections',
865
+ showSection: 'Show',
866
+ showSectionAriaLabel: 'Show this section'
867
+ },
868
+ rememberExpanded: true
869
+ });
870
+ Accordion.schema = Object.freeze({
871
+ properties: {
872
+ i18n: {
873
+ type: 'object'
874
+ },
875
+ rememberExpanded: {
876
+ type: 'boolean'
877
+ }
878
+ }
879
+ });
785
880
 
786
881
  const DEBOUNCE_TIMEOUT_IN_SECONDS = 1;
787
882
 
@@ -789,28 +884,18 @@ const DEBOUNCE_TIMEOUT_IN_SECONDS = 1;
789
884
  * JavaScript enhancements for the Button component
790
885
  *
791
886
  * @preserve
887
+ * @augments ConfigurableComponent<ButtonConfig>
792
888
  */
793
- class Button extends GOVUKFrontendComponent {
889
+ class Button extends ConfigurableComponent {
794
890
  /**
795
- * @param {Element | null} $module - HTML element to use for button
891
+ * @param {Element | null} $root - HTML element to use for button
796
892
  * @param {ButtonConfig} [config] - Button config
797
893
  */
798
- constructor($module, config = {}) {
799
- super();
800
- this.$module = void 0;
801
- this.config = void 0;
894
+ constructor($root, config = {}) {
895
+ super($root, config);
802
896
  this.debounceFormSubmitTimer = null;
803
- if (!($module instanceof HTMLElement)) {
804
- throw new ElementError({
805
- componentName: 'Button',
806
- element: $module,
807
- identifier: 'Root element (`$module`)'
808
- });
809
- }
810
- this.$module = $module;
811
- this.config = mergeConfigs(Button.defaults, config, normaliseDataset(Button, $module.dataset));
812
- this.$module.addEventListener('keydown', event => this.handleKeyDown(event));
813
- this.$module.addEventListener('click', event => this.debounce(event));
897
+ this.$root.addEventListener('keydown', event => this.handleKeyDown(event));
898
+ this.$root.addEventListener('click', event => this.debounce(event));
814
899
  }
815
900
  handleKeyDown(event) {
816
901
  const $target = event.target;
@@ -845,7 +930,7 @@ class Button extends GOVUKFrontendComponent {
845
930
  */
846
931
 
847
932
  /**
848
- * @typedef {import('../../common/index.mjs').Schema} Schema
933
+ * @import { Schema } from '../../common/configuration.mjs'
849
934
  */
850
935
  Button.moduleName = 'govuk-button';
851
936
  Button.defaults = Object.freeze({
@@ -875,69 +960,63 @@ function closestAttributeValue($element, attributeName) {
875
960
  * of the available characters/words has been entered.
876
961
  *
877
962
  * @preserve
963
+ * @augments ConfigurableComponent<CharacterCountConfig>
878
964
  */
879
- class CharacterCount extends GOVUKFrontendComponent {
965
+ class CharacterCount extends ConfigurableComponent {
966
+ [configOverride](datasetConfig) {
967
+ let configOverrides = {};
968
+ if ('maxwords' in datasetConfig || 'maxlength' in datasetConfig) {
969
+ configOverrides = {
970
+ maxlength: undefined,
971
+ maxwords: undefined
972
+ };
973
+ }
974
+ return configOverrides;
975
+ }
976
+
880
977
  /**
881
- * @param {Element | null} $module - HTML element to use for character count
978
+ * @param {Element | null} $root - HTML element to use for character count
882
979
  * @param {CharacterCountConfig} [config] - Character count config
883
980
  */
884
- constructor($module, config = {}) {
981
+ constructor($root, config = {}) {
885
982
  var _ref, _this$config$maxwords;
886
- super();
887
- this.$module = void 0;
983
+ super($root, config);
888
984
  this.$textarea = void 0;
889
985
  this.$visibleCountMessage = void 0;
890
986
  this.$screenReaderCountMessage = void 0;
891
987
  this.lastInputTimestamp = null;
892
988
  this.lastInputValue = '';
893
989
  this.valueChecker = null;
894
- this.config = void 0;
895
990
  this.i18n = void 0;
896
991
  this.maxLength = void 0;
897
- if (!($module instanceof HTMLElement)) {
898
- throw new ElementError({
899
- componentName: 'Character count',
900
- element: $module,
901
- identifier: 'Root element (`$module`)'
902
- });
903
- }
904
- const $textarea = $module.querySelector('.govuk-js-character-count');
992
+ const $textarea = this.$root.querySelector('.govuk-js-character-count');
905
993
  if (!($textarea instanceof HTMLTextAreaElement || $textarea instanceof HTMLInputElement)) {
906
994
  throw new ElementError({
907
- componentName: 'Character count',
995
+ component: CharacterCount,
908
996
  element: $textarea,
909
997
  expectedType: 'HTMLTextareaElement or HTMLInputElement',
910
998
  identifier: 'Form field (`.govuk-js-character-count`)'
911
999
  });
912
1000
  }
913
- const datasetConfig = normaliseDataset(CharacterCount, $module.dataset);
914
- let configOverrides = {};
915
- if ('maxwords' in datasetConfig || 'maxlength' in datasetConfig) {
916
- configOverrides = {
917
- maxlength: undefined,
918
- maxwords: undefined
919
- };
920
- }
921
- this.config = mergeConfigs(CharacterCount.defaults, config, configOverrides, datasetConfig);
922
1001
  const errors = validateConfig(CharacterCount.schema, this.config);
923
1002
  if (errors[0]) {
924
- throw new ConfigError(`Character count: ${errors[0]}`);
1003
+ throw new ConfigError(formatErrorMessage(CharacterCount, errors[0]));
925
1004
  }
926
1005
  this.i18n = new I18n(this.config.i18n, {
927
- locale: closestAttributeValue($module, 'lang')
1006
+ locale: closestAttributeValue(this.$root, 'lang')
928
1007
  });
929
1008
  this.maxLength = (_ref = (_this$config$maxwords = this.config.maxwords) != null ? _this$config$maxwords : this.config.maxlength) != null ? _ref : Infinity;
930
- this.$module = $module;
931
1009
  this.$textarea = $textarea;
932
1010
  const textareaDescriptionId = `${this.$textarea.id}-info`;
933
1011
  const $textareaDescription = document.getElementById(textareaDescriptionId);
934
1012
  if (!$textareaDescription) {
935
1013
  throw new ElementError({
936
- componentName: 'Character count',
1014
+ component: CharacterCount,
937
1015
  element: $textareaDescription,
938
1016
  identifier: `Count message (\`id="${textareaDescriptionId}"\`)`
939
1017
  });
940
1018
  }
1019
+ this.$errorMessage = this.$root.querySelector('.govuk-error-message');
941
1020
  if (`${$textareaDescription.textContent}`.match(/^\s*$/)) {
942
1021
  $textareaDescription.textContent = this.i18n.t('textareaDescription', {
943
1022
  count: this.maxLength
@@ -996,7 +1075,9 @@ class CharacterCount extends GOVUKFrontendComponent {
996
1075
  const remainingNumber = this.maxLength - this.count(this.$textarea.value);
997
1076
  const isError = remainingNumber < 0;
998
1077
  this.$visibleCountMessage.classList.toggle('govuk-character-count__message--disabled', !this.isOverThreshold());
999
- this.$textarea.classList.toggle('govuk-textarea--error', isError);
1078
+ if (!this.$errorMessage) {
1079
+ this.$textarea.classList.toggle('govuk-textarea--error', isError);
1080
+ }
1000
1081
  this.$visibleCountMessage.classList.toggle('govuk-error-message', isError);
1001
1082
  this.$visibleCountMessage.classList.toggle('govuk-hint', !isError);
1002
1083
  this.$visibleCountMessage.textContent = this.getCountMessage();
@@ -1105,8 +1186,8 @@ class CharacterCount extends GOVUKFrontendComponent {
1105
1186
  */
1106
1187
 
1107
1188
  /**
1108
- * @typedef {import('../../common/index.mjs').Schema} Schema
1109
- * @typedef {import('../../i18n.mjs').TranslationPluralForms} TranslationPluralForms
1189
+ * @import { Schema } from '../../common/configuration.mjs'
1190
+ * @import { TranslationPluralForms } from '../../i18n.mjs'
1110
1191
  */
1111
1192
  CharacterCount.moduleName = 'govuk-character-count';
1112
1193
  CharacterCount.defaults = Object.freeze({
@@ -1164,7 +1245,7 @@ CharacterCount.schema = Object.freeze({
1164
1245
  *
1165
1246
  * @preserve
1166
1247
  */
1167
- class Checkboxes extends GOVUKFrontendComponent {
1248
+ class Checkboxes extends Component {
1168
1249
  /**
1169
1250
  * Checkboxes can be associated with a 'conditionally revealed' content block
1170
1251
  * – for example, a checkbox for 'Phone' could reveal an additional form field
@@ -1177,27 +1258,18 @@ class Checkboxes extends GOVUKFrontendComponent {
1177
1258
  * (for example if the user has navigated back), and set up event handlers to
1178
1259
  * keep the reveal in sync with the checkbox state.
1179
1260
  *
1180
- * @param {Element | null} $module - HTML element to use for checkboxes
1261
+ * @param {Element | null} $root - HTML element to use for checkboxes
1181
1262
  */
1182
- constructor($module) {
1183
- super();
1184
- this.$module = void 0;
1263
+ constructor($root) {
1264
+ super($root);
1185
1265
  this.$inputs = void 0;
1186
- if (!($module instanceof HTMLElement)) {
1187
- throw new ElementError({
1188
- componentName: 'Checkboxes',
1189
- element: $module,
1190
- identifier: 'Root element (`$module`)'
1191
- });
1192
- }
1193
- const $inputs = $module.querySelectorAll('input[type="checkbox"]');
1266
+ const $inputs = this.$root.querySelectorAll('input[type="checkbox"]');
1194
1267
  if (!$inputs.length) {
1195
1268
  throw new ElementError({
1196
- componentName: 'Checkboxes',
1269
+ component: Checkboxes,
1197
1270
  identifier: 'Form inputs (`<input type="checkbox">`)'
1198
1271
  });
1199
1272
  }
1200
- this.$module = $module;
1201
1273
  this.$inputs = $inputs;
1202
1274
  this.$inputs.forEach($input => {
1203
1275
  const targetId = $input.getAttribute('data-aria-controls');
@@ -1206,7 +1278,7 @@ class Checkboxes extends GOVUKFrontendComponent {
1206
1278
  }
1207
1279
  if (!document.getElementById(targetId)) {
1208
1280
  throw new ElementError({
1209
- componentName: 'Checkboxes',
1281
+ component: Checkboxes,
1210
1282
  identifier: `Conditional reveal (\`id="${targetId}"\`)`
1211
1283
  });
1212
1284
  }
@@ -1215,7 +1287,7 @@ class Checkboxes extends GOVUKFrontendComponent {
1215
1287
  });
1216
1288
  window.addEventListener('pageshow', () => this.syncAllConditionalReveals());
1217
1289
  this.syncAllConditionalReveals();
1218
- this.$module.addEventListener('click', event => this.handleClick(event));
1290
+ this.$root.addEventListener('click', event => this.handleClick(event));
1219
1291
  }
1220
1292
  syncAllConditionalReveals() {
1221
1293
  this.$inputs.forEach($input => this.syncConditionalRevealWithInputState($input));
@@ -1281,29 +1353,19 @@ Checkboxes.moduleName = 'govuk-checkboxes';
1281
1353
  * configuration.
1282
1354
  *
1283
1355
  * @preserve
1356
+ * @augments ConfigurableComponent<ErrorSummaryConfig>
1284
1357
  */
1285
- class ErrorSummary extends GOVUKFrontendComponent {
1358
+ class ErrorSummary extends ConfigurableComponent {
1286
1359
  /**
1287
- * @param {Element | null} $module - HTML element to use for error summary
1360
+ * @param {Element | null} $root - HTML element to use for error summary
1288
1361
  * @param {ErrorSummaryConfig} [config] - Error summary config
1289
1362
  */
1290
- constructor($module, config = {}) {
1291
- super();
1292
- this.$module = void 0;
1293
- this.config = void 0;
1294
- if (!($module instanceof HTMLElement)) {
1295
- throw new ElementError({
1296
- componentName: 'Error summary',
1297
- element: $module,
1298
- identifier: 'Root element (`$module`)'
1299
- });
1300
- }
1301
- this.$module = $module;
1302
- this.config = mergeConfigs(ErrorSummary.defaults, config, normaliseDataset(ErrorSummary, $module.dataset));
1363
+ constructor($root, config = {}) {
1364
+ super($root, config);
1303
1365
  if (!this.config.disableAutoFocus) {
1304
- setFocus(this.$module);
1366
+ setFocus(this.$root);
1305
1367
  }
1306
- this.$module.addEventListener('click', event => this.handleClick(event));
1368
+ this.$root.addEventListener('click', event => this.handleClick(event));
1307
1369
  }
1308
1370
  handleClick(event) {
1309
1371
  const $target = event.target;
@@ -1366,7 +1428,7 @@ class ErrorSummary extends GOVUKFrontendComponent {
1366
1428
  */
1367
1429
 
1368
1430
  /**
1369
- * @typedef {import('../../common/index.mjs').Schema} Schema
1431
+ * @import { Schema } from '../../common/configuration.mjs'
1370
1432
  */
1371
1433
  ErrorSummary.moduleName = 'govuk-error-summary';
1372
1434
  ErrorSummary.defaults = Object.freeze({
@@ -1384,16 +1446,15 @@ ErrorSummary.schema = Object.freeze({
1384
1446
  * Exit this page component
1385
1447
  *
1386
1448
  * @preserve
1449
+ * @augments ConfigurableComponent<ExitThisPageConfig>
1387
1450
  */
1388
- class ExitThisPage extends GOVUKFrontendComponent {
1451
+ class ExitThisPage extends ConfigurableComponent {
1389
1452
  /**
1390
- * @param {Element | null} $module - HTML element that wraps the Exit This Page button
1453
+ * @param {Element | null} $root - HTML element that wraps the Exit This Page button
1391
1454
  * @param {ExitThisPageConfig} [config] - Exit This Page config
1392
1455
  */
1393
- constructor($module, config = {}) {
1394
- super();
1395
- this.$module = void 0;
1396
- this.config = void 0;
1456
+ constructor($root, config = {}) {
1457
+ super($root, config);
1397
1458
  this.i18n = void 0;
1398
1459
  this.$button = void 0;
1399
1460
  this.$skiplinkButton = null;
@@ -1405,25 +1466,16 @@ class ExitThisPage extends GOVUKFrontendComponent {
1405
1466
  this.timeoutTime = 5000;
1406
1467
  this.keypressTimeoutId = null;
1407
1468
  this.timeoutMessageId = null;
1408
- if (!($module instanceof HTMLElement)) {
1409
- throw new ElementError({
1410
- componentName: 'Exit this page',
1411
- element: $module,
1412
- identifier: 'Root element (`$module`)'
1413
- });
1414
- }
1415
- const $button = $module.querySelector('.govuk-exit-this-page__button');
1469
+ const $button = this.$root.querySelector('.govuk-exit-this-page__button');
1416
1470
  if (!($button instanceof HTMLAnchorElement)) {
1417
1471
  throw new ElementError({
1418
- componentName: 'Exit this page',
1472
+ component: ExitThisPage,
1419
1473
  element: $button,
1420
1474
  expectedType: 'HTMLAnchorElement',
1421
1475
  identifier: 'Button (`.govuk-exit-this-page__button`)'
1422
1476
  });
1423
1477
  }
1424
- this.config = mergeConfigs(ExitThisPage.defaults, config, normaliseDataset(ExitThisPage, $module.dataset));
1425
1478
  this.i18n = new I18n(this.config.i18n);
1426
- this.$module = $module;
1427
1479
  this.$button = $button;
1428
1480
  const $skiplinkButton = document.querySelector('.govuk-js-exit-this-page-skiplink');
1429
1481
  if ($skiplinkButton instanceof HTMLAnchorElement) {
@@ -1442,7 +1494,7 @@ class ExitThisPage extends GOVUKFrontendComponent {
1442
1494
  this.$updateSpan = document.createElement('span');
1443
1495
  this.$updateSpan.setAttribute('role', 'status');
1444
1496
  this.$updateSpan.className = 'govuk-visually-hidden';
1445
- this.$module.appendChild(this.$updateSpan);
1497
+ this.$root.appendChild(this.$updateSpan);
1446
1498
  }
1447
1499
  initButtonClickHandler() {
1448
1500
  this.$button.addEventListener('click', this.handleClick.bind(this));
@@ -1588,7 +1640,7 @@ class ExitThisPage extends GOVUKFrontendComponent {
1588
1640
  */
1589
1641
 
1590
1642
  /**
1591
- * @typedef {import('../../common/index.mjs').Schema} Schema
1643
+ * @import { Schema } from '../../common/configuration.mjs'
1592
1644
  */
1593
1645
  ExitThisPage.moduleName = 'govuk-exit-this-page';
1594
1646
  ExitThisPage.defaults = Object.freeze({
@@ -1607,48 +1659,289 @@ ExitThisPage.schema = Object.freeze({
1607
1659
  }
1608
1660
  });
1609
1661
 
1662
+ /**
1663
+ * File upload component
1664
+ *
1665
+ * @preserve
1666
+ * @augments ConfigurableComponent<FileUploadConfig>
1667
+ */
1668
+ class FileUpload extends ConfigurableComponent {
1669
+ /**
1670
+ * @param {Element | null} $root - File input element
1671
+ * @param {FileUploadConfig} [config] - File Upload config
1672
+ */
1673
+ constructor($root, config = {}) {
1674
+ super($root, config);
1675
+ this.$input = void 0;
1676
+ this.$button = void 0;
1677
+ this.$status = void 0;
1678
+ this.i18n = void 0;
1679
+ this.id = void 0;
1680
+ this.$announcements = void 0;
1681
+ this.enteredAnotherElement = void 0;
1682
+ const $input = this.$root.querySelector('input');
1683
+ if ($input === null) {
1684
+ throw new ElementError({
1685
+ component: FileUpload,
1686
+ identifier: 'File inputs (`<input type="file">`)'
1687
+ });
1688
+ }
1689
+ if ($input.type !== 'file') {
1690
+ throw new ElementError(formatErrorMessage(FileUpload, 'File input (`<input type="file">`) attribute (`type`) is not `file`'));
1691
+ }
1692
+ this.$input = $input;
1693
+ this.$input.setAttribute('hidden', 'true');
1694
+ if (!this.$input.id) {
1695
+ throw new ElementError({
1696
+ component: FileUpload,
1697
+ identifier: 'File input (`<input type="file">`) attribute (`id`)'
1698
+ });
1699
+ }
1700
+ this.id = this.$input.id;
1701
+ this.i18n = new I18n(this.config.i18n, {
1702
+ locale: closestAttributeValue(this.$root, 'lang')
1703
+ });
1704
+ const $label = this.findLabel();
1705
+ if (!$label.id) {
1706
+ $label.id = `${this.id}-label`;
1707
+ }
1708
+ this.$input.id = `${this.id}-input`;
1709
+ const $button = document.createElement('button');
1710
+ $button.classList.add('govuk-file-upload-button');
1711
+ $button.type = 'button';
1712
+ $button.id = this.id;
1713
+ $button.classList.add('govuk-file-upload-button--empty');
1714
+ const ariaDescribedBy = this.$input.getAttribute('aria-describedby');
1715
+ if (ariaDescribedBy) {
1716
+ $button.setAttribute('aria-describedby', ariaDescribedBy);
1717
+ }
1718
+ const $status = document.createElement('span');
1719
+ $status.className = 'govuk-body govuk-file-upload-button__status';
1720
+ $status.setAttribute('aria-live', 'polite');
1721
+ $status.innerText = this.i18n.t('noFileChosen');
1722
+ $button.appendChild($status);
1723
+ const commaSpan = document.createElement('span');
1724
+ commaSpan.className = 'govuk-visually-hidden';
1725
+ commaSpan.innerText = ', ';
1726
+ commaSpan.id = `${this.id}-comma`;
1727
+ $button.appendChild(commaSpan);
1728
+ const containerSpan = document.createElement('span');
1729
+ containerSpan.className = 'govuk-file-upload-button__pseudo-button-container';
1730
+ const buttonSpan = document.createElement('span');
1731
+ buttonSpan.className = 'govuk-button govuk-button--secondary govuk-file-upload-button__pseudo-button';
1732
+ buttonSpan.innerText = this.i18n.t('chooseFilesButton');
1733
+ containerSpan.appendChild(buttonSpan);
1734
+ containerSpan.insertAdjacentText('beforeend', ' ');
1735
+ const instructionSpan = document.createElement('span');
1736
+ instructionSpan.className = 'govuk-body govuk-file-upload-button__instruction';
1737
+ instructionSpan.innerText = this.i18n.t('dropInstruction');
1738
+ containerSpan.appendChild(instructionSpan);
1739
+ $button.appendChild(containerSpan);
1740
+ $button.setAttribute('aria-labelledby', `${$label.id} ${commaSpan.id} ${$button.id}`);
1741
+ $button.addEventListener('click', this.onClick.bind(this));
1742
+ $button.addEventListener('dragover', event => {
1743
+ event.preventDefault();
1744
+ });
1745
+ this.$root.insertAdjacentElement('afterbegin', $button);
1746
+ this.$input.setAttribute('tabindex', '-1');
1747
+ this.$input.setAttribute('aria-hidden', 'true');
1748
+ this.$button = $button;
1749
+ this.$status = $status;
1750
+ this.$input.addEventListener('change', this.onChange.bind(this));
1751
+ this.updateDisabledState();
1752
+ this.observeDisabledState();
1753
+ this.$announcements = document.createElement('span');
1754
+ this.$announcements.classList.add('govuk-file-upload-announcements');
1755
+ this.$announcements.classList.add('govuk-visually-hidden');
1756
+ this.$announcements.setAttribute('aria-live', 'assertive');
1757
+ this.$root.insertAdjacentElement('afterend', this.$announcements);
1758
+ this.$button.addEventListener('drop', this.onDrop.bind(this));
1759
+ document.addEventListener('dragenter', this.updateDropzoneVisibility.bind(this));
1760
+ document.addEventListener('dragenter', () => {
1761
+ this.enteredAnotherElement = true;
1762
+ });
1763
+ document.addEventListener('dragleave', () => {
1764
+ if (!this.enteredAnotherElement && !this.$button.disabled) {
1765
+ this.hideDraggingState();
1766
+ this.$announcements.innerText = this.i18n.t('leftDropZone');
1767
+ }
1768
+ this.enteredAnotherElement = false;
1769
+ });
1770
+ }
1771
+ updateDropzoneVisibility(event) {
1772
+ if (this.$button.disabled) return;
1773
+ if (event.target instanceof Node) {
1774
+ if (this.$root.contains(event.target)) {
1775
+ if (event.dataTransfer && isContainingFiles(event.dataTransfer)) {
1776
+ if (!this.$button.classList.contains('govuk-file-upload-button--dragging')) {
1777
+ this.showDraggingState();
1778
+ this.$announcements.innerText = this.i18n.t('enteredDropZone');
1779
+ }
1780
+ }
1781
+ } else {
1782
+ if (this.$button.classList.contains('govuk-file-upload-button--dragging')) {
1783
+ this.hideDraggingState();
1784
+ this.$announcements.innerText = this.i18n.t('leftDropZone');
1785
+ }
1786
+ }
1787
+ }
1788
+ }
1789
+ showDraggingState() {
1790
+ this.$button.classList.add('govuk-file-upload-button--dragging');
1791
+ }
1792
+ hideDraggingState() {
1793
+ this.$button.classList.remove('govuk-file-upload-button--dragging');
1794
+ }
1795
+ onDrop(event) {
1796
+ event.preventDefault();
1797
+ if (event.dataTransfer && isContainingFiles(event.dataTransfer)) {
1798
+ this.$input.files = event.dataTransfer.files;
1799
+ this.$input.dispatchEvent(new CustomEvent('change'));
1800
+ this.hideDraggingState();
1801
+ }
1802
+ }
1803
+ onChange() {
1804
+ const fileCount = this.$input.files.length;
1805
+ if (fileCount === 0) {
1806
+ this.$status.innerText = this.i18n.t('noFileChosen');
1807
+ this.$button.classList.add('govuk-file-upload-button--empty');
1808
+ } else {
1809
+ if (fileCount === 1) {
1810
+ this.$status.innerText = this.$input.files[0].name;
1811
+ } else {
1812
+ this.$status.innerText = this.i18n.t('multipleFilesChosen', {
1813
+ count: fileCount
1814
+ });
1815
+ }
1816
+ this.$button.classList.remove('govuk-file-upload-button--empty');
1817
+ }
1818
+ }
1819
+ findLabel() {
1820
+ const $label = document.querySelector(`label[for="${this.$input.id}"]`);
1821
+ if (!$label) {
1822
+ throw new ElementError({
1823
+ component: FileUpload,
1824
+ identifier: `Field label (\`<label for=${this.$input.id}>\`)`
1825
+ });
1826
+ }
1827
+ return $label;
1828
+ }
1829
+ onClick() {
1830
+ this.$input.click();
1831
+ }
1832
+ observeDisabledState() {
1833
+ const observer = new MutationObserver(mutationList => {
1834
+ for (const mutation of mutationList) {
1835
+ if (mutation.type === 'attributes' && mutation.attributeName === 'disabled') {
1836
+ this.updateDisabledState();
1837
+ }
1838
+ }
1839
+ });
1840
+ observer.observe(this.$input, {
1841
+ attributes: true
1842
+ });
1843
+ }
1844
+ updateDisabledState() {
1845
+ this.$button.disabled = this.$input.disabled;
1846
+ this.$root.classList.toggle('govuk-drop-zone--disabled', this.$button.disabled);
1847
+ }
1848
+ }
1849
+ FileUpload.moduleName = 'govuk-file-upload';
1850
+ FileUpload.defaults = Object.freeze({
1851
+ i18n: {
1852
+ chooseFilesButton: 'Choose file',
1853
+ dropInstruction: 'or drop file',
1854
+ noFileChosen: 'No file chosen',
1855
+ multipleFilesChosen: {
1856
+ one: '%{count} file chosen',
1857
+ other: '%{count} files chosen'
1858
+ },
1859
+ enteredDropZone: 'Entered drop zone',
1860
+ leftDropZone: 'Left drop zone'
1861
+ }
1862
+ });
1863
+ FileUpload.schema = Object.freeze({
1864
+ properties: {
1865
+ i18n: {
1866
+ type: 'object'
1867
+ }
1868
+ }
1869
+ });
1870
+ function isContainingFiles(dataTransfer) {
1871
+ const hasNoTypesInfo = dataTransfer.types.length === 0;
1872
+ const isDraggingFiles = dataTransfer.types.some(type => type === 'Files');
1873
+ return hasNoTypesInfo || isDraggingFiles;
1874
+ }
1875
+
1876
+ /**
1877
+ * @typedef {HTMLInputElement & {files: FileList}} HTMLFileInputElement
1878
+ */
1879
+
1880
+ /**
1881
+ * File upload config
1882
+ *
1883
+ * @see {@link FileUpload.defaults}
1884
+ * @typedef {object} FileUploadConfig
1885
+ * @property {FileUploadTranslations} [i18n=FileUpload.defaults.i18n] - File upload translations
1886
+ */
1887
+
1888
+ /**
1889
+ * File upload translations
1890
+ *
1891
+ * @see {@link FileUpload.defaults.i18n}
1892
+ * @typedef {object} FileUploadTranslations
1893
+ *
1894
+ * Messages used by the component
1895
+ * @property {string} [chooseFile] - The text of the button that opens the file picker
1896
+ * @property {string} [dropInstruction] - The text informing users they can drop files
1897
+ * @property {TranslationPluralForms} [multipleFilesChosen] - The text displayed when multiple files
1898
+ * have been chosen by the user
1899
+ * @property {string} [noFileChosen] - The text to displayed when no file has been chosen by the user
1900
+ * @property {string} [enteredDropZone] - The text announced by assistive technology
1901
+ * when user drags files and enters the drop zone
1902
+ * @property {string} [leftDropZone] - The text announced by assistive technology
1903
+ * when user drags files and leaves the drop zone without dropping
1904
+ */
1905
+
1906
+ /**
1907
+ * @import { Schema } from '../../common/configuration.mjs'
1908
+ * @import { TranslationPluralForms } from '../../i18n.mjs'
1909
+ */
1910
+
1610
1911
  /**
1611
1912
  * Header component
1612
1913
  *
1613
1914
  * @preserve
1614
1915
  */
1615
- class Header extends GOVUKFrontendComponent {
1916
+ class Header extends Component {
1616
1917
  /**
1617
1918
  * Apply a matchMedia for desktop which will trigger a state sync if the
1618
1919
  * browser viewport moves between states.
1619
1920
  *
1620
- * @param {Element | null} $module - HTML element to use for header
1921
+ * @param {Element | null} $root - HTML element to use for header
1621
1922
  */
1622
- constructor($module) {
1623
- super();
1624
- this.$module = void 0;
1923
+ constructor($root) {
1924
+ super($root);
1625
1925
  this.$menuButton = void 0;
1626
1926
  this.$menu = void 0;
1627
1927
  this.menuIsOpen = false;
1628
1928
  this.mql = null;
1629
- if (!$module) {
1630
- throw new ElementError({
1631
- componentName: 'Header',
1632
- element: $module,
1633
- identifier: 'Root element (`$module`)'
1634
- });
1635
- }
1636
- this.$module = $module;
1637
- const $menuButton = $module.querySelector('.govuk-js-header-toggle');
1929
+ const $menuButton = this.$root.querySelector('.govuk-js-header-toggle');
1638
1930
  if (!$menuButton) {
1639
1931
  return this;
1640
1932
  }
1933
+ this.$root.classList.add('govuk-header--with-js-navigation');
1641
1934
  const menuId = $menuButton.getAttribute('aria-controls');
1642
1935
  if (!menuId) {
1643
1936
  throw new ElementError({
1644
- componentName: 'Header',
1937
+ component: Header,
1645
1938
  identifier: 'Navigation button (`<button class="govuk-js-header-toggle">`) attribute (`aria-controls`)'
1646
1939
  });
1647
1940
  }
1648
1941
  const $menu = document.getElementById(menuId);
1649
1942
  if (!$menu) {
1650
1943
  throw new ElementError({
1651
- componentName: 'Header',
1944
+ component: Header,
1652
1945
  element: $menu,
1653
1946
  identifier: `Navigation (\`<ul id="${menuId}">\`)`
1654
1947
  });
@@ -1662,7 +1955,7 @@ class Header extends GOVUKFrontendComponent {
1662
1955
  const breakpoint = getBreakpoint('desktop');
1663
1956
  if (!breakpoint.value) {
1664
1957
  throw new ElementError({
1665
- componentName: 'Header',
1958
+ component: Header,
1666
1959
  identifier: `CSS custom property (\`${breakpoint.property}\`) on pseudo-class \`:root\``
1667
1960
  });
1668
1961
  }
@@ -1702,27 +1995,17 @@ Header.moduleName = 'govuk-header';
1702
1995
  * Notification Banner component
1703
1996
  *
1704
1997
  * @preserve
1998
+ * @augments ConfigurableComponent<NotificationBannerConfig>
1705
1999
  */
1706
- class NotificationBanner extends GOVUKFrontendComponent {
2000
+ class NotificationBanner extends ConfigurableComponent {
1707
2001
  /**
1708
- * @param {Element | null} $module - HTML element to use for notification banner
2002
+ * @param {Element | null} $root - HTML element to use for notification banner
1709
2003
  * @param {NotificationBannerConfig} [config] - Notification banner config
1710
2004
  */
1711
- constructor($module, config = {}) {
1712
- super();
1713
- this.$module = void 0;
1714
- this.config = void 0;
1715
- if (!($module instanceof HTMLElement)) {
1716
- throw new ElementError({
1717
- componentName: 'Notification banner',
1718
- element: $module,
1719
- identifier: 'Root element (`$module`)'
1720
- });
1721
- }
1722
- this.$module = $module;
1723
- this.config = mergeConfigs(NotificationBanner.defaults, config, normaliseDataset(NotificationBanner, $module.dataset));
1724
- if (this.$module.getAttribute('role') === 'alert' && !this.config.disableAutoFocus) {
1725
- setFocus(this.$module);
2005
+ constructor($root, config = {}) {
2006
+ super($root, config);
2007
+ if (this.$root.getAttribute('role') === 'alert' && !this.config.disableAutoFocus) {
2008
+ setFocus(this.$root);
1726
2009
  }
1727
2010
  }
1728
2011
  }
@@ -1738,7 +2021,7 @@ class NotificationBanner extends GOVUKFrontendComponent {
1738
2021
  */
1739
2022
 
1740
2023
  /**
1741
- * @typedef {import('../../common/index.mjs').Schema} Schema
2024
+ * @import { Schema } from '../../common/configuration.mjs'
1742
2025
  */
1743
2026
  NotificationBanner.moduleName = 'govuk-notification-banner';
1744
2027
  NotificationBanner.defaults = Object.freeze({
@@ -1756,31 +2039,23 @@ NotificationBanner.schema = Object.freeze({
1756
2039
  * Password input component
1757
2040
  *
1758
2041
  * @preserve
2042
+ * @augments ConfigurableComponent<PasswordInputConfig>
1759
2043
  */
1760
- class PasswordInput extends GOVUKFrontendComponent {
2044
+ class PasswordInput extends ConfigurableComponent {
1761
2045
  /**
1762
- * @param {Element | null} $module - HTML element to use for password input
2046
+ * @param {Element | null} $root - HTML element to use for password input
1763
2047
  * @param {PasswordInputConfig} [config] - Password input config
1764
2048
  */
1765
- constructor($module, config = {}) {
1766
- super();
1767
- this.$module = void 0;
1768
- this.config = void 0;
2049
+ constructor($root, config = {}) {
2050
+ super($root, config);
1769
2051
  this.i18n = void 0;
1770
2052
  this.$input = void 0;
1771
2053
  this.$showHideButton = void 0;
1772
2054
  this.$screenReaderStatusMessage = void 0;
1773
- if (!($module instanceof HTMLElement)) {
1774
- throw new ElementError({
1775
- componentName: 'Password input',
1776
- element: $module,
1777
- identifier: 'Root element (`$module`)'
1778
- });
1779
- }
1780
- const $input = $module.querySelector('.govuk-js-password-input-input');
2055
+ const $input = this.$root.querySelector('.govuk-js-password-input-input');
1781
2056
  if (!($input instanceof HTMLInputElement)) {
1782
2057
  throw new ElementError({
1783
- componentName: 'Password input',
2058
+ component: PasswordInput,
1784
2059
  element: $input,
1785
2060
  expectedType: 'HTMLInputElement',
1786
2061
  identifier: 'Form field (`.govuk-js-password-input-input`)'
@@ -1789,10 +2064,10 @@ class PasswordInput extends GOVUKFrontendComponent {
1789
2064
  if ($input.type !== 'password') {
1790
2065
  throw new ElementError('Password input: Form field (`.govuk-js-password-input-input`) must be of type `password`.');
1791
2066
  }
1792
- const $showHideButton = $module.querySelector('.govuk-js-password-input-toggle');
2067
+ const $showHideButton = this.$root.querySelector('.govuk-js-password-input-toggle');
1793
2068
  if (!($showHideButton instanceof HTMLButtonElement)) {
1794
2069
  throw new ElementError({
1795
- componentName: 'Password input',
2070
+ component: PasswordInput,
1796
2071
  element: $showHideButton,
1797
2072
  expectedType: 'HTMLButtonElement',
1798
2073
  identifier: 'Button (`.govuk-js-password-input-toggle`)'
@@ -1801,12 +2076,10 @@ class PasswordInput extends GOVUKFrontendComponent {
1801
2076
  if ($showHideButton.type !== 'button') {
1802
2077
  throw new ElementError('Password input: Button (`.govuk-js-password-input-toggle`) must be of type `button`.');
1803
2078
  }
1804
- this.$module = $module;
1805
2079
  this.$input = $input;
1806
2080
  this.$showHideButton = $showHideButton;
1807
- this.config = mergeConfigs(PasswordInput.defaults, config, normaliseDataset(PasswordInput, $module.dataset));
1808
2081
  this.i18n = new I18n(this.config.i18n, {
1809
- locale: closestAttributeValue($module, 'lang')
2082
+ locale: closestAttributeValue(this.$root, 'lang')
1810
2083
  });
1811
2084
  this.$showHideButton.removeAttribute('hidden');
1812
2085
  const $screenReaderStatusMessage = document.createElement('div');
@@ -1884,8 +2157,7 @@ class PasswordInput extends GOVUKFrontendComponent {
1884
2157
  */
1885
2158
 
1886
2159
  /**
1887
- * @typedef {import('../../common/index.mjs').Schema} Schema
1888
- * @typedef {import('../../i18n.mjs').TranslationPluralForms} TranslationPluralForms
2160
+ * @import { Schema } from '../../common/configuration.mjs'
1889
2161
  */
1890
2162
  PasswordInput.moduleName = 'govuk-password-input';
1891
2163
  PasswordInput.defaults = Object.freeze({
@@ -1911,7 +2183,7 @@ PasswordInput.schema = Object.freeze({
1911
2183
  *
1912
2184
  * @preserve
1913
2185
  */
1914
- class Radios extends GOVUKFrontendComponent {
2186
+ class Radios extends Component {
1915
2187
  /**
1916
2188
  * Radios can be associated with a 'conditionally revealed' content block –
1917
2189
  * for example, a radio for 'Phone' could reveal an additional form field for
@@ -1924,27 +2196,18 @@ class Radios extends GOVUKFrontendComponent {
1924
2196
  * (for example if the user has navigated back), and set up event handlers to
1925
2197
  * keep the reveal in sync with the radio state.
1926
2198
  *
1927
- * @param {Element | null} $module - HTML element to use for radios
2199
+ * @param {Element | null} $root - HTML element to use for radios
1928
2200
  */
1929
- constructor($module) {
1930
- super();
1931
- this.$module = void 0;
2201
+ constructor($root) {
2202
+ super($root);
1932
2203
  this.$inputs = void 0;
1933
- if (!($module instanceof HTMLElement)) {
1934
- throw new ElementError({
1935
- componentName: 'Radios',
1936
- element: $module,
1937
- identifier: 'Root element (`$module`)'
1938
- });
1939
- }
1940
- const $inputs = $module.querySelectorAll('input[type="radio"]');
2204
+ const $inputs = this.$root.querySelectorAll('input[type="radio"]');
1941
2205
  if (!$inputs.length) {
1942
2206
  throw new ElementError({
1943
- componentName: 'Radios',
2207
+ component: Radios,
1944
2208
  identifier: 'Form inputs (`<input type="radio">`)'
1945
2209
  });
1946
2210
  }
1947
- this.$module = $module;
1948
2211
  this.$inputs = $inputs;
1949
2212
  this.$inputs.forEach($input => {
1950
2213
  const targetId = $input.getAttribute('data-aria-controls');
@@ -1953,7 +2216,7 @@ class Radios extends GOVUKFrontendComponent {
1953
2216
  }
1954
2217
  if (!document.getElementById(targetId)) {
1955
2218
  throw new ElementError({
1956
- componentName: 'Radios',
2219
+ component: Radios,
1957
2220
  identifier: `Conditional reveal (\`id="${targetId}"\`)`
1958
2221
  });
1959
2222
  }
@@ -1962,7 +2225,7 @@ class Radios extends GOVUKFrontendComponent {
1962
2225
  });
1963
2226
  window.addEventListener('pageshow', () => this.syncAllConditionalReveals());
1964
2227
  this.syncAllConditionalReveals();
1965
- this.$module.addEventListener('click', event => this.handleClick(event));
2228
+ this.$root.addEventListener('click', event => this.handleClick(event));
1966
2229
  }
1967
2230
  syncAllConditionalReveals() {
1968
2231
  this.$inputs.forEach($input => this.syncConditionalRevealWithInputState($input));
@@ -1999,35 +2262,105 @@ class Radios extends GOVUKFrontendComponent {
1999
2262
  Radios.moduleName = 'govuk-radios';
2000
2263
 
2001
2264
  /**
2002
- * Skip link component
2265
+ * Service Navigation component
2003
2266
  *
2004
2267
  * @preserve
2005
2268
  */
2006
- class SkipLink extends GOVUKFrontendComponent {
2269
+ class ServiceNavigation extends Component {
2007
2270
  /**
2008
- * @param {Element | null} $module - HTML element to use for skip link
2009
- * @throws {ElementError} when $module is not set or the wrong type
2010
- * @throws {ElementError} when $module.hash does not contain a hash
2011
- * @throws {ElementError} when the linked element is missing or the wrong type
2271
+ * @param {Element | null} $root - HTML element to use for header
2012
2272
  */
2013
- constructor($module) {
2014
- var _this$$module$getAttr;
2015
- super();
2016
- this.$module = void 0;
2017
- if (!($module instanceof HTMLAnchorElement)) {
2273
+ constructor($root) {
2274
+ super($root);
2275
+ this.$menuButton = void 0;
2276
+ this.$menu = void 0;
2277
+ this.menuIsOpen = false;
2278
+ this.mql = null;
2279
+ const $menuButton = this.$root.querySelector('.govuk-js-service-navigation-toggle');
2280
+ if (!$menuButton) {
2281
+ return this;
2282
+ }
2283
+ const menuId = $menuButton.getAttribute('aria-controls');
2284
+ if (!menuId) {
2018
2285
  throw new ElementError({
2019
- componentName: 'Skip link',
2020
- element: $module,
2021
- expectedType: 'HTMLAnchorElement',
2022
- identifier: 'Root element (`$module`)'
2286
+ component: ServiceNavigation,
2287
+ identifier: 'Navigation button (`<button class="govuk-js-service-navigation-toggle">`) attribute (`aria-controls`)'
2288
+ });
2289
+ }
2290
+ const $menu = document.getElementById(menuId);
2291
+ if (!$menu) {
2292
+ throw new ElementError({
2293
+ component: ServiceNavigation,
2294
+ element: $menu,
2295
+ identifier: `Navigation (\`<ul id="${menuId}">\`)`
2296
+ });
2297
+ }
2298
+ this.$menu = $menu;
2299
+ this.$menuButton = $menuButton;
2300
+ this.setupResponsiveChecks();
2301
+ this.$menuButton.addEventListener('click', () => this.handleMenuButtonClick());
2302
+ }
2303
+ setupResponsiveChecks() {
2304
+ const breakpoint = getBreakpoint('tablet');
2305
+ if (!breakpoint.value) {
2306
+ throw new ElementError({
2307
+ component: ServiceNavigation,
2308
+ identifier: `CSS custom property (\`${breakpoint.property}\`) on pseudo-class \`:root\``
2023
2309
  });
2024
2310
  }
2025
- this.$module = $module;
2026
- const hash = this.$module.hash;
2027
- const href = (_this$$module$getAttr = this.$module.getAttribute('href')) != null ? _this$$module$getAttr : '';
2311
+ this.mql = window.matchMedia(`(min-width: ${breakpoint.value})`);
2312
+ if ('addEventListener' in this.mql) {
2313
+ this.mql.addEventListener('change', () => this.checkMode());
2314
+ } else {
2315
+ this.mql.addListener(() => this.checkMode());
2316
+ }
2317
+ this.checkMode();
2318
+ }
2319
+ checkMode() {
2320
+ if (!this.mql || !this.$menu || !this.$menuButton) {
2321
+ return;
2322
+ }
2323
+ if (this.mql.matches) {
2324
+ this.$menu.removeAttribute('hidden');
2325
+ this.$menuButton.setAttribute('hidden', '');
2326
+ } else {
2327
+ this.$menuButton.removeAttribute('hidden');
2328
+ this.$menuButton.setAttribute('aria-expanded', this.menuIsOpen.toString());
2329
+ if (this.menuIsOpen) {
2330
+ this.$menu.removeAttribute('hidden');
2331
+ } else {
2332
+ this.$menu.setAttribute('hidden', '');
2333
+ }
2334
+ }
2335
+ }
2336
+ handleMenuButtonClick() {
2337
+ this.menuIsOpen = !this.menuIsOpen;
2338
+ this.checkMode();
2339
+ }
2340
+ }
2341
+ ServiceNavigation.moduleName = 'govuk-service-navigation';
2342
+
2343
+ /**
2344
+ * Skip link component
2345
+ *
2346
+ * @preserve
2347
+ * @augments Component<HTMLAnchorElement>
2348
+ */
2349
+ class SkipLink extends Component {
2350
+ /**
2351
+ * @param {Element | null} $root - HTML element to use for skip link
2352
+ * @throws {ElementError} when $root is not set or the wrong type
2353
+ * @throws {ElementError} when $root.hash does not contain a hash
2354
+ * @throws {ElementError} when the linked element is missing or the wrong type
2355
+ */
2356
+ constructor($root) {
2357
+ var _this$$root$getAttrib;
2358
+ super($root);
2359
+ const hash = this.$root.hash;
2360
+ const href = (_this$$root$getAttrib = this.$root.getAttribute('href')) != null ? _this$$root$getAttrib : '';
2028
2361
  let url;
2029
2362
  try {
2030
- url = new window.URL(this.$module.href);
2363
+ url = new window.URL(this.$root.href);
2031
2364
  } catch (error) {
2032
2365
  throw new ElementError(`Skip link: Target link (\`href="${href}"\`) is invalid`);
2033
2366
  }
@@ -2041,12 +2374,12 @@ class SkipLink extends GOVUKFrontendComponent {
2041
2374
  const $linkedElement = document.getElementById(linkedElementId);
2042
2375
  if (!$linkedElement) {
2043
2376
  throw new ElementError({
2044
- componentName: 'Skip link',
2377
+ component: SkipLink,
2045
2378
  element: $linkedElement,
2046
2379
  identifier: `Target content (\`id="${linkedElementId}"\`)`
2047
2380
  });
2048
2381
  }
2049
- this.$module.addEventListener('click', () => setFocus($linkedElement, {
2382
+ this.$root.addEventListener('click', () => setFocus($linkedElement, {
2050
2383
  onBeforeFocus() {
2051
2384
  $linkedElement.classList.add('govuk-skip-link-focused-element');
2052
2385
  },
@@ -2056,6 +2389,7 @@ class SkipLink extends GOVUKFrontendComponent {
2056
2389
  }));
2057
2390
  }
2058
2391
  }
2392
+ SkipLink.elementType = HTMLAnchorElement;
2059
2393
  SkipLink.moduleName = 'govuk-skip-link';
2060
2394
 
2061
2395
  /**
@@ -2063,13 +2397,12 @@ SkipLink.moduleName = 'govuk-skip-link';
2063
2397
  *
2064
2398
  * @preserve
2065
2399
  */
2066
- class Tabs extends GOVUKFrontendComponent {
2400
+ class Tabs extends Component {
2067
2401
  /**
2068
- * @param {Element | null} $module - HTML element to use for tabs
2402
+ * @param {Element | null} $root - HTML element to use for tabs
2069
2403
  */
2070
- constructor($module) {
2071
- super();
2072
- this.$module = void 0;
2404
+ constructor($root) {
2405
+ super($root);
2073
2406
  this.$tabs = void 0;
2074
2407
  this.$tabList = void 0;
2075
2408
  this.$tabListItems = void 0;
@@ -2079,36 +2412,28 @@ class Tabs extends GOVUKFrontendComponent {
2079
2412
  this.boundTabKeydown = void 0;
2080
2413
  this.boundOnHashChange = void 0;
2081
2414
  this.mql = null;
2082
- if (!$module) {
2083
- throw new ElementError({
2084
- componentName: 'Tabs',
2085
- element: $module,
2086
- identifier: 'Root element (`$module`)'
2087
- });
2088
- }
2089
- const $tabs = $module.querySelectorAll('a.govuk-tabs__tab');
2415
+ const $tabs = this.$root.querySelectorAll('a.govuk-tabs__tab');
2090
2416
  if (!$tabs.length) {
2091
2417
  throw new ElementError({
2092
- componentName: 'Tabs',
2418
+ component: Tabs,
2093
2419
  identifier: 'Links (`<a class="govuk-tabs__tab">`)'
2094
2420
  });
2095
2421
  }
2096
- this.$module = $module;
2097
2422
  this.$tabs = $tabs;
2098
2423
  this.boundTabClick = this.onTabClick.bind(this);
2099
2424
  this.boundTabKeydown = this.onTabKeydown.bind(this);
2100
2425
  this.boundOnHashChange = this.onHashChange.bind(this);
2101
- const $tabList = this.$module.querySelector('.govuk-tabs__list');
2102
- const $tabListItems = this.$module.querySelectorAll('li.govuk-tabs__list-item');
2426
+ const $tabList = this.$root.querySelector('.govuk-tabs__list');
2427
+ const $tabListItems = this.$root.querySelectorAll('li.govuk-tabs__list-item');
2103
2428
  if (!$tabList) {
2104
2429
  throw new ElementError({
2105
- componentName: 'Tabs',
2430
+ component: Tabs,
2106
2431
  identifier: 'List (`<ul class="govuk-tabs__list">`)'
2107
2432
  });
2108
2433
  }
2109
2434
  if (!$tabListItems.length) {
2110
2435
  throw new ElementError({
2111
- componentName: 'Tabs',
2436
+ component: Tabs,
2112
2437
  identifier: 'List items (`<li class="govuk-tabs__list-item">`)'
2113
2438
  });
2114
2439
  }
@@ -2120,7 +2445,7 @@ class Tabs extends GOVUKFrontendComponent {
2120
2445
  const breakpoint = getBreakpoint('tablet');
2121
2446
  if (!breakpoint.value) {
2122
2447
  throw new ElementError({
2123
- componentName: 'Tabs',
2448
+ component: Tabs,
2124
2449
  identifier: `CSS custom property (\`${breakpoint.property}\`) on pseudo-class \`:root\``
2125
2450
  });
2126
2451
  }
@@ -2195,7 +2520,7 @@ class Tabs extends GOVUKFrontendComponent {
2195
2520
  this.showPanel($tab);
2196
2521
  }
2197
2522
  getTab(hash) {
2198
- return this.$module.querySelector(`a.govuk-tabs__tab[href="${hash}"]`);
2523
+ return this.$root.querySelector(`a.govuk-tabs__tab[href="${hash}"]`);
2199
2524
  }
2200
2525
  setAttributes($tab) {
2201
2526
  const panelId = getFragmentFromUrl($tab.href);
@@ -2254,16 +2579,12 @@ class Tabs extends GOVUKFrontendComponent {
2254
2579
  onTabKeydown(event) {
2255
2580
  switch (event.key) {
2256
2581
  case 'ArrowLeft':
2257
- case 'ArrowUp':
2258
2582
  case 'Left':
2259
- case 'Up':
2260
2583
  this.activatePreviousTab();
2261
2584
  event.preventDefault();
2262
2585
  break;
2263
2586
  case 'ArrowRight':
2264
- case 'ArrowDown':
2265
2587
  case 'Right':
2266
- case 'Down':
2267
2588
  this.activateNextTab();
2268
2589
  event.preventDefault();
2269
2590
  break;
@@ -2310,7 +2631,7 @@ class Tabs extends GOVUKFrontendComponent {
2310
2631
  if (!panelId) {
2311
2632
  return null;
2312
2633
  }
2313
- return this.$module.querySelector(`#${panelId}`);
2634
+ return this.$root.querySelector(`#${panelId}`);
2314
2635
  }
2315
2636
  showPanel($tab) {
2316
2637
  const $panel = this.getPanel($tab);
@@ -2343,7 +2664,7 @@ class Tabs extends GOVUKFrontendComponent {
2343
2664
  $tab.setAttribute('tabindex', '0');
2344
2665
  }
2345
2666
  getCurrentTab() {
2346
- return this.$module.querySelector('.govuk-tabs__list-item--selected a.govuk-tabs__tab');
2667
+ return this.$root.querySelector('.govuk-tabs__list-item--selected a.govuk-tabs__tab');
2347
2668
  }
2348
2669
  }
2349
2670
  Tabs.moduleName = 'govuk-tabs';
@@ -2354,19 +2675,28 @@ Tabs.moduleName = 'govuk-tabs';
2354
2675
  * Use the `data-module` attributes to find, instantiate and init all of the
2355
2676
  * components provided as part of GOV.UK Frontend.
2356
2677
  *
2357
- * @param {Config & { scope?: Element }} [config] - Config for all components (with optional scope)
2678
+ * @param {Config & { scope?: Element, onError?: OnErrorCallback<CompatibleClass> }} [config] - Config for all components (with optional scope)
2358
2679
  */
2359
2680
  function initAll(config) {
2360
2681
  var _config$scope;
2361
2682
  config = typeof config !== 'undefined' ? config : {};
2362
2683
  if (!isSupported()) {
2363
- console.log(new SupportError());
2684
+ if (config.onError) {
2685
+ config.onError(new SupportError(), {
2686
+ config
2687
+ });
2688
+ } else {
2689
+ console.log(new SupportError());
2690
+ }
2364
2691
  return;
2365
2692
  }
2366
- 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], [SkipLink], [Tabs]];
2367
- const $scope = (_config$scope = config.scope) != null ? _config$scope : document;
2693
+ const components = [[Accordion, config.accordion], [Button, config.button], [CharacterCount, config.characterCount], [Checkboxes], [ErrorSummary, config.errorSummary], [ExitThisPage, config.exitThisPage], [FileUpload, config.fileUpload], [Header], [NotificationBanner, config.notificationBanner], [PasswordInput, config.passwordInput], [Radios], [ServiceNavigation], [SkipLink], [Tabs]];
2694
+ const options = {
2695
+ scope: (_config$scope = config.scope) != null ? _config$scope : document,
2696
+ onError: config.onError
2697
+ };
2368
2698
  components.forEach(([Component, config]) => {
2369
- createAll(Component, config, $scope);
2699
+ createAll(Component, config, options);
2370
2700
  });
2371
2701
  }
2372
2702
 
@@ -2379,25 +2709,58 @@ function initAll(config) {
2379
2709
  *
2380
2710
  * Any component errors will be caught and logged to the console.
2381
2711
  *
2382
- * @template {CompatibleClass} T
2383
- * @param {T} Component - class of the component to create
2384
- * @param {T["defaults"]} [config] - config for the component
2385
- * @param {Element|Document} [$scope] - scope of the document to search within
2386
- * @returns {Array<InstanceType<T>>} - array of instantiated components
2712
+ * @template {CompatibleClass} ComponentClass
2713
+ * @param {ComponentClass} Component - class of the component to create
2714
+ * @param {ComponentConfig<ComponentClass>} [config] - Config supplied to component
2715
+ * @param {OnErrorCallback<ComponentClass> | Element | Document | CreateAllOptions<ComponentClass> } [createAllOptions] - options for createAll including scope of the document to search within and callback function if error throw by component on init
2716
+ * @returns {Array<InstanceType<ComponentClass>>} - array of instantiated components
2387
2717
  */
2388
- function createAll(Component, config, $scope = document) {
2718
+ function createAll(Component, config, createAllOptions) {
2719
+ let $scope = document;
2720
+ let onError;
2721
+ if (typeof createAllOptions === 'object') {
2722
+ var _createAllOptions$sco;
2723
+ createAllOptions = createAllOptions;
2724
+ $scope = (_createAllOptions$sco = createAllOptions.scope) != null ? _createAllOptions$sco : $scope;
2725
+ onError = createAllOptions.onError;
2726
+ }
2727
+ if (typeof createAllOptions === 'function') {
2728
+ onError = createAllOptions;
2729
+ }
2730
+ if (createAllOptions instanceof HTMLElement) {
2731
+ $scope = createAllOptions;
2732
+ }
2389
2733
  const $elements = $scope.querySelectorAll(`[data-module="${Component.moduleName}"]`);
2734
+ if (!isSupported()) {
2735
+ if (onError) {
2736
+ onError(new SupportError(), {
2737
+ component: Component,
2738
+ config
2739
+ });
2740
+ } else {
2741
+ console.log(new SupportError());
2742
+ }
2743
+ return [];
2744
+ }
2390
2745
  return Array.from($elements).map($element => {
2391
2746
  try {
2392
- return 'defaults' in Component && typeof config !== 'undefined' ? new Component($element, config) : new Component($element);
2747
+ return typeof config !== 'undefined' ? new Component($element, config) : new Component($element);
2393
2748
  } catch (error) {
2394
- console.log(error);
2749
+ if (onError) {
2750
+ onError(error, {
2751
+ element: $element,
2752
+ component: Component,
2753
+ config
2754
+ });
2755
+ } else {
2756
+ console.log(error);
2757
+ }
2395
2758
  return null;
2396
2759
  }
2397
2760
  }).filter(Boolean);
2398
2761
  }
2399
2762
  /**
2400
- * @typedef {{new (...args: any[]): any, defaults?: object, moduleName: string}} CompatibleClass
2763
+ * @typedef {{new (...args: any[]): any, moduleName: string}} CompatibleClass
2401
2764
  */
2402
2765
  /**
2403
2766
  * Config for all components via `initAll()`
@@ -2408,28 +2771,50 @@ function createAll(Component, config, $scope = document) {
2408
2771
  * @property {CharacterCountConfig} [characterCount] - Character Count config
2409
2772
  * @property {ErrorSummaryConfig} [errorSummary] - Error Summary config
2410
2773
  * @property {ExitThisPageConfig} [exitThisPage] - Exit This Page config
2774
+ * @property {FileUploadConfig} [fileUpload] - File Upload config
2411
2775
  * @property {NotificationBannerConfig} [notificationBanner] - Notification Banner config
2412
2776
  * @property {PasswordInputConfig} [passwordInput] - Password input config
2413
2777
  */
2414
2778
  /**
2415
2779
  * Config for individual components
2416
2780
  *
2417
- * @typedef {import('./components/accordion/accordion.mjs').AccordionConfig} AccordionConfig
2418
- * @typedef {import('./components/accordion/accordion.mjs').AccordionTranslations} AccordionTranslations
2419
- * @typedef {import('./components/button/button.mjs').ButtonConfig} ButtonConfig
2420
- * @typedef {import('./components/character-count/character-count.mjs').CharacterCountConfig} CharacterCountConfig
2421
- * @typedef {import('./components/character-count/character-count.mjs').CharacterCountTranslations} CharacterCountTranslations
2422
- * @typedef {import('./components/error-summary/error-summary.mjs').ErrorSummaryConfig} ErrorSummaryConfig
2423
- * @typedef {import('./components/exit-this-page/exit-this-page.mjs').ExitThisPageConfig} ExitThisPageConfig
2424
- * @typedef {import('./components/exit-this-page/exit-this-page.mjs').ExitThisPageTranslations} ExitThisPageTranslations
2425
- * @typedef {import('./components/notification-banner/notification-banner.mjs').NotificationBannerConfig} NotificationBannerConfig
2426
- * @typedef {import('./components/password-input/password-input.mjs').PasswordInputConfig} PasswordInputConfig
2781
+ * @import { AccordionConfig } from './components/accordion/accordion.mjs'
2782
+ * @import { ButtonConfig } from './components/button/button.mjs'
2783
+ * @import { CharacterCountConfig } from './components/character-count/character-count.mjs'
2784
+ * @import { ErrorSummaryConfig } from './components/error-summary/error-summary.mjs'
2785
+ * @import { ExitThisPageConfig } from './components/exit-this-page/exit-this-page.mjs'
2786
+ * @import { NotificationBannerConfig } from './components/notification-banner/notification-banner.mjs'
2787
+ * @import { PasswordInputConfig } from './components/password-input/password-input.mjs'
2788
+ * @import { FileUploadConfig } from './components/file-upload/file-upload.mjs'
2427
2789
  */
2428
2790
  /**
2429
2791
  * Component config keys, e.g. `accordion` and `characterCount`
2430
2792
  *
2431
2793
  * @typedef {keyof Config} ConfigKey
2432
2794
  */
2795
+ /**
2796
+ * @template {CompatibleClass} ComponentClass
2797
+ * @typedef {ConstructorParameters<ComponentClass>[1]} ComponentConfig
2798
+ */
2799
+ /**
2800
+ * @template {CompatibleClass} ComponentClass
2801
+ * @typedef {object} ErrorContext
2802
+ * @property {Element} [element] - Element used for component module initialisation
2803
+ * @property {ComponentClass} [component] - Class of component
2804
+ * @property {ComponentConfig<ComponentClass>} config - Config supplied to component
2805
+ */
2806
+ /**
2807
+ * @template {CompatibleClass} ComponentClass
2808
+ * @callback OnErrorCallback
2809
+ * @param {unknown} error - Thrown error
2810
+ * @param {ErrorContext<ComponentClass>} context - Object containing the element, component class and configuration
2811
+ */
2812
+ /**
2813
+ * @template {CompatibleClass} ComponentClass
2814
+ * @typedef {object} CreateAllOptions
2815
+ * @property {Element | Document} [scope] - scope of the document to search within
2816
+ * @property {OnErrorCallback<ComponentClass>} [onError] - callback function if error throw by component on init
2817
+ */
2433
2818
 
2434
- export { Accordion, Button, CharacterCount, Checkboxes, ErrorSummary, ExitThisPage, Header, NotificationBanner, PasswordInput, Radios, SkipLink, Tabs, createAll, initAll, version };
2819
+ export { Accordion, Button, CharacterCount, Checkboxes, Component, ConfigurableComponent, ErrorSummary, ExitThisPage, FileUpload, Header, NotificationBanner, PasswordInput, Radios, ServiceNavigation, SkipLink, Tabs, createAll, initAll, isSupported, version };
2435
2820
  //# sourceMappingURL=all.bundle.mjs.map