govuk_publishing_components 44.0.0 → 44.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (146) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/images/govuk_publishing_components/crests/dbt_crest_18px_x2.png +0 -0
  3. data/app/assets/images/govuk_publishing_components/crests/ho_crest_18px_x2.png +0 -0
  4. data/app/assets/images/govuk_publishing_components/crests/mod_crest_18px_x2.png +0 -0
  5. data/app/assets/images/govuk_publishing_components/crests/org_crest_18px_x2.png +0 -0
  6. data/app/assets/images/govuk_publishing_components/crests/portcullis_18px_x2.png +0 -0
  7. data/app/assets/images/govuk_publishing_components/crests/so_crest_18px_x2.png +0 -0
  8. data/app/assets/images/govuk_publishing_components/crests/wales_crest_18px_x2.png +0 -0
  9. data/lib/govuk_publishing_components/version.rb +1 -1
  10. data/node_modules/govuk-frontend/dist/govuk/all.bundle.js +336 -225
  11. data/node_modules/govuk-frontend/dist/govuk/all.bundle.js.map +1 -1
  12. data/node_modules/govuk-frontend/dist/govuk/all.bundle.mjs +334 -226
  13. data/node_modules/govuk-frontend/dist/govuk/all.bundle.mjs.map +1 -1
  14. data/node_modules/govuk-frontend/dist/govuk/all.mjs +3 -0
  15. data/node_modules/govuk-frontend/dist/govuk/all.mjs.map +1 -1
  16. data/node_modules/govuk-frontend/dist/govuk/assets/images/govuk-crest.svg +1 -0
  17. data/node_modules/govuk-frontend/dist/govuk/common/govuk-frontend-version.mjs +1 -1
  18. data/node_modules/govuk-frontend/dist/govuk/common/index.mjs +21 -1
  19. data/node_modules/govuk-frontend/dist/govuk/common/index.mjs.map +1 -1
  20. data/node_modules/govuk-frontend/dist/govuk/components/_index.scss +1 -0
  21. data/node_modules/govuk-frontend/dist/govuk/components/_index.scss.map +1 -1
  22. data/node_modules/govuk-frontend/dist/govuk/components/accordion/accordion.bundle.js +92 -26
  23. data/node_modules/govuk-frontend/dist/govuk/components/accordion/accordion.bundle.js.map +1 -1
  24. data/node_modules/govuk-frontend/dist/govuk/components/accordion/accordion.bundle.mjs +92 -26
  25. data/node_modules/govuk-frontend/dist/govuk/components/accordion/accordion.bundle.mjs.map +1 -1
  26. data/node_modules/govuk-frontend/dist/govuk/components/accordion/accordion.mjs +12 -21
  27. data/node_modules/govuk-frontend/dist/govuk/components/accordion/accordion.mjs.map +1 -1
  28. data/node_modules/govuk-frontend/dist/govuk/components/button/button.bundle.js +86 -20
  29. data/node_modules/govuk-frontend/dist/govuk/components/button/button.bundle.js.map +1 -1
  30. data/node_modules/govuk-frontend/dist/govuk/components/button/button.bundle.mjs +86 -20
  31. data/node_modules/govuk-frontend/dist/govuk/components/button/button.bundle.mjs.map +1 -1
  32. data/node_modules/govuk-frontend/dist/govuk/components/button/button.mjs +6 -16
  33. data/node_modules/govuk-frontend/dist/govuk/components/button/button.mjs.map +1 -1
  34. data/node_modules/govuk-frontend/dist/govuk/components/character-count/character-count.bundle.js +89 -23
  35. data/node_modules/govuk-frontend/dist/govuk/components/character-count/character-count.bundle.js.map +1 -1
  36. data/node_modules/govuk-frontend/dist/govuk/components/character-count/character-count.bundle.mjs +89 -23
  37. data/node_modules/govuk-frontend/dist/govuk/components/character-count/character-count.bundle.mjs.map +1 -1
  38. data/node_modules/govuk-frontend/dist/govuk/components/character-count/character-count.mjs +10 -19
  39. data/node_modules/govuk-frontend/dist/govuk/components/character-count/character-count.mjs.map +1 -1
  40. data/node_modules/govuk-frontend/dist/govuk/components/checkboxes/checkboxes.bundle.js +113 -47
  41. data/node_modules/govuk-frontend/dist/govuk/components/checkboxes/checkboxes.bundle.js.map +1 -1
  42. data/node_modules/govuk-frontend/dist/govuk/components/checkboxes/checkboxes.bundle.mjs +113 -47
  43. data/node_modules/govuk-frontend/dist/govuk/components/checkboxes/checkboxes.bundle.mjs.map +1 -1
  44. data/node_modules/govuk-frontend/dist/govuk/components/checkboxes/checkboxes.mjs +7 -16
  45. data/node_modules/govuk-frontend/dist/govuk/components/checkboxes/checkboxes.mjs.map +1 -1
  46. data/node_modules/govuk-frontend/dist/govuk/components/details/_index.scss +7 -2
  47. data/node_modules/govuk-frontend/dist/govuk/components/details/_index.scss.map +1 -1
  48. data/node_modules/govuk-frontend/dist/govuk/components/error-summary/error-summary.bundle.js +86 -20
  49. data/node_modules/govuk-frontend/dist/govuk/components/error-summary/error-summary.bundle.js.map +1 -1
  50. data/node_modules/govuk-frontend/dist/govuk/components/error-summary/error-summary.bundle.mjs +86 -20
  51. data/node_modules/govuk-frontend/dist/govuk/components/error-summary/error-summary.bundle.mjs.map +1 -1
  52. data/node_modules/govuk-frontend/dist/govuk/components/error-summary/error-summary.mjs +6 -16
  53. data/node_modules/govuk-frontend/dist/govuk/components/error-summary/error-summary.mjs.map +1 -1
  54. data/node_modules/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.bundle.js +87 -21
  55. data/node_modules/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.bundle.js.map +1 -1
  56. data/node_modules/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.bundle.mjs +87 -21
  57. data/node_modules/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.bundle.mjs.map +1 -1
  58. data/node_modules/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.mjs +7 -16
  59. data/node_modules/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.mjs.map +1 -1
  60. data/node_modules/govuk-frontend/dist/govuk/components/footer/_index.scss +8 -10
  61. data/node_modules/govuk-frontend/dist/govuk/components/footer/_index.scss.map +1 -1
  62. data/node_modules/govuk-frontend/dist/govuk/components/header/_index.scss +8 -0
  63. data/node_modules/govuk-frontend/dist/govuk/components/header/_index.scss.map +1 -1
  64. data/node_modules/govuk-frontend/dist/govuk/components/header/fixtures.json +12 -0
  65. data/node_modules/govuk-frontend/dist/govuk/components/header/header.bundle.js +87 -21
  66. data/node_modules/govuk-frontend/dist/govuk/components/header/header.bundle.js.map +1 -1
  67. data/node_modules/govuk-frontend/dist/govuk/components/header/header.bundle.mjs +87 -21
  68. data/node_modules/govuk-frontend/dist/govuk/components/header/header.bundle.mjs.map +1 -1
  69. data/node_modules/govuk-frontend/dist/govuk/components/header/header.mjs +7 -16
  70. data/node_modules/govuk-frontend/dist/govuk/components/header/header.mjs.map +1 -1
  71. data/node_modules/govuk-frontend/dist/govuk/components/header/template-with-full-width-border.html +24 -0
  72. data/node_modules/govuk-frontend/dist/govuk/components/notification-banner/notification-banner.bundle.js +86 -20
  73. data/node_modules/govuk-frontend/dist/govuk/components/notification-banner/notification-banner.bundle.js.map +1 -1
  74. data/node_modules/govuk-frontend/dist/govuk/components/notification-banner/notification-banner.bundle.mjs +86 -20
  75. data/node_modules/govuk-frontend/dist/govuk/components/notification-banner/notification-banner.bundle.mjs.map +1 -1
  76. data/node_modules/govuk-frontend/dist/govuk/components/notification-banner/notification-banner.mjs +6 -16
  77. data/node_modules/govuk-frontend/dist/govuk/components/notification-banner/notification-banner.mjs.map +1 -1
  78. data/node_modules/govuk-frontend/dist/govuk/components/password-input/password-input.bundle.js +89 -23
  79. data/node_modules/govuk-frontend/dist/govuk/components/password-input/password-input.bundle.js.map +1 -1
  80. data/node_modules/govuk-frontend/dist/govuk/components/password-input/password-input.bundle.mjs +89 -23
  81. data/node_modules/govuk-frontend/dist/govuk/components/password-input/password-input.bundle.mjs.map +1 -1
  82. data/node_modules/govuk-frontend/dist/govuk/components/password-input/password-input.mjs +9 -18
  83. data/node_modules/govuk-frontend/dist/govuk/components/password-input/password-input.mjs.map +1 -1
  84. data/node_modules/govuk-frontend/dist/govuk/components/radios/radios.bundle.js +113 -47
  85. data/node_modules/govuk-frontend/dist/govuk/components/radios/radios.bundle.js.map +1 -1
  86. data/node_modules/govuk-frontend/dist/govuk/components/radios/radios.bundle.mjs +113 -47
  87. data/node_modules/govuk-frontend/dist/govuk/components/radios/radios.bundle.mjs.map +1 -1
  88. data/node_modules/govuk-frontend/dist/govuk/components/radios/radios.mjs +7 -16
  89. data/node_modules/govuk-frontend/dist/govuk/components/radios/radios.mjs.map +1 -1
  90. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/README.md +15 -0
  91. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/_index.scss +168 -0
  92. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/_index.scss.map +1 -0
  93. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/_service-navigation.scss +4 -0
  94. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/_service-navigation.scss.map +1 -0
  95. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/fixtures.json +464 -0
  96. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/macro-options.json +138 -0
  97. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/macro.njk +3 -0
  98. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/service-navigation.bundle.js +249 -0
  99. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/service-navigation.bundle.js.map +1 -0
  100. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/service-navigation.bundle.mjs +241 -0
  101. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/service-navigation.bundle.mjs.map +1 -0
  102. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/service-navigation.mjs +85 -0
  103. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/service-navigation.mjs.map +1 -0
  104. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/template-default.html +57 -0
  105. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/template-with-html-navigation-items.html +49 -0
  106. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/template-with-large-navigation.html +153 -0
  107. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/template-with-long-service-name.html +20 -0
  108. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/template-with-navigation-with-a-current-item.html +58 -0
  109. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/template-with-navigation-with-an-active-item.html +58 -0
  110. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/template-with-non-link-navigation-items.html +49 -0
  111. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/template-with-service-link.html +20 -0
  112. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/template-with-service-name-and-navigation.html +63 -0
  113. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/template-with-service-name.html +18 -0
  114. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/template.njk +102 -0
  115. data/node_modules/govuk-frontend/dist/govuk/components/skip-link/skip-link.bundle.js +93 -26
  116. data/node_modules/govuk-frontend/dist/govuk/components/skip-link/skip-link.bundle.js.map +1 -1
  117. data/node_modules/govuk-frontend/dist/govuk/components/skip-link/skip-link.bundle.mjs +93 -26
  118. data/node_modules/govuk-frontend/dist/govuk/components/skip-link/skip-link.bundle.mjs.map +1 -1
  119. data/node_modules/govuk-frontend/dist/govuk/components/skip-link/skip-link.mjs +13 -21
  120. data/node_modules/govuk-frontend/dist/govuk/components/skip-link/skip-link.mjs.map +1 -1
  121. data/node_modules/govuk-frontend/dist/govuk/components/tabs/tabs.bundle.js +93 -27
  122. data/node_modules/govuk-frontend/dist/govuk/components/tabs/tabs.bundle.js.map +1 -1
  123. data/node_modules/govuk-frontend/dist/govuk/components/tabs/tabs.bundle.mjs +93 -27
  124. data/node_modules/govuk-frontend/dist/govuk/components/tabs/tabs.bundle.mjs.map +1 -1
  125. data/node_modules/govuk-frontend/dist/govuk/components/tabs/tabs.mjs +13 -22
  126. data/node_modules/govuk-frontend/dist/govuk/components/tabs/tabs.mjs.map +1 -1
  127. data/node_modules/govuk-frontend/dist/govuk/components/warning-text/_index.scss +4 -3
  128. data/node_modules/govuk-frontend/dist/govuk/components/warning-text/_index.scss.map +1 -1
  129. data/node_modules/govuk-frontend/dist/govuk/core/_govuk-frontend-properties.scss +1 -1
  130. data/node_modules/govuk-frontend/dist/govuk/errors/index.mjs +16 -3
  131. data/node_modules/govuk-frontend/dist/govuk/errors/index.mjs.map +1 -1
  132. data/node_modules/govuk-frontend/dist/govuk/govuk-frontend-component.mjs +49 -5
  133. data/node_modules/govuk-frontend/dist/govuk/govuk-frontend-component.mjs.map +1 -1
  134. data/node_modules/govuk-frontend/dist/govuk/govuk-frontend.min.css +2 -2
  135. data/node_modules/govuk-frontend/dist/govuk/govuk-frontend.min.css.map +1 -1
  136. data/node_modules/govuk-frontend/dist/govuk/govuk-frontend.min.js +1 -1
  137. data/node_modules/govuk-frontend/dist/govuk/govuk-frontend.min.js.map +1 -1
  138. data/node_modules/govuk-frontend/dist/govuk/init.mjs +72 -10
  139. data/node_modules/govuk-frontend/dist/govuk/init.mjs.map +1 -1
  140. data/node_modules/govuk-frontend/dist/govuk/settings/_colours-organisations.scss +3 -0
  141. data/node_modules/govuk-frontend/dist/govuk/settings/_colours-organisations.scss.map +1 -1
  142. data/node_modules/govuk-frontend/govuk-prototype-kit.config.json +5 -1
  143. data/node_modules/govuk-frontend/package.json +8 -8
  144. metadata +29 -4
  145. data/node_modules/govuk-frontend/dist/govuk/assets/images/govuk-crest-2x.png +0 -0
  146. data/node_modules/govuk-frontend/dist/govuk/assets/images/govuk-crest.png +0 -0
