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
@@ -4,7 +4,7 @@
4
4
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.GOVUKFrontend = {}));
5
5
  })(this, (function (exports) { 'use strict';
6
6
 
7
- const version = '5.5.0';
7
+ const version = '5.7.0';
8
8
 
9
9
  function normaliseString(value, property) {
10
10
  const trimmedValue = value ? value.trim() : '';
@@ -114,6 +114,19 @@
114
114
  (_options$onBeforeFocu = options.onBeforeFocus) == null || _options$onBeforeFocu.call($element);
115
115
  $element.focus();
116
116
  }
117
+ function isInitialised($root, moduleName) {
118
+ return $root instanceof HTMLElement && $root.hasAttribute(`data-${moduleName}-init`);
119
+ }
120
+
121
+ /**
122
+ * Checks if GOV.UK Frontend is supported on this page
123
+ *
124
+ * Some browsers will load and run our JavaScript but GOV.UK Frontend
125
+ * won't be supported.
126
+ *
127
+ * @param {HTMLElement | null} [$scope] - (internal) `<body>` HTML element checked for browser support
128
+ * @returns {boolean} Whether GOV.UK Frontend is supported on this page
129
+ */
117
130
  function isSupported($scope = document.body) {
118
131
  if (!$scope) {
119
132
  return false;
@@ -146,6 +159,9 @@
146
159
  function isObject(option) {
147
160
  return !!option && typeof option === 'object' && !isArray(option);
148
161
  }
162
+ function formatErrorMessage(Component, message) {
163
+ return `${Component.moduleName}: ${message}`;
164
+ }
149
165
 
150
166
  /**
151
167
  * Schema for component config
@@ -169,6 +185,10 @@
169
185
  * @property {string[]} required - List of required config fields
170
186
  * @property {string} errorMessage - Error message when required config fields not provided
171
187
  */
188
+ /**
189
+ * @typedef ComponentWithModuleName
190
+ * @property {string} moduleName - Name of the component
191
+ */
172
192
 
173
193
  function normaliseDataset(Component, dataset) {
174
194
  const out = {};
@@ -212,30 +232,85 @@
212
232
  let message = typeof messageOrOptions === 'string' ? messageOrOptions : '';
213
233
  if (typeof messageOrOptions === 'object') {
214
234
  const {
215
- componentName,
235
+ component,
216
236
  identifier,
217
237
  element,
218
238
  expectedType
219
239
  } = messageOrOptions;
220
- message = `${componentName}: ${identifier}`;
240
+ message = identifier;
221
241
  message += element ? ` is not of type ${expectedType != null ? expectedType : 'HTMLElement'}` : ' not found';
242
+ message = formatErrorMessage(component, message);
222
243
  }
223
244
  super(message);
224
245
  this.name = 'ElementError';
225
246
  }
226
247
  }
248
+ class InitError extends GOVUKFrontendError {
249
+ constructor(componentOrMessage) {
250
+ const message = typeof componentOrMessage === 'string' ? componentOrMessage : formatErrorMessage(componentOrMessage, `Root element (\`$root\`) already initialised`);
251
+ super(message);
252
+ this.name = 'InitError';
253
+ }
254
+ }
255
+ /**
256
+ * @typedef {import('../common/index.mjs').ComponentWithModuleName} ComponentWithModuleName
257
+ */
227
258
 
228
259
  class GOVUKFrontendComponent {
229
- constructor() {
230
- this.checkSupport();
260
+ /**
261
+ * Returns the root element of the component
262
+ *
263
+ * @protected
264
+ * @returns {RootElementType} - the root element of component
265
+ */
266
+ get $root() {
267
+ return this._$root;
268
+ }
269
+ constructor($root) {
270
+ this._$root = void 0;
271
+ const childConstructor = this.constructor;
272
+ if (typeof childConstructor.moduleName !== 'string') {
273
+ throw new InitError(`\`moduleName\` not defined in component`);
274
+ }
275
+ if (!($root instanceof childConstructor.elementType)) {
276
+ throw new ElementError({
277
+ element: $root,
278
+ component: childConstructor,
279
+ identifier: 'Root element (`$root`)',
280
+ expectedType: childConstructor.elementType.name
281
+ });
282
+ } else {
283
+ this._$root = $root;
284
+ }
285
+ childConstructor.checkSupport();
286
+ this.checkInitialised();
287
+ const moduleName = childConstructor.moduleName;
288
+ this.$root.setAttribute(`data-${moduleName}-init`, '');
289
+ }
290
+ checkInitialised() {
291
+ const constructor = this.constructor;
292
+ const moduleName = constructor.moduleName;
293
+ if (moduleName && isInitialised(this.$root, moduleName)) {
294
+ throw new InitError(constructor);
295
+ }
231
296
  }
232
- checkSupport() {
297
+ static checkSupport() {
233
298
  if (!isSupported()) {
234
299
  throw new SupportError();
235
300
  }
236
301
  }
237
302
  }
238
303
 
304
+ /**
305
+ * @typedef ChildClass
306
+ * @property {string} moduleName - The module name that'll be looked for in the DOM when initialising the component
307
+ */
308
+
309
+ /**
310
+ * @typedef {typeof GOVUKFrontendComponent & ChildClass} ChildClassConstructor
311
+ */
312
+ GOVUKFrontendComponent.elementType = HTMLElement;
313
+
239
314
  class I18n {
240
315
  constructor(translations = {}, config = {}) {
241
316
  var _config$locale;
@@ -445,12 +520,11 @@
445
520
  */
446
521
  class Accordion extends GOVUKFrontendComponent {
447
522
  /**
448
- * @param {Element | null} $module - HTML element to use for accordion
523
+ * @param {Element | null} $root - HTML element to use for accordion
449
524
  * @param {AccordionConfig} [config] - Accordion config
450
525
  */
451
- constructor($module, config = {}) {
452
- super();
453
- this.$module = void 0;
526
+ constructor($root, config = {}) {
527
+ super($root);
454
528
  this.config = void 0;
455
529
  this.i18n = void 0;
456
530
  this.controlsClass = 'govuk-accordion__controls';
@@ -476,20 +550,12 @@
476
550
  this.$showAllButton = null;
477
551
  this.$showAllIcon = null;
478
552
  this.$showAllText = null;
479
- if (!($module instanceof HTMLElement)) {
480
- throw new ElementError({
481
- componentName: 'Accordion',
482
- element: $module,
483
- identifier: 'Root element (`$module`)'
484
- });
485
- }
486
- this.$module = $module;
487
- this.config = mergeConfigs(Accordion.defaults, config, normaliseDataset(Accordion, $module.dataset));
553
+ this.config = mergeConfigs(Accordion.defaults, config, normaliseDataset(Accordion, this.$root.dataset));
488
554
  this.i18n = new I18n(this.config.i18n);
489
- const $sections = this.$module.querySelectorAll(`.${this.sectionClass}`);
555
+ const $sections = this.$root.querySelectorAll(`.${this.sectionClass}`);
490
556
  if (!$sections.length) {
491
557
  throw new ElementError({
492
- componentName: 'Accordion',
558
+ component: Accordion,
493
559
  identifier: `Sections (\`<div class="${this.sectionClass}">\`)`
494
560
  });
495
561
  }
@@ -509,7 +575,7 @@
509
575
  const $accordionControls = document.createElement('div');
510
576
  $accordionControls.setAttribute('class', this.controlsClass);
511
577
  $accordionControls.appendChild(this.$showAllButton);
512
- this.$module.insertBefore($accordionControls, this.$module.firstChild);
578
+ this.$root.insertBefore($accordionControls, this.$root.firstChild);
513
579
  this.$showAllText = document.createElement('span');
514
580
  this.$showAllText.classList.add(this.showAllTextClass);
515
581
  this.$showAllButton.appendChild(this.$showAllText);
@@ -523,7 +589,7 @@
523
589
  const $header = $section.querySelector(`.${this.sectionHeaderClass}`);
524
590
  if (!$header) {
525
591
  throw new ElementError({
526
- componentName: 'Accordion',
592
+ component: Accordion,
527
593
  identifier: `Section headers (\`<div class="${this.sectionHeaderClass}">\`)`
528
594
  });
529
595
  }
@@ -539,19 +605,19 @@
539
605
  const $summary = $header.querySelector(`.${this.sectionSummaryClass}`);
540
606
  if (!$heading) {
541
607
  throw new ElementError({
542
- componentName: 'Accordion',
608
+ component: Accordion,
543
609
  identifier: `Section heading (\`.${this.sectionHeadingClass}\`)`
544
610
  });
545
611
  }
546
612
  if (!$span) {
547
613
  throw new ElementError({
548
- componentName: 'Accordion',
614
+ component: Accordion,
549
615
  identifier: `Section button placeholder (\`<span class="${this.sectionButtonClass}">\`)`
550
616
  });
551
617
  }
552
618
  const $button = document.createElement('button');
553
619
  $button.setAttribute('type', 'button');
554
- $button.setAttribute('aria-controls', `${this.$module.id}-content-${index + 1}`);
620
+ $button.setAttribute('aria-controls', `${this.$root.id}-content-${index + 1}`);
555
621
  for (const attr of Array.from($span.attributes)) {
556
622
  if (attr.name !== 'id') {
557
623
  $button.setAttribute(attr.name, attr.value);
@@ -625,7 +691,7 @@
625
691
  const $content = $section.querySelector(`.${this.sectionContentClass}`);
626
692
  if (!$content) {
627
693
  throw new ElementError({
628
- componentName: 'Accordion',
694
+ component: Accordion,
629
695
  identifier: `Section content (\`<div class="${this.sectionContentClass}">\`)`
630
696
  });
631
697
  }
@@ -788,25 +854,16 @@
788
854
  */
789
855
  class Button extends GOVUKFrontendComponent {
790
856
  /**
791
- * @param {Element | null} $module - HTML element to use for button
857
+ * @param {Element | null} $root - HTML element to use for button
792
858
  * @param {ButtonConfig} [config] - Button config
793
859
  */
794
- constructor($module, config = {}) {
795
- super();
796
- this.$module = void 0;
860
+ constructor($root, config = {}) {
861
+ super($root);
797
862
  this.config = void 0;
798
863
  this.debounceFormSubmitTimer = null;
799
- if (!($module instanceof HTMLElement)) {
800
- throw new ElementError({
801
- componentName: 'Button',
802
- element: $module,
803
- identifier: 'Root element (`$module`)'
804
- });
805
- }
806
- this.$module = $module;
807
- this.config = mergeConfigs(Button.defaults, config, normaliseDataset(Button, $module.dataset));
808
- this.$module.addEventListener('keydown', event => this.handleKeyDown(event));
809
- this.$module.addEventListener('click', event => this.debounce(event));
864
+ this.config = mergeConfigs(Button.defaults, config, normaliseDataset(Button, this.$root.dataset));
865
+ this.$root.addEventListener('keydown', event => this.handleKeyDown(event));
866
+ this.$root.addEventListener('click', event => this.debounce(event));
810
867
  }
811
868
  handleKeyDown(event) {
812
869
  const $target = event.target;
@@ -874,13 +931,12 @@
874
931
  */
875
932
  class CharacterCount extends GOVUKFrontendComponent {
876
933
  /**
877
- * @param {Element | null} $module - HTML element to use for character count
934
+ * @param {Element | null} $root - HTML element to use for character count
878
935
  * @param {CharacterCountConfig} [config] - Character count config
879
936
  */
880
- constructor($module, config = {}) {
937
+ constructor($root, config = {}) {
881
938
  var _ref, _this$config$maxwords;
882
- super();
883
- this.$module = void 0;
939
+ super($root);
884
940
  this.$textarea = void 0;
885
941
  this.$visibleCountMessage = void 0;
886
942
  this.$screenReaderCountMessage = void 0;
@@ -890,23 +946,16 @@
890
946
  this.config = void 0;
891
947
  this.i18n = void 0;
892
948
  this.maxLength = void 0;
893
- if (!($module instanceof HTMLElement)) {
894
- throw new ElementError({
895
- componentName: 'Character count',
896
- element: $module,
897
- identifier: 'Root element (`$module`)'
898
- });
899
- }
900
- const $textarea = $module.querySelector('.govuk-js-character-count');
949
+ const $textarea = this.$root.querySelector('.govuk-js-character-count');
901
950
  if (!($textarea instanceof HTMLTextAreaElement || $textarea instanceof HTMLInputElement)) {
902
951
  throw new ElementError({
903
- componentName: 'Character count',
952
+ component: CharacterCount,
904
953
  element: $textarea,
905
954
  expectedType: 'HTMLTextareaElement or HTMLInputElement',
906
955
  identifier: 'Form field (`.govuk-js-character-count`)'
907
956
  });
908
957
  }
909
- const datasetConfig = normaliseDataset(CharacterCount, $module.dataset);
958
+ const datasetConfig = normaliseDataset(CharacterCount, this.$root.dataset);
910
959
  let configOverrides = {};
911
960
  if ('maxwords' in datasetConfig || 'maxlength' in datasetConfig) {
912
961
  configOverrides = {
@@ -917,19 +966,18 @@
917
966
  this.config = mergeConfigs(CharacterCount.defaults, config, configOverrides, datasetConfig);
918
967
  const errors = validateConfig(CharacterCount.schema, this.config);
919
968
  if (errors[0]) {
920
- throw new ConfigError(`Character count: ${errors[0]}`);
969
+ throw new ConfigError(formatErrorMessage(CharacterCount, errors[0]));
921
970
  }
922
971
  this.i18n = new I18n(this.config.i18n, {
923
- locale: closestAttributeValue($module, 'lang')
972
+ locale: closestAttributeValue(this.$root, 'lang')
924
973
  });
925
974
  this.maxLength = (_ref = (_this$config$maxwords = this.config.maxwords) != null ? _this$config$maxwords : this.config.maxlength) != null ? _ref : Infinity;
926
- this.$module = $module;
927
975
  this.$textarea = $textarea;
928
976
  const textareaDescriptionId = `${this.$textarea.id}-info`;
929
977
  const $textareaDescription = document.getElementById(textareaDescriptionId);
930
978
  if (!$textareaDescription) {
931
979
  throw new ElementError({
932
- componentName: 'Character count',
980
+ component: CharacterCount,
933
981
  element: $textareaDescription,
934
982
  identifier: `Count message (\`id="${textareaDescriptionId}"\`)`
935
983
  });
@@ -1173,27 +1221,18 @@
1173
1221
  * (for example if the user has navigated back), and set up event handlers to
1174
1222
  * keep the reveal in sync with the checkbox state.
1175
1223
  *
1176
- * @param {Element | null} $module - HTML element to use for checkboxes
1224
+ * @param {Element | null} $root - HTML element to use for checkboxes
1177
1225
  */
1178
- constructor($module) {
1179
- super();
1180
- this.$module = void 0;
1226
+ constructor($root) {
1227
+ super($root);
1181
1228
  this.$inputs = void 0;
1182
- if (!($module instanceof HTMLElement)) {
1183
- throw new ElementError({
1184
- componentName: 'Checkboxes',
1185
- element: $module,
1186
- identifier: 'Root element (`$module`)'
1187
- });
1188
- }
1189
- const $inputs = $module.querySelectorAll('input[type="checkbox"]');
1229
+ const $inputs = this.$root.querySelectorAll('input[type="checkbox"]');
1190
1230
  if (!$inputs.length) {
1191
1231
  throw new ElementError({
1192
- componentName: 'Checkboxes',
1232
+ component: Checkboxes,
1193
1233
  identifier: 'Form inputs (`<input type="checkbox">`)'
1194
1234
  });
1195
1235
  }
1196
- this.$module = $module;
1197
1236
  this.$inputs = $inputs;
1198
1237
  this.$inputs.forEach($input => {
1199
1238
  const targetId = $input.getAttribute('data-aria-controls');
@@ -1202,7 +1241,7 @@
1202
1241
  }
1203
1242
  if (!document.getElementById(targetId)) {
1204
1243
  throw new ElementError({
1205
- componentName: 'Checkboxes',
1244
+ component: Checkboxes,
1206
1245
  identifier: `Conditional reveal (\`id="${targetId}"\`)`
1207
1246
  });
1208
1247
  }
@@ -1211,7 +1250,7 @@
1211
1250
  });
1212
1251
  window.addEventListener('pageshow', () => this.syncAllConditionalReveals());
1213
1252
  this.syncAllConditionalReveals();
1214
- this.$module.addEventListener('click', event => this.handleClick(event));
1253
+ this.$root.addEventListener('click', event => this.handleClick(event));
1215
1254
  }
1216
1255
  syncAllConditionalReveals() {
1217
1256
  this.$inputs.forEach($input => this.syncConditionalRevealWithInputState($input));
@@ -1280,26 +1319,17 @@
1280
1319
  */
1281
1320
  class ErrorSummary extends GOVUKFrontendComponent {
1282
1321
  /**
1283
- * @param {Element | null} $module - HTML element to use for error summary
1322
+ * @param {Element | null} $root - HTML element to use for error summary
1284
1323
  * @param {ErrorSummaryConfig} [config] - Error summary config
1285
1324
  */
1286
- constructor($module, config = {}) {
1287
- super();
1288
- this.$module = void 0;
1325
+ constructor($root, config = {}) {
1326
+ super($root);
1289
1327
  this.config = void 0;
1290
- if (!($module instanceof HTMLElement)) {
1291
- throw new ElementError({
1292
- componentName: 'Error summary',
1293
- element: $module,
1294
- identifier: 'Root element (`$module`)'
1295
- });
1296
- }
1297
- this.$module = $module;
1298
- this.config = mergeConfigs(ErrorSummary.defaults, config, normaliseDataset(ErrorSummary, $module.dataset));
1328
+ this.config = mergeConfigs(ErrorSummary.defaults, config, normaliseDataset(ErrorSummary, this.$root.dataset));
1299
1329
  if (!this.config.disableAutoFocus) {
1300
- setFocus(this.$module);
1330
+ setFocus(this.$root);
1301
1331
  }
1302
- this.$module.addEventListener('click', event => this.handleClick(event));
1332
+ this.$root.addEventListener('click', event => this.handleClick(event));
1303
1333
  }
1304
1334
  handleClick(event) {
1305
1335
  const $target = event.target;
@@ -1383,12 +1413,11 @@
1383
1413
  */
1384
1414
  class ExitThisPage extends GOVUKFrontendComponent {
1385
1415
  /**
1386
- * @param {Element | null} $module - HTML element that wraps the Exit This Page button
1416
+ * @param {Element | null} $root - HTML element that wraps the Exit This Page button
1387
1417
  * @param {ExitThisPageConfig} [config] - Exit This Page config
1388
1418
  */
1389
- constructor($module, config = {}) {
1390
- super();
1391
- this.$module = void 0;
1419
+ constructor($root, config = {}) {
1420
+ super($root);
1392
1421
  this.config = void 0;
1393
1422
  this.i18n = void 0;
1394
1423
  this.$button = void 0;
@@ -1401,25 +1430,17 @@
1401
1430
  this.timeoutTime = 5000;
1402
1431
  this.keypressTimeoutId = null;
1403
1432
  this.timeoutMessageId = null;
1404
- if (!($module instanceof HTMLElement)) {
1405
- throw new ElementError({
1406
- componentName: 'Exit this page',
1407
- element: $module,
1408
- identifier: 'Root element (`$module`)'
1409
- });
1410
- }
1411
- const $button = $module.querySelector('.govuk-exit-this-page__button');
1433
+ const $button = this.$root.querySelector('.govuk-exit-this-page__button');
1412
1434
  if (!($button instanceof HTMLAnchorElement)) {
1413
1435
  throw new ElementError({
1414
- componentName: 'Exit this page',
1436
+ component: ExitThisPage,
1415
1437
  element: $button,
1416
1438
  expectedType: 'HTMLAnchorElement',
1417
1439
  identifier: 'Button (`.govuk-exit-this-page__button`)'
1418
1440
  });
1419
1441
  }
1420
- this.config = mergeConfigs(ExitThisPage.defaults, config, normaliseDataset(ExitThisPage, $module.dataset));
1442
+ this.config = mergeConfigs(ExitThisPage.defaults, config, normaliseDataset(ExitThisPage, this.$root.dataset));
1421
1443
  this.i18n = new I18n(this.config.i18n);
1422
- this.$module = $module;
1423
1444
  this.$button = $button;
1424
1445
  const $skiplinkButton = document.querySelector('.govuk-js-exit-this-page-skiplink');
1425
1446
  if ($skiplinkButton instanceof HTMLAnchorElement) {
@@ -1438,7 +1459,7 @@
1438
1459
  this.$updateSpan = document.createElement('span');
1439
1460
  this.$updateSpan.setAttribute('role', 'status');
1440
1461
  this.$updateSpan.className = 'govuk-visually-hidden';
1441
- this.$module.appendChild(this.$updateSpan);
1462
+ this.$root.appendChild(this.$updateSpan);
1442
1463
  }
1443
1464
  initButtonClickHandler() {
1444
1465
  this.$button.addEventListener('click', this.handleClick.bind(this));
@@ -1613,38 +1634,29 @@
1613
1634
  * Apply a matchMedia for desktop which will trigger a state sync if the
1614
1635
  * browser viewport moves between states.
1615
1636
  *
1616
- * @param {Element | null} $module - HTML element to use for header
1637
+ * @param {Element | null} $root - HTML element to use for header
1617
1638
  */
1618
- constructor($module) {
1619
- super();
1620
- this.$module = void 0;
1639
+ constructor($root) {
1640
+ super($root);
1621
1641
  this.$menuButton = void 0;
1622
1642
  this.$menu = void 0;
1623
1643
  this.menuIsOpen = false;
1624
1644
  this.mql = null;
1625
- if (!$module) {
1626
- throw new ElementError({
1627
- componentName: 'Header',
1628
- element: $module,
1629
- identifier: 'Root element (`$module`)'
1630
- });
1631
- }
1632
- this.$module = $module;
1633
- const $menuButton = $module.querySelector('.govuk-js-header-toggle');
1645
+ const $menuButton = this.$root.querySelector('.govuk-js-header-toggle');
1634
1646
  if (!$menuButton) {
1635
1647
  return this;
1636
1648
  }
1637
1649
  const menuId = $menuButton.getAttribute('aria-controls');
1638
1650
  if (!menuId) {
1639
1651
  throw new ElementError({
1640
- componentName: 'Header',
1652
+ component: Header,
1641
1653
  identifier: 'Navigation button (`<button class="govuk-js-header-toggle">`) attribute (`aria-controls`)'
1642
1654
  });
1643
1655
  }
1644
1656
  const $menu = document.getElementById(menuId);
1645
1657
  if (!$menu) {
1646
1658
  throw new ElementError({
1647
- componentName: 'Header',
1659
+ component: Header,
1648
1660
  element: $menu,
1649
1661
  identifier: `Navigation (\`<ul id="${menuId}">\`)`
1650
1662
  });
@@ -1658,7 +1670,7 @@
1658
1670
  const breakpoint = getBreakpoint('desktop');
1659
1671
  if (!breakpoint.value) {
1660
1672
  throw new ElementError({
1661
- componentName: 'Header',
1673
+ component: Header,
1662
1674
  identifier: `CSS custom property (\`${breakpoint.property}\`) on pseudo-class \`:root\``
1663
1675
  });
1664
1676
  }
@@ -1701,24 +1713,15 @@
1701
1713
  */
1702
1714
  class NotificationBanner extends GOVUKFrontendComponent {
1703
1715
  /**
1704
- * @param {Element | null} $module - HTML element to use for notification banner
1716
+ * @param {Element | null} $root - HTML element to use for notification banner
1705
1717
  * @param {NotificationBannerConfig} [config] - Notification banner config
1706
1718
  */
1707
- constructor($module, config = {}) {
1708
- super();
1709
- this.$module = void 0;
1719
+ constructor($root, config = {}) {
1720
+ super($root);
1710
1721
  this.config = void 0;
1711
- if (!($module instanceof HTMLElement)) {
1712
- throw new ElementError({
1713
- componentName: 'Notification banner',
1714
- element: $module,
1715
- identifier: 'Root element (`$module`)'
1716
- });
1717
- }
1718
- this.$module = $module;
1719
- this.config = mergeConfigs(NotificationBanner.defaults, config, normaliseDataset(NotificationBanner, $module.dataset));
1720
- if (this.$module.getAttribute('role') === 'alert' && !this.config.disableAutoFocus) {
1721
- setFocus(this.$module);
1722
+ this.config = mergeConfigs(NotificationBanner.defaults, config, normaliseDataset(NotificationBanner, this.$root.dataset));
1723
+ if (this.$root.getAttribute('role') === 'alert' && !this.config.disableAutoFocus) {
1724
+ setFocus(this.$root);
1722
1725
  }
1723
1726
  }
1724
1727
  }
@@ -1755,28 +1758,20 @@
1755
1758
  */
1756
1759
  class PasswordInput extends GOVUKFrontendComponent {
1757
1760
  /**
1758
- * @param {Element | null} $module - HTML element to use for password input
1761
+ * @param {Element | null} $root - HTML element to use for password input
1759
1762
  * @param {PasswordInputConfig} [config] - Password input config
1760
1763
  */
1761
- constructor($module, config = {}) {
1762
- super();
1763
- this.$module = void 0;
1764
+ constructor($root, config = {}) {
1765
+ super($root);
1764
1766
  this.config = void 0;
1765
1767
  this.i18n = void 0;
1766
1768
  this.$input = void 0;
1767
1769
  this.$showHideButton = void 0;
1768
1770
  this.$screenReaderStatusMessage = void 0;
1769
- if (!($module instanceof HTMLElement)) {
1770
- throw new ElementError({
1771
- componentName: 'Password input',
1772
- element: $module,
1773
- identifier: 'Root element (`$module`)'
1774
- });
1775
- }
1776
- const $input = $module.querySelector('.govuk-js-password-input-input');
1771
+ const $input = this.$root.querySelector('.govuk-js-password-input-input');
1777
1772
  if (!($input instanceof HTMLInputElement)) {
1778
1773
  throw new ElementError({
1779
- componentName: 'Password input',
1774
+ component: PasswordInput,
1780
1775
  element: $input,
1781
1776
  expectedType: 'HTMLInputElement',
1782
1777
  identifier: 'Form field (`.govuk-js-password-input-input`)'
@@ -1785,10 +1780,10 @@
1785
1780
  if ($input.type !== 'password') {
1786
1781
  throw new ElementError('Password input: Form field (`.govuk-js-password-input-input`) must be of type `password`.');
1787
1782
  }
1788
- const $showHideButton = $module.querySelector('.govuk-js-password-input-toggle');
1783
+ const $showHideButton = this.$root.querySelector('.govuk-js-password-input-toggle');
1789
1784
  if (!($showHideButton instanceof HTMLButtonElement)) {
1790
1785
  throw new ElementError({
1791
- componentName: 'Password input',
1786
+ component: PasswordInput,
1792
1787
  element: $showHideButton,
1793
1788
  expectedType: 'HTMLButtonElement',
1794
1789
  identifier: 'Button (`.govuk-js-password-input-toggle`)'
@@ -1797,12 +1792,11 @@
1797
1792
  if ($showHideButton.type !== 'button') {
1798
1793
  throw new ElementError('Password input: Button (`.govuk-js-password-input-toggle`) must be of type `button`.');
1799
1794
  }
1800
- this.$module = $module;
1801
1795
  this.$input = $input;
1802
1796
  this.$showHideButton = $showHideButton;
1803
- this.config = mergeConfigs(PasswordInput.defaults, config, normaliseDataset(PasswordInput, $module.dataset));
1797
+ this.config = mergeConfigs(PasswordInput.defaults, config, normaliseDataset(PasswordInput, this.$root.dataset));
1804
1798
  this.i18n = new I18n(this.config.i18n, {
1805
- locale: closestAttributeValue($module, 'lang')
1799
+ locale: closestAttributeValue(this.$root, 'lang')
1806
1800
  });
1807
1801
  this.$showHideButton.removeAttribute('hidden');
1808
1802
  const $screenReaderStatusMessage = document.createElement('div');
@@ -1920,27 +1914,18 @@
1920
1914
  * (for example if the user has navigated back), and set up event handlers to
1921
1915
  * keep the reveal in sync with the radio state.
1922
1916
  *
1923
- * @param {Element | null} $module - HTML element to use for radios
1917
+ * @param {Element | null} $root - HTML element to use for radios
1924
1918
  */
1925
- constructor($module) {
1926
- super();
1927
- this.$module = void 0;
1919
+ constructor($root) {
1920
+ super($root);
1928
1921
  this.$inputs = void 0;
1929
- if (!($module instanceof HTMLElement)) {
1930
- throw new ElementError({
1931
- componentName: 'Radios',
1932
- element: $module,
1933
- identifier: 'Root element (`$module`)'
1934
- });
1935
- }
1936
- const $inputs = $module.querySelectorAll('input[type="radio"]');
1922
+ const $inputs = this.$root.querySelectorAll('input[type="radio"]');
1937
1923
  if (!$inputs.length) {
1938
1924
  throw new ElementError({
1939
- componentName: 'Radios',
1925
+ component: Radios,
1940
1926
  identifier: 'Form inputs (`<input type="radio">`)'
1941
1927
  });
1942
1928
  }
1943
- this.$module = $module;
1944
1929
  this.$inputs = $inputs;
1945
1930
  this.$inputs.forEach($input => {
1946
1931
  const targetId = $input.getAttribute('data-aria-controls');
@@ -1949,7 +1934,7 @@
1949
1934
  }
1950
1935
  if (!document.getElementById(targetId)) {
1951
1936
  throw new ElementError({
1952
- componentName: 'Radios',
1937
+ component: Radios,
1953
1938
  identifier: `Conditional reveal (\`id="${targetId}"\`)`
1954
1939
  });
1955
1940
  }
@@ -1958,7 +1943,7 @@
1958
1943
  });
1959
1944
  window.addEventListener('pageshow', () => this.syncAllConditionalReveals());
1960
1945
  this.syncAllConditionalReveals();
1961
- this.$module.addEventListener('click', event => this.handleClick(event));
1946
+ this.$root.addEventListener('click', event => this.handleClick(event));
1962
1947
  }
1963
1948
  syncAllConditionalReveals() {
1964
1949
  this.$inputs.forEach($input => this.syncConditionalRevealWithInputState($input));
@@ -1995,35 +1980,105 @@
1995
1980
  Radios.moduleName = 'govuk-radios';
1996
1981
 
1997
1982
  /**
1998
- * Skip link component
1983
+ * Service Navigation component
1999
1984
  *
2000
1985
  * @preserve
2001
1986
  */
2002
- class SkipLink extends GOVUKFrontendComponent {
1987
+ class ServiceNavigation extends GOVUKFrontendComponent {
2003
1988
  /**
2004
- * @param {Element | null} $module - HTML element to use for skip link
2005
- * @throws {ElementError} when $module is not set or the wrong type
2006
- * @throws {ElementError} when $module.hash does not contain a hash
2007
- * @throws {ElementError} when the linked element is missing or the wrong type
1989
+ * @param {Element | null} $root - HTML element to use for header
2008
1990
  */
2009
- constructor($module) {
2010
- var _this$$module$getAttr;
2011
- super();
2012
- this.$module = void 0;
2013
- if (!($module instanceof HTMLAnchorElement)) {
1991
+ constructor($root) {
1992
+ super($root);
1993
+ this.$menuButton = void 0;
1994
+ this.$menu = void 0;
1995
+ this.menuIsOpen = false;
1996
+ this.mql = null;
1997
+ const $menuButton = this.$root.querySelector('.govuk-js-service-navigation-toggle');
1998
+ if (!$menuButton) {
1999
+ return this;
2000
+ }
2001
+ const menuId = $menuButton.getAttribute('aria-controls');
2002
+ if (!menuId) {
2014
2003
  throw new ElementError({
2015
- componentName: 'Skip link',
2016
- element: $module,
2017
- expectedType: 'HTMLAnchorElement',
2018
- identifier: 'Root element (`$module`)'
2004
+ component: ServiceNavigation,
2005
+ identifier: 'Navigation button (`<button class="govuk-js-service-navigation-toggle">`) attribute (`aria-controls`)'
2006
+ });
2007
+ }
2008
+ const $menu = document.getElementById(menuId);
2009
+ if (!$menu) {
2010
+ throw new ElementError({
2011
+ component: ServiceNavigation,
2012
+ element: $menu,
2013
+ identifier: `Navigation (\`<ul id="${menuId}">\`)`
2014
+ });
2015
+ }
2016
+ this.$menu = $menu;
2017
+ this.$menuButton = $menuButton;
2018
+ this.setupResponsiveChecks();
2019
+ this.$menuButton.addEventListener('click', () => this.handleMenuButtonClick());
2020
+ }
2021
+ setupResponsiveChecks() {
2022
+ const breakpoint = getBreakpoint('tablet');
2023
+ if (!breakpoint.value) {
2024
+ throw new ElementError({
2025
+ component: ServiceNavigation,
2026
+ identifier: `CSS custom property (\`${breakpoint.property}\`) on pseudo-class \`:root\``
2019
2027
  });
2020
2028
  }
2021
- this.$module = $module;
2022
- const hash = this.$module.hash;
2023
- const href = (_this$$module$getAttr = this.$module.getAttribute('href')) != null ? _this$$module$getAttr : '';
2029
+ this.mql = window.matchMedia(`(min-width: ${breakpoint.value})`);
2030
+ if ('addEventListener' in this.mql) {
2031
+ this.mql.addEventListener('change', () => this.checkMode());
2032
+ } else {
2033
+ this.mql.addListener(() => this.checkMode());
2034
+ }
2035
+ this.checkMode();
2036
+ }
2037
+ checkMode() {
2038
+ if (!this.mql || !this.$menu || !this.$menuButton) {
2039
+ return;
2040
+ }
2041
+ if (this.mql.matches) {
2042
+ this.$menu.removeAttribute('hidden');
2043
+ this.$menuButton.setAttribute('hidden', '');
2044
+ } else {
2045
+ this.$menuButton.removeAttribute('hidden');
2046
+ this.$menuButton.setAttribute('aria-expanded', this.menuIsOpen.toString());
2047
+ if (this.menuIsOpen) {
2048
+ this.$menu.removeAttribute('hidden');
2049
+ } else {
2050
+ this.$menu.setAttribute('hidden', '');
2051
+ }
2052
+ }
2053
+ }
2054
+ handleMenuButtonClick() {
2055
+ this.menuIsOpen = !this.menuIsOpen;
2056
+ this.checkMode();
2057
+ }
2058
+ }
2059
+ ServiceNavigation.moduleName = 'govuk-service-navigation';
2060
+
2061
+ /**
2062
+ * Skip link component
2063
+ *
2064
+ * @preserve
2065
+ * @augments GOVUKFrontendComponent<HTMLAnchorElement>
2066
+ */
2067
+ class SkipLink extends GOVUKFrontendComponent {
2068
+ /**
2069
+ * @param {Element | null} $root - HTML element to use for skip link
2070
+ * @throws {ElementError} when $root is not set or the wrong type
2071
+ * @throws {ElementError} when $root.hash does not contain a hash
2072
+ * @throws {ElementError} when the linked element is missing or the wrong type
2073
+ */
2074
+ constructor($root) {
2075
+ var _this$$root$getAttrib;
2076
+ super($root);
2077
+ const hash = this.$root.hash;
2078
+ const href = (_this$$root$getAttrib = this.$root.getAttribute('href')) != null ? _this$$root$getAttrib : '';
2024
2079
  let url;
2025
2080
  try {
2026
- url = new window.URL(this.$module.href);
2081
+ url = new window.URL(this.$root.href);
2027
2082
  } catch (error) {
2028
2083
  throw new ElementError(`Skip link: Target link (\`href="${href}"\`) is invalid`);
2029
2084
  }
@@ -2037,12 +2092,12 @@
2037
2092
  const $linkedElement = document.getElementById(linkedElementId);
2038
2093
  if (!$linkedElement) {
2039
2094
  throw new ElementError({
2040
- componentName: 'Skip link',
2095
+ component: SkipLink,
2041
2096
  element: $linkedElement,
2042
2097
  identifier: `Target content (\`id="${linkedElementId}"\`)`
2043
2098
  });
2044
2099
  }
2045
- this.$module.addEventListener('click', () => setFocus($linkedElement, {
2100
+ this.$root.addEventListener('click', () => setFocus($linkedElement, {
2046
2101
  onBeforeFocus() {
2047
2102
  $linkedElement.classList.add('govuk-skip-link-focused-element');
2048
2103
  },
@@ -2052,6 +2107,7 @@
2052
2107
  }));
2053
2108
  }
2054
2109
  }
2110
+ SkipLink.elementType = HTMLAnchorElement;
2055
2111
  SkipLink.moduleName = 'govuk-skip-link';
2056
2112
 
2057
2113
  /**
@@ -2061,11 +2117,10 @@
2061
2117
  */
2062
2118
  class Tabs extends GOVUKFrontendComponent {
2063
2119
  /**
2064
- * @param {Element | null} $module - HTML element to use for tabs
2120
+ * @param {Element | null} $root - HTML element to use for tabs
2065
2121
  */
2066
- constructor($module) {
2067
- super();
2068
- this.$module = void 0;
2122
+ constructor($root) {
2123
+ super($root);
2069
2124
  this.$tabs = void 0;
2070
2125
  this.$tabList = void 0;
2071
2126
  this.$tabListItems = void 0;
@@ -2075,36 +2130,28 @@
2075
2130
  this.boundTabKeydown = void 0;
2076
2131
  this.boundOnHashChange = void 0;
2077
2132
  this.mql = null;
2078
- if (!$module) {
2079
- throw new ElementError({
2080
- componentName: 'Tabs',
2081
- element: $module,
2082
- identifier: 'Root element (`$module`)'
2083
- });
2084
- }
2085
- const $tabs = $module.querySelectorAll('a.govuk-tabs__tab');
2133
+ const $tabs = this.$root.querySelectorAll('a.govuk-tabs__tab');
2086
2134
  if (!$tabs.length) {
2087
2135
  throw new ElementError({
2088
- componentName: 'Tabs',
2136
+ component: Tabs,
2089
2137
  identifier: 'Links (`<a class="govuk-tabs__tab">`)'
2090
2138
  });
2091
2139
  }
2092
- this.$module = $module;
2093
2140
  this.$tabs = $tabs;
2094
2141
  this.boundTabClick = this.onTabClick.bind(this);
2095
2142
  this.boundTabKeydown = this.onTabKeydown.bind(this);
2096
2143
  this.boundOnHashChange = this.onHashChange.bind(this);
2097
- const $tabList = this.$module.querySelector('.govuk-tabs__list');
2098
- const $tabListItems = this.$module.querySelectorAll('li.govuk-tabs__list-item');
2144
+ const $tabList = this.$root.querySelector('.govuk-tabs__list');
2145
+ const $tabListItems = this.$root.querySelectorAll('li.govuk-tabs__list-item');
2099
2146
  if (!$tabList) {
2100
2147
  throw new ElementError({
2101
- componentName: 'Tabs',
2148
+ component: Tabs,
2102
2149
  identifier: 'List (`<ul class="govuk-tabs__list">`)'
2103
2150
  });
2104
2151
  }
2105
2152
  if (!$tabListItems.length) {
2106
2153
  throw new ElementError({
2107
- componentName: 'Tabs',
2154
+ component: Tabs,
2108
2155
  identifier: 'List items (`<li class="govuk-tabs__list-item">`)'
2109
2156
  });
2110
2157
  }
@@ -2116,7 +2163,7 @@
2116
2163
  const breakpoint = getBreakpoint('tablet');
2117
2164
  if (!breakpoint.value) {
2118
2165
  throw new ElementError({
2119
- componentName: 'Tabs',
2166
+ component: Tabs,
2120
2167
  identifier: `CSS custom property (\`${breakpoint.property}\`) on pseudo-class \`:root\``
2121
2168
  });
2122
2169
  }
@@ -2191,7 +2238,7 @@
2191
2238
  this.showPanel($tab);
2192
2239
  }
2193
2240
  getTab(hash) {
2194
- return this.$module.querySelector(`a.govuk-tabs__tab[href="${hash}"]`);
2241
+ return this.$root.querySelector(`a.govuk-tabs__tab[href="${hash}"]`);
2195
2242
  }
2196
2243
  setAttributes($tab) {
2197
2244
  const panelId = getFragmentFromUrl($tab.href);
@@ -2302,7 +2349,7 @@
2302
2349
  if (!panelId) {
2303
2350
  return null;
2304
2351
  }
2305
- return this.$module.querySelector(`#${panelId}`);
2352
+ return this.$root.querySelector(`#${panelId}`);
2306
2353
  }
2307
2354
  showPanel($tab) {
2308
2355
  const $panel = this.getPanel($tab);
@@ -2335,7 +2382,7 @@
2335
2382
  $tab.setAttribute('tabindex', '0');
2336
2383
  }
2337
2384
  getCurrentTab() {
2338
- return this.$module.querySelector('.govuk-tabs__list-item--selected a.govuk-tabs__tab');
2385
+ return this.$root.querySelector('.govuk-tabs__list-item--selected a.govuk-tabs__tab');
2339
2386
  }
2340
2387
  }
2341
2388
  Tabs.moduleName = 'govuk-tabs';
@@ -2346,19 +2393,28 @@
2346
2393
  * Use the `data-module` attributes to find, instantiate and init all of the
2347
2394
  * components provided as part of GOV.UK Frontend.
2348
2395
  *
2349
- * @param {Config & { scope?: Element }} [config] - Config for all components (with optional scope)
2396
+ * @param {Config & { scope?: Element, onError?: OnErrorCallback<CompatibleClass> }} [config] - Config for all components (with optional scope)
2350
2397
  */
2351
2398
  function initAll(config) {
2352
2399
  var _config$scope;
2353
2400
  config = typeof config !== 'undefined' ? config : {};
2354
2401
  if (!isSupported()) {
2355
- console.log(new SupportError());
2402
+ if (config.onError) {
2403
+ config.onError(new SupportError(), {
2404
+ config
2405
+ });
2406
+ } else {
2407
+ console.log(new SupportError());
2408
+ }
2356
2409
  return;
2357
2410
  }
2358
- 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]];
2359
- const $scope = (_config$scope = config.scope) != null ? _config$scope : document;
2411
+ const components = [[Accordion, config.accordion], [Button, config.button], [CharacterCount, config.characterCount], [Checkboxes], [ErrorSummary, config.errorSummary], [ExitThisPage, config.exitThisPage], [Header], [NotificationBanner, config.notificationBanner], [PasswordInput, config.passwordInput], [Radios], [ServiceNavigation], [SkipLink], [Tabs]];
2412
+ const options = {
2413
+ scope: (_config$scope = config.scope) != null ? _config$scope : document,
2414
+ onError: config.onError
2415
+ };
2360
2416
  components.forEach(([Component, config]) => {
2361
- createAll(Component, config, $scope);
2417
+ createAll(Component, config, options);
2362
2418
  });
2363
2419
  }
2364
2420
 
@@ -2373,17 +2429,50 @@
2373
2429
  *
2374
2430
  * @template {CompatibleClass} T
2375
2431
  * @param {T} Component - class of the component to create
2376
- * @param {T["defaults"]} [config] - config for the component
2377
- * @param {Element|Document} [$scope] - scope of the document to search within
2432
+ * @param {T["defaults"]} [config] - Config supplied to component
2433
+ * @param {OnErrorCallback<T> | Element | Document | CreateAllOptions<T> } [createAllOptions] - options for createAll including scope of the document to search within and callback function if error throw by component on init
2378
2434
  * @returns {Array<InstanceType<T>>} - array of instantiated components
2379
2435
  */
2380
- function createAll(Component, config, $scope = document) {
2436
+ function createAll(Component, config, createAllOptions) {
2437
+ let $scope = document;
2438
+ let onError;
2439
+ if (typeof createAllOptions === 'object') {
2440
+ var _createAllOptions$sco;
2441
+ createAllOptions = createAllOptions;
2442
+ $scope = (_createAllOptions$sco = createAllOptions.scope) != null ? _createAllOptions$sco : $scope;
2443
+ onError = createAllOptions.onError;
2444
+ }
2445
+ if (typeof createAllOptions === 'function') {
2446
+ onError = createAllOptions;
2447
+ }
2448
+ if (createAllOptions instanceof HTMLElement) {
2449
+ $scope = createAllOptions;
2450
+ }
2381
2451
  const $elements = $scope.querySelectorAll(`[data-module="${Component.moduleName}"]`);
2452
+ if (!isSupported()) {
2453
+ if (onError) {
2454
+ onError(new SupportError(), {
2455
+ component: Component,
2456
+ config
2457
+ });
2458
+ } else {
2459
+ console.log(new SupportError());
2460
+ }
2461
+ return [];
2462
+ }
2382
2463
  return Array.from($elements).map($element => {
2383
2464
  try {
2384
- return 'defaults' in Component && typeof config !== 'undefined' ? new Component($element, config) : new Component($element);
2465
+ return typeof config !== 'undefined' ? new Component($element, config) : new Component($element);
2385
2466
  } catch (error) {
2386
- console.log(error);
2467
+ if (onError) {
2468
+ onError(error, {
2469
+ element: $element,
2470
+ component: Component,
2471
+ config
2472
+ });
2473
+ } else {
2474
+ console.log(error);
2475
+ }
2387
2476
  return null;
2388
2477
  }
2389
2478
  }).filter(Boolean);
@@ -2422,21 +2511,43 @@
2422
2511
  *
2423
2512
  * @typedef {keyof Config} ConfigKey
2424
2513
  */
2514
+ /**
2515
+ * @template {CompatibleClass} T
2516
+ * @typedef {object} ErrorContext
2517
+ * @property {Element} [element] - Element used for component module initialisation
2518
+ * @property {T} [component] - Class of component
2519
+ * @property {T["defaults"]} config - Config supplied to component
2520
+ */
2521
+ /**
2522
+ * @template {CompatibleClass} T
2523
+ * @callback OnErrorCallback
2524
+ * @param {unknown} error - Thrown error
2525
+ * @param {ErrorContext<T>} context - Object containing the element, component class and configuration
2526
+ */
2527
+ /**
2528
+ * @template {CompatibleClass} T
2529
+ * @typedef {object} CreateAllOptions
2530
+ * @property {Element | Document} [scope] - scope of the document to search within
2531
+ * @property {OnErrorCallback<T>} [onError] - callback function if error throw by component on init
2532
+ */
2425
2533
 
2426
2534
  exports.Accordion = Accordion;
2427
2535
  exports.Button = Button;
2428
2536
  exports.CharacterCount = CharacterCount;
2429
2537
  exports.Checkboxes = Checkboxes;
2538
+ exports.Component = GOVUKFrontendComponent;
2430
2539
  exports.ErrorSummary = ErrorSummary;
2431
2540
  exports.ExitThisPage = ExitThisPage;
2432
2541
  exports.Header = Header;
2433
2542
  exports.NotificationBanner = NotificationBanner;
2434
2543
  exports.PasswordInput = PasswordInput;
2435
2544
  exports.Radios = Radios;
2545
+ exports.ServiceNavigation = ServiceNavigation;
2436
2546
  exports.SkipLink = SkipLink;
2437
2547
  exports.Tabs = Tabs;
2438
2548
  exports.createAll = createAll;
2439
2549
  exports.initAll = initAll;
2550
+ exports.isSupported = isSupported;
2440
2551
  exports.version = version;
2441
2552
 
2442
2553
  }));