@@ -1,4 +1,4 @@
1
- const version = '5.5.0';
1
+ const version = '5.7.0';
2
2
 
3
3
  function normaliseString(value, property) {
4
4
  const trimmedValue = value ? value.trim() : '';
@@ -108,6 +108,19 @@ function setFocus($element, options = {}) {
108
108
  (_options$onBeforeFocu = options.onBeforeFocus) == null || _options$onBeforeFocu.call($element);
109
109
  $element.focus();
110
110
  }
111
+ function isInitialised($root, moduleName) {
112
+ return $root instanceof HTMLElement && $root.hasAttribute(`data-${moduleName}-init`);
113
+ }
114
+
115
+ /**
116
+ * Checks if GOV.UK Frontend is supported on this page
117
+ *
118
+ * Some browsers will load and run our JavaScript but GOV.UK Frontend
119
+ * won't be supported.
120
+ *
121
+ * @param {HTMLElement | null} [$scope] - (internal) `<body>` HTML element checked for browser support
122
+ * @returns {boolean} Whether GOV.UK Frontend is supported on this page
123
+ */
111
124
  function isSupported($scope = document.body) {
112
125
  if (!$scope) {
113
126
  return false;
@@ -140,6 +153,9 @@ function isArray(option) {
140
153
  function isObject(option) {
141
154
  return !!option && typeof option === 'object' && !isArray(option);
142
155
  }
156
+ function formatErrorMessage(Component, message) {
157
+ return `${Component.moduleName}: ${message}`;
158
+ }
143
159
 
144
160
  /**
145
161
  * Schema for component config
@@ -163,6 +179,10 @@ function isObject(option) {
163
179
  * @property {string[]} required - List of required config fields
164
180
  * @property {string} errorMessage - Error message when required config fields not provided
165
181
  */
182
+ /**
183
+ * @typedef ComponentWithModuleName
184
+ * @property {string} moduleName - Name of the component
185
+ */
166
186
 
167
187
  function normaliseDataset(Component, dataset) {
168
188
  const out = {};
@@ -206,30 +226,85 @@ class ElementError extends GOVUKFrontendError {
206
226
  let message = typeof messageOrOptions === 'string' ? messageOrOptions : '';
207
227
  if (typeof messageOrOptions === 'object') {
208
228
  const {
209
- componentName,
229
+ component,
210
230
  identifier,
211
231
  element,
212
232
  expectedType
213
233
  } = messageOrOptions;
214
- message = `${componentName}: ${identifier}`;
234
+ message = identifier;
215
235
  message += element ? ` is not of type ${expectedType != null ? expectedType : 'HTMLElement'}` : ' not found';
236
+ message = formatErrorMessage(component, message);
216
237
  }
217
238
  super(message);
218
239
  this.name = 'ElementError';
219
240
  }
220
241
  }
242
+ class InitError extends GOVUKFrontendError {
243
+ constructor(componentOrMessage) {
244
+ const message = typeof componentOrMessage === 'string' ? componentOrMessage : formatErrorMessage(componentOrMessage, `Root element (\`$root\`) already initialised`);
245
+ super(message);
246
+ this.name = 'InitError';
247
+ }
248
+ }
249
+ /**
250
+ * @typedef {import('../common/index.mjs').ComponentWithModuleName} ComponentWithModuleName
251
+ */
221
252
 
222
253
  class GOVUKFrontendComponent {
223
- constructor() {
224
- this.checkSupport();
254
+ /**
255
+ * Returns the root element of the component
256
+ *
257
+ * @protected
258
+ * @returns {RootElementType} - the root element of component
259
+ */
260
+ get $root() {
261
+ return this._$root;
262
+ }
263
+ constructor($root) {
264
+ this._$root = void 0;
265
+ const childConstructor = this.constructor;
266
+ if (typeof childConstructor.moduleName !== 'string') {
267
+ throw new InitError(`\`moduleName\` not defined in component`);
268
+ }
269
+ if (!($root instanceof childConstructor.elementType)) {
270
+ throw new ElementError({
271
+ element: $root,
272
+ component: childConstructor,
273
+ identifier: 'Root element (`$root`)',
274
+ expectedType: childConstructor.elementType.name
275
+ });
276
+ } else {
277
+ this._$root = $root;
278
+ }
279
+ childConstructor.checkSupport();
280
+ this.checkInitialised();
281
+ const moduleName = childConstructor.moduleName;
282
+ this.$root.setAttribute(`data-${moduleName}-init`, '');
283
+ }
284
+ checkInitialised() {
285
+ const constructor = this.constructor;
286
+ const moduleName = constructor.moduleName;
287
+ if (moduleName && isInitialised(this.$root, moduleName)) {
288
+ throw new InitError(constructor);
289
+ }
225
290
  }
226
- checkSupport() {
291
+ static checkSupport() {
227
292
  if (!isSupported()) {
228
293
  throw new SupportError();
229
294
  }
230
295
  }
231
296
  }
232
297
 
298
+ /**
299
+ * @typedef ChildClass
300
+ * @property {string} moduleName - The module name that'll be looked for in the DOM when initialising the component
301
+ */
302
+
303
+ /**
304
+ * @typedef {typeof GOVUKFrontendComponent & ChildClass} ChildClassConstructor
305
+ */
306
+ GOVUKFrontendComponent.elementType = HTMLElement;
307
+
233
308
  class I18n {
234
309
  constructor(translations = {}, config = {}) {
235
310
  var _config$locale;
@@ -439,12 +514,11 @@ I18n.pluralRules = {
439
514
  */
440
515
  class Accordion extends GOVUKFrontendComponent {
441
516
  /**
442
- * @param {Element | null} $module - HTML element to use for accordion
517
+ * @param {Element | null} $root - HTML element to use for accordion
443
518
  * @param {AccordionConfig} [config] - Accordion config
444
519
  */
445
- constructor($module, config = {}) {
446
- super();
447
- this.$module = void 0;
520
+ constructor($root, config = {}) {
521
+ super($root);
448
522
  this.config = void 0;
449
523
  this.i18n = void 0;
450
524
  this.controlsClass = 'govuk-accordion__controls';
@@ -470,20 +544,12 @@ class Accordion extends GOVUKFrontendComponent {
470
544
  this.$showAllButton = null;
471
545
  this.$showAllIcon = null;
472
546
  this.$showAllText = null;
473
- if (!($module instanceof HTMLElement)) {
474
- throw new ElementError({
475
- componentName: 'Accordion',
476
- element: $module,
477
- identifier: 'Root element (`$module`)'
478
- });
479
- }
480
- this.$module = $module;
481
- this.config = mergeConfigs(Accordion.defaults, config, normaliseDataset(Accordion, $module.dataset));
547
+ this.config = mergeConfigs(Accordion.defaults, config, normaliseDataset(Accordion, this.$root.dataset));
482
548
  this.i18n = new I18n(this.config.i18n);
483
- const $sections = this.$module.querySelectorAll(`.${this.sectionClass}`);
549
+ const $sections = this.$root.querySelectorAll(`.${this.sectionClass}`);
484
550
  if (!$sections.length) {
485
551
  throw new ElementError({
486
- componentName: 'Accordion',
552
+ component: Accordion,
487
553
  identifier: `Sections (\`<div class="${this.sectionClass}">\`)`
488
554
  });
489
555
  }
@@ -503,7 +569,7 @@ class Accordion extends GOVUKFrontendComponent {
503
569
  const $accordionControls = document.createElement('div');
504
570
  $accordionControls.setAttribute('class', this.controlsClass);
505
571
  $accordionControls.appendChild(this.$showAllButton);
506
- this.$module.insertBefore($accordionControls, this.$module.firstChild);
572
+ this.$root.insertBefore($accordionControls, this.$root.firstChild);
507
573
  this.$showAllText = document.createElement('span');
508
574
  this.$showAllText.classList.add(this.showAllTextClass);
509
575
  this.$showAllButton.appendChild(this.$showAllText);
@@ -517,7 +583,7 @@ class Accordion extends GOVUKFrontendComponent {
517
583
  const $header = $section.querySelector(`.${this.sectionHeaderClass}`);
518
584
  if (!$header) {
519
585
  throw new ElementError({
520
- componentName: 'Accordion',
586
+ component: Accordion,
521
587
  identifier: `Section headers (\`<div class="${this.sectionHeaderClass}">\`)`
522
588
  });
523
589
  }
@@ -533,19 +599,19 @@ class Accordion extends GOVUKFrontendComponent {
533
599
  const $summary = $header.querySelector(`.${this.sectionSummaryClass}`);
534
600
  if (!$heading) {
535
601
  throw new ElementError({
536
- componentName: 'Accordion',
602
+ component: Accordion,
537
603
  identifier: `Section heading (\`.${this.sectionHeadingClass}\`)`
538
604
  });
539
605
  }
540
606
  if (!$span) {
541
607
  throw new ElementError({
542
- componentName: 'Accordion',
608
+ component: Accordion,
543
609
  identifier: `Section button placeholder (\`<span class="${this.sectionButtonClass}">\`)`
544
610
  });
545
611
  }
546
612
  const $button = document.createElement('button');
547
613
  $button.setAttribute('type', 'button');
548
- $button.setAttribute('aria-controls', `${this.$module.id}-content-${index + 1}`);
614
+ $button.setAttribute('aria-controls', `${this.$root.id}-content-${index + 1}`);
549
615
  for (const attr of Array.from($span.attributes)) {
550
616
  if (attr.name !== 'id') {
551
617
  $button.setAttribute(attr.name, attr.value);
@@ -619,7 +685,7 @@ class Accordion extends GOVUKFrontendComponent {
619
685
  const $content = $section.querySelector(`.${this.sectionContentClass}`);
620
686
  if (!$content) {
621
687
  throw new ElementError({
622
- componentName: 'Accordion',
688
+ component: Accordion,
623
689
  identifier: `Section content (\`<div class="${this.sectionContentClass}">\`)`
624
690
  });
625
691
  }
@@ -782,25 +848,16 @@ const DEBOUNCE_TIMEOUT_IN_SECONDS = 1;
782
848
  */
783
849
  class Button extends GOVUKFrontendComponent {
784
850
  /**
785
- * @param {Element | null} $module - HTML element to use for button
851
+ * @param {Element | null} $root - HTML element to use for button
786
852
  * @param {ButtonConfig} [config] - Button config
787
853
  */
788
- constructor($module, config = {}) {
789
- super();
790
- this.$module = void 0;
854
+ constructor($root, config = {}) {
855
+ super($root);
791
856
  this.config = void 0;
792
857
  this.debounceFormSubmitTimer = null;
793
- if (!($module instanceof HTMLElement)) {
794
- throw new ElementError({
795
- componentName: 'Button',
796
- element: $module,
797
- identifier: 'Root element (`$module`)'
798
- });
799
- }
800
- this.$module = $module;
801
- this.config = mergeConfigs(Button.defaults, config, normaliseDataset(Button, $module.dataset));
802
- this.$module.addEventListener('keydown', event => this.handleKeyDown(event));
803
- this.$module.addEventListener('click', event => this.debounce(event));
858
+ this.config = mergeConfigs(Button.defaults, config, normaliseDataset(Button, this.$root.dataset));
859
+ this.$root.addEventListener('keydown', event => this.handleKeyDown(event));
860
+ this.$root.addEventListener('click', event => this.debounce(event));
804
861
  }
805
862
  handleKeyDown(event) {
806
863
  const $target = event.target;
@@ -868,13 +925,12 @@ function closestAttributeValue($element, attributeName) {
868
925
  */
869
926
  class CharacterCount extends GOVUKFrontendComponent {
870
927
  /**
871
- * @param {Element | null} $module - HTML element to use for character count
928
+ * @param {Element | null} $root - HTML element to use for character count
872
929
  * @param {CharacterCountConfig} [config] - Character count config
873
930
  */
874
- constructor($module, config = {}) {
931
+ constructor($root, config = {}) {
875
932
  var _ref, _this$config$maxwords;
876
- super();
877
- this.$module = void 0;
933
+ super($root);
878
934
  this.$textarea = void 0;
879
935
  this.$visibleCountMessage = void 0;
880
936
  this.$screenReaderCountMessage = void 0;
@@ -884,23 +940,16 @@ class CharacterCount extends GOVUKFrontendComponent {
884
940
  this.config = void 0;
885
941
  this.i18n = void 0;
886
942
  this.maxLength = void 0;
887
- if (!($module instanceof HTMLElement)) {
888
- throw new ElementError({
889
- componentName: 'Character count',
890
- element: $module,
891
- identifier: 'Root element (`$module`)'
892
- });
893
- }
894
- const $textarea = $module.querySelector('.govuk-js-character-count');
943
+ const $textarea = this.$root.querySelector('.govuk-js-character-count');
895
944
  if (!($textarea instanceof HTMLTextAreaElement || $textarea instanceof HTMLInputElement)) {
896
945
  throw new ElementError({
897
- componentName: 'Character count',
946
+ component: CharacterCount,
898
947
  element: $textarea,
899
948
  expectedType: 'HTMLTextareaElement or HTMLInputElement',
900
949
  identifier: 'Form field (`.govuk-js-character-count`)'
901
950
  });
902
951
  }
903
- const datasetConfig = normaliseDataset(CharacterCount, $module.dataset);
952
+ const datasetConfig = normaliseDataset(CharacterCount, this.$root.dataset);
904
953
  let configOverrides = {};
905
954
  if ('maxwords' in datasetConfig || 'maxlength' in datasetConfig) {
906
955
  configOverrides = {
@@ -911,19 +960,18 @@ class CharacterCount extends GOVUKFrontendComponent {
911
960
  this.config = mergeConfigs(CharacterCount.defaults, config, configOverrides, datasetConfig);
912
961
  const errors = validateConfig(CharacterCount.schema, this.config);
913
962
  if (errors[0]) {
914
- throw new ConfigError(`Character count: ${errors[0]}`);
963
+ throw new ConfigError(formatErrorMessage(CharacterCount, errors[0]));
915
964
  }
916
965
  this.i18n = new I18n(this.config.i18n, {
917
- locale: closestAttributeValue($module, 'lang')
966
+ locale: closestAttributeValue(this.$root, 'lang')
918
967
  });
919
968
  this.maxLength = (_ref = (_this$config$maxwords = this.config.maxwords) != null ? _this$config$maxwords : this.config.maxlength) != null ? _ref : Infinity;
920
- this.$module = $module;
921
969
  this.$textarea = $textarea;
922
970
  const textareaDescriptionId = `${this.$textarea.id}-info`;
923
971
  const $textareaDescription = document.getElementById(textareaDescriptionId);
924
972
  if (!$textareaDescription) {
925
973
  throw new ElementError({
926
- componentName: 'Character count',
974
+ component: CharacterCount,
927
975
  element: $textareaDescription,
928
976
  identifier: `Count message (\`id="${textareaDescriptionId}"\`)`
929
977
  });
@@ -1167,27 +1215,18 @@ class Checkboxes extends GOVUKFrontendComponent {
1167
1215
  * (for example if the user has navigated back), and set up event handlers to
1168
1216
  * keep the reveal in sync with the checkbox state.
1169
1217
  *
1170
- * @param {Element | null} $module - HTML element to use for checkboxes
1218
+ * @param {Element | null} $root - HTML element to use for checkboxes
1171
1219
  */
1172
- constructor($module) {
1173
- super();
1174
- this.$module = void 0;
1220
+ constructor($root) {
1221
+ super($root);
1175
1222
  this.$inputs = void 0;
1176
- if (!($module instanceof HTMLElement)) {
1177
- throw new ElementError({
1178
- componentName: 'Checkboxes',
1179
- element: $module,
1180
- identifier: 'Root element (`$module`)'
1181
- });
1182
- }
1183
- const $inputs = $module.querySelectorAll('input[type="checkbox"]');
1223
+ const $inputs = this.$root.querySelectorAll('input[type="checkbox"]');
1184
1224
  if (!$inputs.length) {
1185
1225
  throw new ElementError({
1186
- componentName: 'Checkboxes',
1226
+ component: Checkboxes,
1187
1227
  identifier: 'Form inputs (`<input type="checkbox">`)'
1188
1228
  });
1189
1229
  }
1190
- this.$module = $module;
1191
1230
  this.$inputs = $inputs;
1192
1231
  this.$inputs.forEach($input => {
1193
1232
  const targetId = $input.getAttribute('data-aria-controls');
@@ -1196,7 +1235,7 @@ class Checkboxes extends GOVUKFrontendComponent {
1196
1235
  }
1197
1236
  if (!document.getElementById(targetId)) {
1198
1237
  throw new ElementError({
1199
- componentName: 'Checkboxes',
1238
+ component: Checkboxes,
1200
1239
  identifier: `Conditional reveal (\`id="${targetId}"\`)`
1201
1240
  });
1202
1241
  }
@@ -1205,7 +1244,7 @@ class Checkboxes extends GOVUKFrontendComponent {
1205
1244
  });
1206
1245
  window.addEventListener('pageshow', () => this.syncAllConditionalReveals());
1207
1246
  this.syncAllConditionalReveals();
1208
- this.$module.addEventListener('click', event => this.handleClick(event));
1247
+ this.$root.addEventListener('click', event => this.handleClick(event));
1209
1248
  }
1210
1249
  syncAllConditionalReveals() {
1211
1250
  this.$inputs.forEach($input => this.syncConditionalRevealWithInputState($input));
@@ -1274,26 +1313,17 @@ Checkboxes.moduleName = 'govuk-checkboxes';
1274
1313
  */
1275
1314
  class ErrorSummary extends GOVUKFrontendComponent {
1276
1315
  /**
1277
- * @param {Element | null} $module - HTML element to use for error summary
1316
+ * @param {Element | null} $root - HTML element to use for error summary
1278
1317
  * @param {ErrorSummaryConfig} [config] - Error summary config
1279
1318
  */
1280
- constructor($module, config = {}) {
1281
- super();
1282
- this.$module = void 0;
1319
+ constructor($root, config = {}) {
1320
+ super($root);
1283
1321
  this.config = void 0;
1284
- if (!($module instanceof HTMLElement)) {
1285
- throw new ElementError({
1286
- componentName: 'Error summary',
1287
- element: $module,
1288
- identifier: 'Root element (`$module`)'
1289
- });
1290
- }
1291
- this.$module = $module;
1292
- this.config = mergeConfigs(ErrorSummary.defaults, config, normaliseDataset(ErrorSummary, $module.dataset));
1322
+ this.config = mergeConfigs(ErrorSummary.defaults, config, normaliseDataset(ErrorSummary, this.$root.dataset));
1293
1323
  if (!this.config.disableAutoFocus) {
1294
- setFocus(this.$module);
1324
+ setFocus(this.$root);
1295
1325
  }
1296
- this.$module.addEventListener('click', event => this.handleClick(event));
1326
+ this.$root.addEventListener('click', event => this.handleClick(event));
1297
1327
  }
1298
1328
  handleClick(event) {
1299
1329
  const $target = event.target;
@@ -1377,12 +1407,11 @@ ErrorSummary.schema = Object.freeze({
1377
1407
  */
1378
1408
  class ExitThisPage extends GOVUKFrontendComponent {
1379
1409
  /**
1380
- * @param {Element | null} $module - HTML element that wraps the Exit This Page button
1410
+ * @param {Element | null} $root - HTML element that wraps the Exit This Page button
1381
1411
  * @param {ExitThisPageConfig} [config] - Exit This Page config
1382
1412
  */
1383
- constructor($module, config = {}) {
1384
- super();
1385
- this.$module = void 0;
1413
+ constructor($root, config = {}) {
1414
+ super($root);
1386
1415
  this.config = void 0;
1387
1416
  this.i18n = void 0;
1388
1417
  this.$button = void 0;
@@ -1395,25 +1424,17 @@ class ExitThisPage extends GOVUKFrontendComponent {
1395
1424
  this.timeoutTime = 5000;
1396
1425
  this.keypressTimeoutId = null;
1397
1426
  this.timeoutMessageId = null;
1398
- if (!($module instanceof HTMLElement)) {
1399
- throw new ElementError({
1400
- componentName: 'Exit this page',
1401
- element: $module,
1402
- identifier: 'Root element (`$module`)'
1403
- });
1404
- }
1405
- const $button = $module.querySelector('.govuk-exit-this-page__button');
1427
+ const $button = this.$root.querySelector('.govuk-exit-this-page__button');
1406
1428
  if (!($button instanceof HTMLAnchorElement)) {
1407
1429
  throw new ElementError({
1408
- componentName: 'Exit this page',
1430
+ component: ExitThisPage,
1409
1431
  element: $button,
1410
1432
  expectedType: 'HTMLAnchorElement',
1411
1433
  identifier: 'Button (`.govuk-exit-this-page__button`)'
1412
1434
  });
1413
1435
  }
1414
- this.config = mergeConfigs(ExitThisPage.defaults, config, normaliseDataset(ExitThisPage, $module.dataset));
1436
+ this.config = mergeConfigs(ExitThisPage.defaults, config, normaliseDataset(ExitThisPage, this.$root.dataset));
1415
1437
  this.i18n = new I18n(this.config.i18n);
1416
- this.$module = $module;
1417
1438
  this.$button = $button;
1418
1439
  const $skiplinkButton = document.querySelector('.govuk-js-exit-this-page-skiplink');
1419
1440
  if ($skiplinkButton instanceof HTMLAnchorElement) {
@@ -1432,7 +1453,7 @@ class ExitThisPage extends GOVUKFrontendComponent {
1432
1453
  this.$updateSpan = document.createElement('span');
1433
1454
  this.$updateSpan.setAttribute('role', 'status');
1434
1455
  this.$updateSpan.className = 'govuk-visually-hidden';
1435
- this.$module.appendChild(this.$updateSpan);
1456
+ this.$root.appendChild(this.$updateSpan);
1436
1457
  }
1437
1458
  initButtonClickHandler() {
1438
1459
  this.$button.addEventListener('click', this.handleClick.bind(this));
@@ -1607,38 +1628,29 @@ class Header extends GOVUKFrontendComponent {
1607
1628
  * Apply a matchMedia for desktop which will trigger a state sync if the
1608
1629
  * browser viewport moves between states.
1609
1630
  *
1610
- * @param {Element | null} $module - HTML element to use for header
1631
+ * @param {Element | null} $root - HTML element to use for header
1611
1632
  */
1612
- constructor($module) {
1613
- super();
1614
- this.$module = void 0;
1633
+ constructor($root) {
1634
+ super($root);
1615
1635
  this.$menuButton = void 0;
1616
1636
  this.$menu = void 0;
1617
1637
  this.menuIsOpen = false;
1618
1638
  this.mql = null;
1619
- if (!$module) {
1620
- throw new ElementError({
1621
- componentName: 'Header',
1622
- element: $module,
1623
- identifier: 'Root element (`$module`)'
1624
- });
1625
- }
1626
- this.$module = $module;
1627
- const $menuButton = $module.querySelector('.govuk-js-header-toggle');
1639
+ const $menuButton = this.$root.querySelector('.govuk-js-header-toggle');
1628
1640
  if (!$menuButton) {
1629
1641
  return this;
1630
1642
  }
1631
1643
  const menuId = $menuButton.getAttribute('aria-controls');
1632
1644
  if (!menuId) {
1633
1645
  throw new ElementError({
1634
- componentName: 'Header',
1646
+ component: Header,
1635
1647
  identifier: 'Navigation button (`<button class="govuk-js-header-toggle">`) attribute (`aria-controls`)'
1636
1648
  });
1637
1649
  }
1638
1650
  const $menu = document.getElementById(menuId);
1639
1651
  if (!$menu) {
1640
1652
  throw new ElementError({
1641
- componentName: 'Header',
1653
+ component: Header,
1642
1654
  element: $menu,
1643
1655
  identifier: `Navigation (\`<ul id="${menuId}">\`)`
1644
1656
  });
@@ -1652,7 +1664,7 @@ class Header extends GOVUKFrontendComponent {
1652
1664
  const breakpoint = getBreakpoint('desktop');
1653
1665
  if (!breakpoint.value) {
1654
1666
  throw new ElementError({
1655
- componentName: 'Header',
1667
+ component: Header,
1656
1668
  identifier: `CSS custom property (\`${breakpoint.property}\`) on pseudo-class \`:root\``
1657
1669
  });
1658
1670
  }
@@ -1695,24 +1707,15 @@ Header.moduleName = 'govuk-header';
1695
1707
  */
1696
1708
  class NotificationBanner extends GOVUKFrontendComponent {
1697
1709
  /**
1698
- * @param {Element | null} $module - HTML element to use for notification banner
1710
+ * @param {Element | null} $root - HTML element to use for notification banner
1699
1711
  * @param {NotificationBannerConfig} [config] - Notification banner config
1700
1712
  */
1701
- constructor($module, config = {}) {
1702
- super();
1703
- this.$module = void 0;
1713
+ constructor($root, config = {}) {
1714
+ super($root);
1704
1715
  this.config = void 0;
1705
- if (!($module instanceof HTMLElement)) {
1706
- throw new ElementError({
1707
- componentName: 'Notification banner',
1708
- element: $module,
1709
- identifier: 'Root element (`$module`)'
1710
- });
1711
- }
1712
- this.$module = $module;
1713
- this.config = mergeConfigs(NotificationBanner.defaults, config, normaliseDataset(NotificationBanner, $module.dataset));
1714
- if (this.$module.getAttribute('role') === 'alert' && !this.config.disableAutoFocus) {
1715
- setFocus(this.$module);
1716
+ this.config = mergeConfigs(NotificationBanner.defaults, config, normaliseDataset(NotificationBanner, this.$root.dataset));
1717
+ if (this.$root.getAttribute('role') === 'alert' && !this.config.disableAutoFocus) {
1718
+ setFocus(this.$root);
1716
1719
  }
1717
1720
  }
1718
1721
  }
@@ -1749,28 +1752,20 @@ NotificationBanner.schema = Object.freeze({
1749
1752
  */
1750
1753
  class PasswordInput extends GOVUKFrontendComponent {
1751
1754
  /**
1752
- * @param {Element | null} $module - HTML element to use for password input
1755
+ * @param {Element | null} $root - HTML element to use for password input
1753
1756
  * @param {PasswordInputConfig} [config] - Password input config
1754
1757
  */
1755
- constructor($module, config = {}) {
1756
- super();
1757
- this.$module = void 0;
1758
+ constructor($root, config = {}) {
1759
+ super($root);
1758
1760
  this.config = void 0;
1759
1761
  this.i18n = void 0;
1760
1762
  this.$input = void 0;
1761
1763
  this.$showHideButton = void 0;
1762
1764
  this.$screenReaderStatusMessage = void 0;
1763
- if (!($module instanceof HTMLElement)) {
1764
- throw new ElementError({
1765
- componentName: 'Password input',
1766
- element: $module,
1767
- identifier: 'Root element (`$module`)'
1768
- });
1769
- }
1770
- const $input = $module.querySelector('.govuk-js-password-input-input');
1765
+ const $input = this.$root.querySelector('.govuk-js-password-input-input');
1771
1766
  if (!($input instanceof HTMLInputElement)) {
1772
1767
  throw new ElementError({
1773
- componentName: 'Password input',
1768
+ component: PasswordInput,
1774
1769
  element: $input,
1775
1770
  expectedType: 'HTMLInputElement',
1776
1771
  identifier: 'Form field (`.govuk-js-password-input-input`)'
@@ -1779,10 +1774,10 @@ class PasswordInput extends GOVUKFrontendComponent {
1779
1774
  if ($input.type !== 'password') {
1780
1775
  throw new ElementError('Password input: Form field (`.govuk-js-password-input-input`) must be of type `password`.');
1781
1776
  }
1782
- const $showHideButton = $module.querySelector('.govuk-js-password-input-toggle');
1777
+ const $showHideButton = this.$root.querySelector('.govuk-js-password-input-toggle');
1783
1778
  if (!($showHideButton instanceof HTMLButtonElement)) {
1784
1779
  throw new ElementError({
1785
- componentName: 'Password input',
1780
+ component: PasswordInput,
1786
1781
  element: $showHideButton,
1787
1782
  expectedType: 'HTMLButtonElement',
1788
1783
  identifier: 'Button (`.govuk-js-password-input-toggle`)'
@@ -1791,12 +1786,11 @@ class PasswordInput extends GOVUKFrontendComponent {
1791
1786
  if ($showHideButton.type !== 'button') {
1792
1787
  throw new ElementError('Password input: Button (`.govuk-js-password-input-toggle`) must be of type `button`.');
1793
1788
  }
1794
- this.$module = $module;
1795
1789
  this.$input = $input;
1796
1790
  this.$showHideButton = $showHideButton;
1797
- this.config = mergeConfigs(PasswordInput.defaults, config, normaliseDataset(PasswordInput, $module.dataset));
1791
+ this.config = mergeConfigs(PasswordInput.defaults, config, normaliseDataset(PasswordInput, this.$root.dataset));
1798
1792
  this.i18n = new I18n(this.config.i18n, {
1799
- locale: closestAttributeValue($module, 'lang')
1793
+ locale: closestAttributeValue(this.$root, 'lang')
1800
1794
  });
1801
1795
  this.$showHideButton.removeAttribute('hidden');
1802
1796
  const $screenReaderStatusMessage = document.createElement('div');
@@ -1914,27 +1908,18 @@ class Radios extends GOVUKFrontendComponent {
1914
1908
  * (for example if the user has navigated back), and set up event handlers to
1915
1909
  * keep the reveal in sync with the radio state.
1916
1910
  *
1917
- * @param {Element | null} $module - HTML element to use for radios
1911
+ * @param {Element | null} $root - HTML element to use for radios
1918
1912
  */
1919
- constructor($module) {
1920
- super();
1921
- this.$module = void 0;
1913
+ constructor($root) {
1914
+ super($root);
1922
1915
  this.$inputs = void 0;
1923
- if (!($module instanceof HTMLElement)) {
1924
- throw new ElementError({
1925
- componentName: 'Radios',
1926
- element: $module,
1927
- identifier: 'Root element (`$module`)'
1928
- });
1929
- }
1930
- const $inputs = $module.querySelectorAll('input[type="radio"]');
1916
+ const $inputs = this.$root.querySelectorAll('input[type="radio"]');
1931
1917
  if (!$inputs.length) {
1932
1918
  throw new ElementError({
1933
- componentName: 'Radios',
1919
+ component: Radios,
1934
1920
  identifier: 'Form inputs (`<input type="radio">`)'
1935
1921
  });
1936
1922
  }
1937
- this.$module = $module;
1938
1923
  this.$inputs = $inputs;
1939
1924
  this.$inputs.forEach($input => {
1940
1925
  const targetId = $input.getAttribute('data-aria-controls');
@@ -1943,7 +1928,7 @@ class Radios extends GOVUKFrontendComponent {
1943
1928
  }
1944
1929
  if (!document.getElementById(targetId)) {
1945
1930
  throw new ElementError({
1946
- componentName: 'Radios',
1931
+ component: Radios,
1947
1932
  identifier: `Conditional reveal (\`id="${targetId}"\`)`
1948
1933
  });
1949
1934
  }
@@ -1952,7 +1937,7 @@ class Radios extends GOVUKFrontendComponent {
1952
1937
  });
1953
1938
  window.addEventListener('pageshow', () => this.syncAllConditionalReveals());
1954
1939
  this.syncAllConditionalReveals();
1955
- this.$module.addEventListener('click', event => this.handleClick(event));
1940
+ this.$root.addEventListener('click', event => this.handleClick(event));
1956
1941
  }
1957
1942
  syncAllConditionalReveals() {
1958
1943
  this.$inputs.forEach($input => this.syncConditionalRevealWithInputState($input));
@@ -1989,35 +1974,105 @@ class Radios extends GOVUKFrontendComponent {
1989
1974
  Radios.moduleName = 'govuk-radios';
1990
1975
 
1991
1976
  /**
1992
- * Skip link component
1977
+ * Service Navigation component
1993
1978
  *
1994
1979
  * @preserve
1995
1980
  */
1996
- class SkipLink extends GOVUKFrontendComponent {
1981
+ class ServiceNavigation extends GOVUKFrontendComponent {
1997
1982
  /**
1998
- * @param {Element | null} $module - HTML element to use for skip link
1999
- * @throws {ElementError} when $module is not set or the wrong type
2000
- * @throws {ElementError} when $module.hash does not contain a hash
2001
- * @throws {ElementError} when the linked element is missing or the wrong type
1983
+ * @param {Element | null} $root - HTML element to use for header
2002
1984
  */
2003
- constructor($module) {
2004
- var _this$$module$getAttr;
2005
- super();
2006
- this.$module = void 0;
2007
- if (!($module instanceof HTMLAnchorElement)) {
1985
+ constructor($root) {
1986
+ super($root);
1987
+ this.$menuButton = void 0;
1988
+ this.$menu = void 0;
1989
+ this.menuIsOpen = false;
1990
+ this.mql = null;
1991
+ const $menuButton = this.$root.querySelector('.govuk-js-service-navigation-toggle');
1992
+ if (!$menuButton) {
1993
+ return this;
1994
+ }
1995
+ const menuId = $menuButton.getAttribute('aria-controls');
1996
+ if (!menuId) {
2008
1997
  throw new ElementError({
2009
- componentName: 'Skip link',
2010
- element: $module,
2011
- expectedType: 'HTMLAnchorElement',
2012
- identifier: 'Root element (`$module`)'
1998
+ component: ServiceNavigation,
1999
+ identifier: 'Navigation button (`<button class="govuk-js-service-navigation-toggle">`) attribute (`aria-controls`)'
2000
+ });
2001
+ }
2002
+ const $menu = document.getElementById(menuId);
2003
+ if (!$menu) {
2004
+ throw new ElementError({
2005
+ component: ServiceNavigation,
2006
+ element: $menu,
2007
+ identifier: `Navigation (\`<ul id="${menuId}">\`)`
2008
+ });
2009
+ }
2010
+ this.$menu = $menu;
2011
+ this.$menuButton = $menuButton;
2012
+ this.setupResponsiveChecks();
2013
+ this.$menuButton.addEventListener('click', () => this.handleMenuButtonClick());
2014
+ }
2015
+ setupResponsiveChecks() {
2016
+ const breakpoint = getBreakpoint('tablet');
2017
+ if (!breakpoint.value) {
2018
+ throw new ElementError({
2019
+ component: ServiceNavigation,
2020
+ identifier: `CSS custom property (\`${breakpoint.property}\`) on pseudo-class \`:root\``
2013
2021
  });
2014
2022
  }
2015
- this.$module = $module;
2016
- const hash = this.$module.hash;
2017
- const href = (_this$$module$getAttr = this.$module.getAttribute('href')) != null ? _this$$module$getAttr : '';
2023
+ this.mql = window.matchMedia(`(min-width: ${breakpoint.value})`);
2024
+ if ('addEventListener' in this.mql) {
2025
+ this.mql.addEventListener('change', () => this.checkMode());
2026
+ } else {
2027
+ this.mql.addListener(() => this.checkMode());
2028
+ }
2029
+ this.checkMode();
2030
+ }
2031
+ checkMode() {
2032
+ if (!this.mql || !this.$menu || !this.$menuButton) {
2033
+ return;
2034
+ }
2035
+ if (this.mql.matches) {
2036
+ this.$menu.removeAttribute('hidden');
2037
+ this.$menuButton.setAttribute('hidden', '');
2038
+ } else {
2039
+ this.$menuButton.removeAttribute('hidden');
2040
+ this.$menuButton.setAttribute('aria-expanded', this.menuIsOpen.toString());
2041
+ if (this.menuIsOpen) {
2042
+ this.$menu.removeAttribute('hidden');
2043
+ } else {
2044
+ this.$menu.setAttribute('hidden', '');
2045
+ }
2046
+ }
2047
+ }
2048
+ handleMenuButtonClick() {
2049
+ this.menuIsOpen = !this.menuIsOpen;
2050
+ this.checkMode();
2051
+ }
2052
+ }
2053
+ ServiceNavigation.moduleName = 'govuk-service-navigation';
2054
+
2055
+ /**
2056
+ * Skip link component
2057
+ *
2058
+ * @preserve
2059
+ * @augments GOVUKFrontendComponent<HTMLAnchorElement>
2060
+ */
2061
+ class SkipLink extends GOVUKFrontendComponent {
2062
+ /**
2063
+ * @param {Element | null} $root - HTML element to use for skip link
2064
+ * @throws {ElementError} when $root is not set or the wrong type
2065
+ * @throws {ElementError} when $root.hash does not contain a hash
2066
+ * @throws {ElementError} when the linked element is missing or the wrong type
2067
+ */
2068
+ constructor($root) {
2069
+ var _this$$root$getAttrib;
2070
+ super($root);
2071
+ const hash = this.$root.hash;
2072
+ const href = (_this$$root$getAttrib = this.$root.getAttribute('href')) != null ? _this$$root$getAttrib : '';
2018
2073
  let url;
2019
2074
  try {
2020
- url = new window.URL(this.$module.href);
2075
+ url = new window.URL(this.$root.href);
2021
2076
  } catch (error) {
2022
2077
  throw new ElementError(`Skip link: Target link (\`href="${href}"\`) is invalid`);
2023
2078
  }
@@ -2031,12 +2086,12 @@ class SkipLink extends GOVUKFrontendComponent {
2031
2086
  const $linkedElement = document.getElementById(linkedElementId);
2032
2087
  if (!$linkedElement) {
2033
2088
  throw new ElementError({
2034
- componentName: 'Skip link',
2089
+ component: SkipLink,
2035
2090
  element: $linkedElement,
2036
2091
  identifier: `Target content (\`id="${linkedElementId}"\`)`
2037
2092
  });
2038
2093
  }
2039
- this.$module.addEventListener('click', () => setFocus($linkedElement, {
2094
+ this.$root.addEventListener('click', () => setFocus($linkedElement, {
2040
2095
  onBeforeFocus() {
2041
2096
  $linkedElement.classList.add('govuk-skip-link-focused-element');
2042
2097
  },
@@ -2046,6 +2101,7 @@ class SkipLink extends GOVUKFrontendComponent {
2046
2101
  }));
2047
2102
  }
2048
2103
  }
2104
+ SkipLink.elementType = HTMLAnchorElement;
2049
2105
  SkipLink.moduleName = 'govuk-skip-link';
2050
2106
 
2051
2107
  /**
@@ -2055,11 +2111,10 @@ SkipLink.moduleName = 'govuk-skip-link';
2055
2111
  */
2056
2112
  class Tabs extends GOVUKFrontendComponent {
2057
2113
  /**
2058
- * @param {Element | null} $module - HTML element to use for tabs
2114
+ * @param {Element | null} $root - HTML element to use for tabs
2059
2115
  */
2060
- constructor($module) {
2061
- super();
2062
- this.$module = void 0;
2116
+ constructor($root) {
2117
+ super($root);
2063
2118
  this.$tabs = void 0;
2064
2119
  this.$tabList = void 0;
2065
2120
  this.$tabListItems = void 0;
@@ -2069,36 +2124,28 @@ class Tabs extends GOVUKFrontendComponent {
2069
2124
  this.boundTabKeydown = void 0;
2070
2125
  this.boundOnHashChange = void 0;
2071
2126
  this.mql = null;
2072
- if (!$module) {
2073
- throw new ElementError({
2074
- componentName: 'Tabs',
2075
- element: $module,
2076
- identifier: 'Root element (`$module`)'
2077
- });
2078
- }
2079
- const $tabs = $module.querySelectorAll('a.govuk-tabs__tab');
2127
+ const $tabs = this.$root.querySelectorAll('a.govuk-tabs__tab');
2080
2128
  if (!$tabs.length) {
2081
2129
  throw new ElementError({
2082
- componentName: 'Tabs',
2130
+ component: Tabs,
2083
2131
  identifier: 'Links (`<a class="govuk-tabs__tab">`)'
2084
2132
  });
2085
2133
  }
2086
- this.$module = $module;
2087
2134
  this.$tabs = $tabs;
2088
2135
  this.boundTabClick = this.onTabClick.bind(this);
2089
2136
  this.boundTabKeydown = this.onTabKeydown.bind(this);
2090
2137
  this.boundOnHashChange = this.onHashChange.bind(this);
2091
- const $tabList = this.$module.querySelector('.govuk-tabs__list');
2092
- const $tabListItems = this.$module.querySelectorAll('li.govuk-tabs__list-item');
2138
+ const $tabList = this.$root.querySelector('.govuk-tabs__list');
2139
+ const $tabListItems = this.$root.querySelectorAll('li.govuk-tabs__list-item');
2093
2140
  if (!$tabList) {
2094
2141
  throw new ElementError({
2095
- componentName: 'Tabs',
2142
+ component: Tabs,
2096
2143
  identifier: 'List (`<ul class="govuk-tabs__list">`)'
2097
2144
  });
2098
2145
  }
2099
2146
  if (!$tabListItems.length) {
2100
2147
  throw new ElementError({
2101
- componentName: 'Tabs',
2148
+ component: Tabs,
2102
2149
  identifier: 'List items (`<li class="govuk-tabs__list-item">`)'
2103
2150
  });
2104
2151
  }
@@ -2110,7 +2157,7 @@ class Tabs extends GOVUKFrontendComponent {
2110
2157
  const breakpoint = getBreakpoint('tablet');
2111
2158
  if (!breakpoint.value) {
2112
2159
  throw new ElementError({
2113
- componentName: 'Tabs',
2160
+ component: Tabs,
2114
2161
  identifier: `CSS custom property (\`${breakpoint.property}\`) on pseudo-class \`:root\``
2115
2162
  });
2116
2163
  }
@@ -2185,7 +2232,7 @@ class Tabs extends GOVUKFrontendComponent {
2185
2232
  this.showPanel($tab);
2186
2233
  }
2187
2234
  getTab(hash) {
2188
- return this.$module.querySelector(`a.govuk-tabs__tab[href="${hash}"]`);
2235
+ return this.$root.querySelector(`a.govuk-tabs__tab[href="${hash}"]`);
2189
2236
  }
2190
2237
  setAttributes($tab) {
2191
2238
  const panelId = getFragmentFromUrl($tab.href);
@@ -2296,7 +2343,7 @@ class Tabs extends GOVUKFrontendComponent {
2296
2343
  if (!panelId) {
2297
2344
  return null;
2298
2345
  }
2299
- return this.$module.querySelector(`#${panelId}`);
2346
+ return this.$root.querySelector(`#${panelId}`);
2300
2347
  }
2301
2348
  showPanel($tab) {
2302
2349
  const $panel = this.getPanel($tab);
@@ -2329,7 +2376,7 @@ class Tabs extends GOVUKFrontendComponent {
2329
2376
  $tab.setAttribute('tabindex', '0');
2330
2377
  }
2331
2378
  getCurrentTab() {
2332
- return this.$module.querySelector('.govuk-tabs__list-item--selected a.govuk-tabs__tab');
2379
+ return this.$root.querySelector('.govuk-tabs__list-item--selected a.govuk-tabs__tab');
2333
2380
  }
2334
2381
  }
2335
2382
  Tabs.moduleName = 'govuk-tabs';
@@ -2340,19 +2387,28 @@ Tabs.moduleName = 'govuk-tabs';
2340
2387
  * Use the `data-module` attributes to find, instantiate and init all of the
2341
2388
  * components provided as part of GOV.UK Frontend.
2342
2389
  *
2343
- * @param {Config & { scope?: Element }} [config] - Config for all components (with optional scope)
2390
+ * @param {Config & { scope?: Element, onError?: OnErrorCallback<CompatibleClass> }} [config] - Config for all components (with optional scope)
2344
2391
  */
2345
2392
  function initAll(config) {
2346
2393
  var _config$scope;
2347
2394
  config = typeof config !== 'undefined' ? config : {};
2348
2395
  if (!isSupported()) {
2349
- console.log(new SupportError());
2396
+ if (config.onError) {
2397
+ config.onError(new SupportError(), {
2398
+ config
2399
+ });
2400
+ } else {
2401
+ console.log(new SupportError());
2402
+ }
2350
2403
  return;
2351
2404
  }
2352
- 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]];
2353
- const $scope = (_config$scope = config.scope) != null ? _config$scope : document;
2405
+ const components = [[Accordion, config.accordion], [Button, config.button], [CharacterCount, config.characterCount], [Checkboxes], [ErrorSummary, config.errorSummary], [ExitThisPage, config.exitThisPage], [Header], [NotificationBanner, config.notificationBanner], [PasswordInput, config.passwordInput], [Radios], [ServiceNavigation], [SkipLink], [Tabs]];
2406
+ const options = {
2407
+ scope: (_config$scope = config.scope) != null ? _config$scope : document,
2408
+ onError: config.onError
2409
+ };
2354
2410
  components.forEach(([Component, config]) => {
2355
- createAll(Component, config, $scope);
2411
+ createAll(Component, config, options);
2356
2412
  });
2357
2413
  }
2358
2414
 
@@ -2367,17 +2423,50 @@ function initAll(config) {
2367
2423
  *
2368
2424
  * @template {CompatibleClass} T
2369
2425
  * @param {T} Component - class of the component to create
2370
- * @param {T["defaults"]} [config] - config for the component
2371
- * @param {Element|Document} [$scope] - scope of the document to search within
2426
+ * @param {T["defaults"]} [config] - Config supplied to component
2427
+ * @param {OnErrorCallback<T> | Element | Document | CreateAllOptions<T> } [createAllOptions] - options for createAll including scope of the document to search within and callback function if error throw by component on init
2372
2428
  * @returns {Array<InstanceType<T>>} - array of instantiated components
2373
2429
  */
2374
- function createAll(Component, config, $scope = document) {
2430
+ function createAll(Component, config, createAllOptions) {
2431
+ let $scope = document;
2432
+ let onError;
2433
+ if (typeof createAllOptions === 'object') {
2434
+ var _createAllOptions$sco;
2435
+ createAllOptions = createAllOptions;
2436
+ $scope = (_createAllOptions$sco = createAllOptions.scope) != null ? _createAllOptions$sco : $scope;
2437
+ onError = createAllOptions.onError;
2438
+ }
2439
+ if (typeof createAllOptions === 'function') {
2440
+ onError = createAllOptions;
2441
+ }
2442
+ if (createAllOptions instanceof HTMLElement) {
2443
+ $scope = createAllOptions;
2444
+ }
2375
2445
  const $elements = $scope.querySelectorAll(`[data-module="${Component.moduleName}"]`);
2446
+ if (!isSupported()) {
2447
+ if (onError) {
2448
+ onError(new SupportError(), {
2449
+ component: Component,
2450
+ config
2451
+ });
2452
+ } else {
2453
+ console.log(new SupportError());
2454
+ }
2455
+ return [];
2456
+ }
2376
2457
  return Array.from($elements).map($element => {
2377
2458
  try {
2378
- return 'defaults' in Component && typeof config !== 'undefined' ? new Component($element, config) : new Component($element);
2459
+ return typeof config !== 'undefined' ? new Component($element, config) : new Component($element);
2379
2460
  } catch (error) {
2380
- console.log(error);
2461
+ if (onError) {
2462
+ onError(error, {
2463
+ element: $element,
2464
+ component: Component,
2465
+ config
2466
+ });
2467
+ } else {
2468
+ console.log(error);
2469
+ }
2381
2470
  return null;
2382
2471
  }
2383
2472
  }).filter(Boolean);
@@ -2416,6 +2505,25 @@ function createAll(Component, config, $scope = document) {
2416
2505
  *
2417
2506
  * @typedef {keyof Config} ConfigKey
2418
2507
  */
2508
+ /**
2509
+ * @template {CompatibleClass} T
2510
+ * @typedef {object} ErrorContext
2511
+ * @property {Element} [element] - Element used for component module initialisation
2512
+ * @property {T} [component] - Class of component
2513
+ * @property {T["defaults"]} config - Config supplied to component
2514
+ */
2515
+ /**
2516
+ * @template {CompatibleClass} T
2517
+ * @callback OnErrorCallback
2518
+ * @param {unknown} error - Thrown error
2519
+ * @param {ErrorContext<T>} context - Object containing the element, component class and configuration
2520
+ */
2521
+ /**
2522
+ * @template {CompatibleClass} T
2523
+ * @typedef {object} CreateAllOptions
2524
+ * @property {Element | Document} [scope] - scope of the document to search within
2525
+ * @property {OnErrorCallback<T>} [onError] - callback function if error throw by component on init
2526
+ */
2419
2527
 
2420
- export { Accordion, Button, CharacterCount, Checkboxes, ErrorSummary, ExitThisPage, Header, NotificationBanner, PasswordInput, Radios, SkipLink, Tabs, createAll, initAll, version };
2528
+ export { Accordion, Button, CharacterCount, Checkboxes, GOVUKFrontendComponent as Component, ErrorSummary, ExitThisPage, Header, NotificationBanner, PasswordInput, Radios, ServiceNavigation, SkipLink, Tabs, createAll, initAll, isSupported, version };
2421
2529
  //# sourceMappingURL=all.bundle.mjs.map