govuk_tech_docs 4.2.0 → 4.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +10 -0
  3. data/govuk_tech_docs.gemspec +1 -1
  4. data/lib/assets/stylesheets/_govuk_tech_docs.scss +3 -0
  5. data/lib/govuk_tech_docs/version.rb +1 -1
  6. data/node_modules/govuk-frontend/dist/govuk/all.bundle.js +508 -209
  7. data/node_modules/govuk-frontend/dist/govuk/all.bundle.mjs +505 -208
  8. data/node_modules/govuk-frontend/dist/govuk/all.mjs +3 -1
  9. data/node_modules/govuk-frontend/dist/govuk/all.scss +6 -0
  10. data/node_modules/govuk-frontend/dist/govuk/common/configuration.mjs +169 -0
  11. data/node_modules/govuk-frontend/dist/govuk/common/govuk-frontend-version.mjs +1 -1
  12. data/node_modules/govuk-frontend/dist/govuk/common/index.mjs +4 -87
  13. data/node_modules/govuk-frontend/dist/govuk/{govuk-frontend-component.mjs → component.mjs} +5 -5
  14. data/node_modules/govuk-frontend/dist/govuk/components/accordion/accordion.bundle.js +161 -116
  15. data/node_modules/govuk-frontend/dist/govuk/components/accordion/accordion.bundle.mjs +160 -115
  16. data/node_modules/govuk-frontend/dist/govuk/components/accordion/accordion.mjs +5 -8
  17. data/node_modules/govuk-frontend/dist/govuk/components/button/button.bundle.js +161 -116
  18. data/node_modules/govuk-frontend/dist/govuk/components/button/button.bundle.mjs +160 -115
  19. data/node_modules/govuk-frontend/dist/govuk/components/button/button.mjs +5 -8
  20. data/node_modules/govuk-frontend/dist/govuk/components/character-count/_index.scss +8 -0
  21. data/node_modules/govuk-frontend/dist/govuk/components/character-count/character-count.bundle.js +187 -145
  22. data/node_modules/govuk-frontend/dist/govuk/components/character-count/character-count.bundle.mjs +186 -144
  23. data/node_modules/govuk-frontend/dist/govuk/components/character-count/character-count.mjs +18 -17
  24. data/node_modules/govuk-frontend/dist/govuk/components/checkboxes/checkboxes.bundle.js +9 -29
  25. data/node_modules/govuk-frontend/dist/govuk/components/checkboxes/checkboxes.bundle.mjs +8 -28
  26. data/node_modules/govuk-frontend/dist/govuk/components/checkboxes/checkboxes.mjs +2 -2
  27. data/node_modules/govuk-frontend/dist/govuk/components/error-summary/error-summary.bundle.js +161 -116
  28. data/node_modules/govuk-frontend/dist/govuk/components/error-summary/error-summary.bundle.mjs +160 -115
  29. data/node_modules/govuk-frontend/dist/govuk/components/error-summary/error-summary.mjs +6 -8
  30. data/node_modules/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.bundle.js +161 -116
  31. data/node_modules/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.bundle.mjs +160 -115
  32. data/node_modules/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.mjs +5 -8
  33. data/node_modules/govuk-frontend/dist/govuk/components/file-upload/_index.scss +167 -0
  34. data/node_modules/govuk-frontend/dist/govuk/components/file-upload/file-upload.bundle.js +754 -0
  35. data/node_modules/govuk-frontend/dist/govuk/components/file-upload/file-upload.bundle.mjs +746 -0
  36. data/node_modules/govuk-frontend/dist/govuk/components/file-upload/file-upload.mjs +267 -0
  37. data/node_modules/govuk-frontend/dist/govuk/components/header/_index.scss +14 -10
  38. data/node_modules/govuk-frontend/dist/govuk/components/header/header.bundle.js +9 -29
  39. data/node_modules/govuk-frontend/dist/govuk/components/header/header.bundle.mjs +8 -28
  40. data/node_modules/govuk-frontend/dist/govuk/components/header/header.mjs +2 -2
  41. data/node_modules/govuk-frontend/dist/govuk/components/notification-banner/notification-banner.bundle.js +161 -116
  42. data/node_modules/govuk-frontend/dist/govuk/components/notification-banner/notification-banner.bundle.mjs +160 -115
  43. data/node_modules/govuk-frontend/dist/govuk/components/notification-banner/notification-banner.mjs +6 -8
  44. data/node_modules/govuk-frontend/dist/govuk/components/password-input/password-input.bundle.js +161 -117
  45. data/node_modules/govuk-frontend/dist/govuk/components/password-input/password-input.bundle.mjs +160 -116
  46. data/node_modules/govuk-frontend/dist/govuk/components/password-input/password-input.mjs +5 -9
  47. data/node_modules/govuk-frontend/dist/govuk/components/radios/radios.bundle.js +9 -29
  48. data/node_modules/govuk-frontend/dist/govuk/components/radios/radios.bundle.mjs +8 -28
  49. data/node_modules/govuk-frontend/dist/govuk/components/radios/radios.mjs +2 -2
  50. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/service-navigation.bundle.js +9 -29
  51. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/service-navigation.bundle.mjs +8 -28
  52. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/service-navigation.mjs +2 -2
  53. data/node_modules/govuk-frontend/dist/govuk/components/skip-link/skip-link.bundle.js +10 -30
  54. data/node_modules/govuk-frontend/dist/govuk/components/skip-link/skip-link.bundle.mjs +9 -29
  55. data/node_modules/govuk-frontend/dist/govuk/components/skip-link/skip-link.mjs +3 -3
  56. data/node_modules/govuk-frontend/dist/govuk/components/summary-list/_index.scss +12 -21
  57. data/node_modules/govuk-frontend/dist/govuk/components/tabs/tabs.bundle.js +9 -29
  58. data/node_modules/govuk-frontend/dist/govuk/components/tabs/tabs.bundle.mjs +8 -28
  59. data/node_modules/govuk-frontend/dist/govuk/components/tabs/tabs.mjs +2 -2
  60. data/node_modules/govuk-frontend/dist/govuk/core/_govuk-frontend-properties.scss +1 -1
  61. data/node_modules/govuk-frontend/dist/govuk/errors/index.mjs +1 -1
  62. data/node_modules/govuk-frontend/dist/govuk/govuk-frontend.min.js +1 -1
  63. data/node_modules/govuk-frontend/dist/govuk/helpers/_colour.scss +2 -2
  64. data/node_modules/govuk-frontend/dist/govuk/init.mjs +28 -24
  65. data/node_modules/govuk-frontend/dist/govuk/settings/_colours-organisations.scss +18 -5
  66. data/node_modules/govuk-frontend/dist/govuk/settings/_typography-responsive.scss +5 -10
  67. data/node_modules/govuk-frontend/dist/govuk-prototype-kit/init.scss +1 -1
  68. data/package-lock.json +8 -7
  69. data/package.json +1 -1
  70. metadata +12 -10
  71. data/node_modules/govuk-frontend/dist/govuk/common/normalise-dataset.mjs +0 -18
  72. data/node_modules/govuk-frontend/dist/govuk/common/normalise-string.mjs +0 -31
@@ -1,79 +1,9 @@
1
1
  (function (global, factory) {
2
2
  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
3
3
  typeof define === 'function' && define.amd ? define(['exports'], factory) :
4
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.GOVUKFrontend = {}));
4
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.GOVUKFrontend = global.GOVUKFrontend || {}));
5
5
  })(this, (function (exports) { 'use strict';
6
6
 
7
- function normaliseString(value, property) {
8
- const trimmedValue = value ? value.trim() : '';
9
- let output;
10
- let outputType = property == null ? void 0 : property.type;
11
- if (!outputType) {
12
- if (['true', 'false'].includes(trimmedValue)) {
13
- outputType = 'boolean';
14
- }
15
- if (trimmedValue.length > 0 && isFinite(Number(trimmedValue))) {
16
- outputType = 'number';
17
- }
18
- }
19
- switch (outputType) {
20
- case 'boolean':
21
- output = trimmedValue === 'true';
22
- break;
23
- case 'number':
24
- output = Number(trimmedValue);
25
- break;
26
- default:
27
- output = value;
28
- }
29
- return output;
30
- }
31
-
32
- /**
33
- * @typedef {import('./index.mjs').SchemaProperty} SchemaProperty
34
- */
35
-
36
- function mergeConfigs(...configObjects) {
37
- const formattedConfigObject = {};
38
- for (const configObject of configObjects) {
39
- for (const key of Object.keys(configObject)) {
40
- const option = formattedConfigObject[key];
41
- const override = configObject[key];
42
- if (isObject(option) && isObject(override)) {
43
- formattedConfigObject[key] = mergeConfigs(option, override);
44
- } else {
45
- formattedConfigObject[key] = override;
46
- }
47
- }
48
- }
49
- return formattedConfigObject;
50
- }
51
- function extractConfigByNamespace(Component, dataset, namespace) {
52
- const property = Component.schema.properties[namespace];
53
- if ((property == null ? void 0 : property.type) !== 'object') {
54
- return;
55
- }
56
- const newObject = {
57
- [namespace]: ({})
58
- };
59
- for (const [key, value] of Object.entries(dataset)) {
60
- let current = newObject;
61
- const keyParts = key.split('.');
62
- for (const [index, name] of keyParts.entries()) {
63
- if (typeof current === 'object') {
64
- if (index < keyParts.length - 1) {
65
- if (!isObject(current[name])) {
66
- current[name] = {};
67
- }
68
- current = current[name];
69
- } else if (key !== namespace) {
70
- current[name] = normaliseString(value);
71
- }
72
- }
73
- }
74
- }
75
- return newObject[namespace];
76
- }
77
7
  function setFocus($element, options = {}) {
78
8
  var _options$onBeforeFocu;
79
9
  const isFocusable = $element.getAttribute('tabindex');
@@ -126,46 +56,13 @@
126
56
  function formatErrorMessage(Component, message) {
127
57
  return `${Component.moduleName}: ${message}`;
128
58
  }
129
-
130
- /**
131
- * Schema for component config
132
- *
133
- * @typedef {object} Schema
134
- * @property {{ [field: string]: SchemaProperty | undefined }} properties - Schema properties
135
- * @property {SchemaCondition[]} [anyOf] - List of schema conditions
136
- */
137
-
138
- /**
139
- * Schema property for component config
140
- *
141
- * @typedef {object} SchemaProperty
142
- * @property {'string' | 'boolean' | 'number' | 'object'} type - Property type
143
- */
144
-
145
- /**
146
- * Schema condition for component config
147
- *
148
- * @typedef {object} SchemaCondition
149
- * @property {string[]} required - List of required config fields
150
- * @property {string} errorMessage - Error message when required config fields not provided
151
- */
152
59
  /**
153
60
  * @typedef ComponentWithModuleName
154
61
  * @property {string} moduleName - Name of the component
155
62
  */
156
-
157
- function normaliseDataset(Component, dataset) {
158
- const out = {};
159
- for (const [field, property] of Object.entries(Component.schema.properties)) {
160
- if (field in dataset) {
161
- out[field] = normaliseString(dataset[field], property);
162
- }
163
- if ((property == null ? void 0 : property.type) === 'object') {
164
- out[field] = extractConfigByNamespace(Component, dataset, field);
165
- }
166
- }
167
- return out;
168
- }
63
+ /**
64
+ * @import { ObjectNested } from './configuration.mjs'
65
+ */
169
66
 
170
67
  class GOVUKFrontendError extends Error {
171
68
  constructor(...args) {
@@ -185,6 +82,12 @@
185
82
  this.name = 'SupportError';
186
83
  }
187
84
  }
85
+ class ConfigError extends GOVUKFrontendError {
86
+ constructor(...args) {
87
+ super(...args);
88
+ this.name = 'ConfigError';
89
+ }
90
+ }
188
91
  class ElementError extends GOVUKFrontendError {
189
92
  constructor(messageOrOptions) {
190
93
  let message = typeof messageOrOptions === 'string' ? messageOrOptions : '';
@@ -211,10 +114,10 @@
211
114
  }
212
115
  }
213
116
  /**
214
- * @typedef {import('../common/index.mjs').ComponentWithModuleName} ComponentWithModuleName
117
+ * @import { ComponentWithModuleName } from '../common/index.mjs'
215
118
  */
216
119
 
217
- class GOVUKFrontendComponent {
120
+ class Component {
218
121
  /**
219
122
  * Returns the root element of the component
220
123
  *
@@ -265,24 +168,166 @@
265
168
  */
266
169
 
267
170
  /**
268
- * @typedef {typeof GOVUKFrontendComponent & ChildClass} ChildClassConstructor
171
+ * @typedef {typeof Component & ChildClass} ChildClassConstructor
172
+ */
173
+ Component.elementType = HTMLElement;
174
+
175
+ const configOverride = Symbol.for('configOverride');
176
+ class ConfigurableComponent extends Component {
177
+ [configOverride](param) {
178
+ return {};
179
+ }
180
+
181
+ /**
182
+ * Returns the root element of the component
183
+ *
184
+ * @protected
185
+ * @returns {ConfigurationType} - the root element of component
186
+ */
187
+ get config() {
188
+ return this._config;
189
+ }
190
+ constructor($root, config) {
191
+ super($root);
192
+ this._config = void 0;
193
+ const childConstructor = this.constructor;
194
+ if (!isObject(childConstructor.defaults)) {
195
+ throw new ConfigError(formatErrorMessage(childConstructor, 'Config passed as parameter into constructor but no defaults defined'));
196
+ }
197
+ const datasetConfig = normaliseDataset(childConstructor, this._$root.dataset);
198
+ this._config = mergeConfigs(childConstructor.defaults, config != null ? config : {}, this[configOverride](datasetConfig), datasetConfig);
199
+ }
200
+ }
201
+ function normaliseString(value, property) {
202
+ const trimmedValue = value ? value.trim() : '';
203
+ let output;
204
+ let outputType = property == null ? void 0 : property.type;
205
+ if (!outputType) {
206
+ if (['true', 'false'].includes(trimmedValue)) {
207
+ outputType = 'boolean';
208
+ }
209
+ if (trimmedValue.length > 0 && isFinite(Number(trimmedValue))) {
210
+ outputType = 'number';
211
+ }
212
+ }
213
+ switch (outputType) {
214
+ case 'boolean':
215
+ output = trimmedValue === 'true';
216
+ break;
217
+ case 'number':
218
+ output = Number(trimmedValue);
219
+ break;
220
+ default:
221
+ output = value;
222
+ }
223
+ return output;
224
+ }
225
+ function normaliseDataset(Component, dataset) {
226
+ if (!isObject(Component.schema)) {
227
+ throw new ConfigError(formatErrorMessage(Component, 'Config passed as parameter into constructor but no schema defined'));
228
+ }
229
+ const out = {};
230
+ const entries = Object.entries(Component.schema.properties);
231
+ for (const entry of entries) {
232
+ const [namespace, property] = entry;
233
+ const field = namespace.toString();
234
+ if (field in dataset) {
235
+ out[field] = normaliseString(dataset[field], property);
236
+ }
237
+ if ((property == null ? void 0 : property.type) === 'object') {
238
+ out[field] = extractConfigByNamespace(Component.schema, dataset, namespace);
239
+ }
240
+ }
241
+ return out;
242
+ }
243
+ function mergeConfigs(...configObjects) {
244
+ const formattedConfigObject = {};
245
+ for (const configObject of configObjects) {
246
+ for (const key of Object.keys(configObject)) {
247
+ const option = formattedConfigObject[key];
248
+ const override = configObject[key];
249
+ if (isObject(option) && isObject(override)) {
250
+ formattedConfigObject[key] = mergeConfigs(option, override);
251
+ } else {
252
+ formattedConfigObject[key] = override;
253
+ }
254
+ }
255
+ }
256
+ return formattedConfigObject;
257
+ }
258
+ function extractConfigByNamespace(schema, dataset, namespace) {
259
+ const property = schema.properties[namespace];
260
+ if ((property == null ? void 0 : property.type) !== 'object') {
261
+ return;
262
+ }
263
+ const newObject = {
264
+ [namespace]: {}
265
+ };
266
+ for (const [key, value] of Object.entries(dataset)) {
267
+ let current = newObject;
268
+ const keyParts = key.split('.');
269
+ for (const [index, name] of keyParts.entries()) {
270
+ if (isObject(current)) {
271
+ if (index < keyParts.length - 1) {
272
+ if (!isObject(current[name])) {
273
+ current[name] = {};
274
+ }
275
+ current = current[name];
276
+ } else if (key !== namespace) {
277
+ current[name] = normaliseString(value);
278
+ }
279
+ }
280
+ }
281
+ }
282
+ return newObject[namespace];
283
+ }
284
+ /**
285
+ * Schema for component config
286
+ *
287
+ * @template {Partial<Record<keyof ConfigurationType, unknown>>} ConfigurationType
288
+ * @typedef {object} Schema
289
+ * @property {Record<keyof ConfigurationType, SchemaProperty | undefined>} properties - Schema properties
290
+ * @property {SchemaCondition<ConfigurationType>[]} [anyOf] - List of schema conditions
291
+ */
292
+ /**
293
+ * Schema property for component config
294
+ *
295
+ * @typedef {object} SchemaProperty
296
+ * @property {'string' | 'boolean' | 'number' | 'object'} type - Property type
297
+ */
298
+ /**
299
+ * Schema condition for component config
300
+ *
301
+ * @template {Partial<Record<keyof ConfigurationType, unknown>>} ConfigurationType
302
+ * @typedef {object} SchemaCondition
303
+ * @property {(keyof ConfigurationType)[]} required - List of required config fields
304
+ * @property {string} errorMessage - Error message when required config fields not provided
305
+ */
306
+ /**
307
+ * @template {Partial<Record<keyof ConfigurationType, unknown>>} [ConfigurationType=ObjectNested]
308
+ * @typedef ChildClass
309
+ * @property {string} moduleName - The module name that'll be looked for in the DOM when initialising the component
310
+ * @property {Schema<ConfigurationType>} [schema] - The schema of the component configuration
311
+ * @property {ConfigurationType} [defaults] - The default values of the configuration of the component
312
+ */
313
+ /**
314
+ * @template {Partial<Record<keyof ConfigurationType, unknown>>} [ConfigurationType=ObjectNested]
315
+ * @typedef {typeof Component & ChildClass<ConfigurationType>} ChildClassConstructor<ConfigurationType>
269
316
  */
270
- GOVUKFrontendComponent.elementType = HTMLElement;
271
317
 
272
318
  /**
273
319
  * Notification Banner component
274
320
  *
275
321
  * @preserve
322
+ * @augments ConfigurableComponent<NotificationBannerConfig>
276
323
  */
277
- class NotificationBanner extends GOVUKFrontendComponent {
324
+ class NotificationBanner extends ConfigurableComponent {
278
325
  /**
279
326
  * @param {Element | null} $root - HTML element to use for notification banner
280
327
  * @param {NotificationBannerConfig} [config] - Notification banner config
281
328
  */
282
329
  constructor($root, config = {}) {
283
- super($root);
284
- this.config = void 0;
285
- this.config = mergeConfigs(NotificationBanner.defaults, config, normaliseDataset(NotificationBanner, this.$root.dataset));
330
+ super($root, config);
286
331
  if (this.$root.getAttribute('role') === 'alert' && !this.config.disableAutoFocus) {
287
332
  setFocus(this.$root);
288
333
  }
@@ -300,7 +345,7 @@
300
345
  */
301
346
 
302
347
  /**
303
- * @typedef {import('../../common/index.mjs').Schema} Schema
348
+ * @import { Schema } from '../../common/configuration.mjs'
304
349
  */
305
350
  NotificationBanner.moduleName = 'govuk-notification-banner';
306
351
  NotificationBanner.defaults = Object.freeze({
@@ -1,73 +1,3 @@
1
- function normaliseString(value, property) {
2
- const trimmedValue = value ? value.trim() : '';
3
- let output;
4
- let outputType = property == null ? void 0 : property.type;
5
- if (!outputType) {
6
- if (['true', 'false'].includes(trimmedValue)) {
7
- outputType = 'boolean';
8
- }
9
- if (trimmedValue.length > 0 && isFinite(Number(trimmedValue))) {
10
- outputType = 'number';
11
- }
12
- }
13
- switch (outputType) {
14
- case 'boolean':
15
- output = trimmedValue === 'true';
16
- break;
17
- case 'number':
18
- output = Number(trimmedValue);
19
- break;
20
- default:
21
- output = value;
22
- }
23
- return output;
24
- }
25
-
26
- /**
27
- * @typedef {import('./index.mjs').SchemaProperty} SchemaProperty
28
- */
29
-
30
- function mergeConfigs(...configObjects) {
31
- const formattedConfigObject = {};
32
- for (const configObject of configObjects) {
33
- for (const key of Object.keys(configObject)) {
34
- const option = formattedConfigObject[key];
35
- const override = configObject[key];
36
- if (isObject(option) && isObject(override)) {
37
- formattedConfigObject[key] = mergeConfigs(option, override);
38
- } else {
39
- formattedConfigObject[key] = override;
40
- }
41
- }
42
- }
43
- return formattedConfigObject;
44
- }
45
- function extractConfigByNamespace(Component, dataset, namespace) {
46
- const property = Component.schema.properties[namespace];
47
- if ((property == null ? void 0 : property.type) !== 'object') {
48
- return;
49
- }
50
- const newObject = {
51
- [namespace]: ({})
52
- };
53
- for (const [key, value] of Object.entries(dataset)) {
54
- let current = newObject;
55
- const keyParts = key.split('.');
56
- for (const [index, name] of keyParts.entries()) {
57
- if (typeof current === 'object') {
58
- if (index < keyParts.length - 1) {
59
- if (!isObject(current[name])) {
60
- current[name] = {};
61
- }
62
- current = current[name];
63
- } else if (key !== namespace) {
64
- current[name] = normaliseString(value);
65
- }
66
- }
67
- }
68
- }
69
- return newObject[namespace];
70
- }
71
1
  function setFocus($element, options = {}) {
72
2
  var _options$onBeforeFocu;
73
3
  const isFocusable = $element.getAttribute('tabindex');
@@ -120,46 +50,13 @@ function isObject(option) {
120
50
  function formatErrorMessage(Component, message) {
121
51
  return `${Component.moduleName}: ${message}`;
122
52
  }
123
-
124
- /**
125
- * Schema for component config
126
- *
127
- * @typedef {object} Schema
128
- * @property {{ [field: string]: SchemaProperty | undefined }} properties - Schema properties
129
- * @property {SchemaCondition[]} [anyOf] - List of schema conditions
130
- */
131
-
132
- /**
133
- * Schema property for component config
134
- *
135
- * @typedef {object} SchemaProperty
136
- * @property {'string' | 'boolean' | 'number' | 'object'} type - Property type
137
- */
138
-
139
- /**
140
- * Schema condition for component config
141
- *
142
- * @typedef {object} SchemaCondition
143
- * @property {string[]} required - List of required config fields
144
- * @property {string} errorMessage - Error message when required config fields not provided
145
- */
146
53
  /**
147
54
  * @typedef ComponentWithModuleName
148
55
  * @property {string} moduleName - Name of the component
149
56
  */
150
-
151
- function normaliseDataset(Component, dataset) {
152
- const out = {};
153
- for (const [field, property] of Object.entries(Component.schema.properties)) {
154
- if (field in dataset) {
155
- out[field] = normaliseString(dataset[field], property);
156
- }
157
- if ((property == null ? void 0 : property.type) === 'object') {
158
- out[field] = extractConfigByNamespace(Component, dataset, field);
159
- }
160
- }
161
- return out;
162
- }
57
+ /**
58
+ * @import { ObjectNested } from './configuration.mjs'
59
+ */
163
60
 
164
61
  class GOVUKFrontendError extends Error {
165
62
  constructor(...args) {
@@ -179,6 +76,12 @@ class SupportError extends GOVUKFrontendError {
179
76
  this.name = 'SupportError';
180
77
  }
181
78
  }
79
+ class ConfigError extends GOVUKFrontendError {
80
+ constructor(...args) {
81
+ super(...args);
82
+ this.name = 'ConfigError';
83
+ }
84
+ }
182
85
  class ElementError extends GOVUKFrontendError {
183
86
  constructor(messageOrOptions) {
184
87
  let message = typeof messageOrOptions === 'string' ? messageOrOptions : '';
@@ -205,10 +108,10 @@ class InitError extends GOVUKFrontendError {
205
108
  }
206
109
  }
207
110
  /**
208
- * @typedef {import('../common/index.mjs').ComponentWithModuleName} ComponentWithModuleName
111
+ * @import { ComponentWithModuleName } from '../common/index.mjs'
209
112
  */
210
113
 
211
- class GOVUKFrontendComponent {
114
+ class Component {
212
115
  /**
213
116
  * Returns the root element of the component
214
117
  *
@@ -259,24 +162,166 @@ class GOVUKFrontendComponent {
259
162
  */
260
163
 
261
164
  /**
262
- * @typedef {typeof GOVUKFrontendComponent & ChildClass} ChildClassConstructor
165
+ * @typedef {typeof Component & ChildClass} ChildClassConstructor
166
+ */
167
+ Component.elementType = HTMLElement;
168
+
169
+ const configOverride = Symbol.for('configOverride');
170
+ class ConfigurableComponent extends Component {
171
+ [configOverride](param) {
172
+ return {};
173
+ }
174
+
175
+ /**
176
+ * Returns the root element of the component
177
+ *
178
+ * @protected
179
+ * @returns {ConfigurationType} - the root element of component
180
+ */
181
+ get config() {
182
+ return this._config;
183
+ }
184
+ constructor($root, config) {
185
+ super($root);
186
+ this._config = void 0;
187
+ const childConstructor = this.constructor;
188
+ if (!isObject(childConstructor.defaults)) {
189
+ throw new ConfigError(formatErrorMessage(childConstructor, 'Config passed as parameter into constructor but no defaults defined'));
190
+ }
191
+ const datasetConfig = normaliseDataset(childConstructor, this._$root.dataset);
192
+ this._config = mergeConfigs(childConstructor.defaults, config != null ? config : {}, this[configOverride](datasetConfig), datasetConfig);
193
+ }
194
+ }
195
+ function normaliseString(value, property) {
196
+ const trimmedValue = value ? value.trim() : '';
197
+ let output;
198
+ let outputType = property == null ? void 0 : property.type;
199
+ if (!outputType) {
200
+ if (['true', 'false'].includes(trimmedValue)) {
201
+ outputType = 'boolean';
202
+ }
203
+ if (trimmedValue.length > 0 && isFinite(Number(trimmedValue))) {
204
+ outputType = 'number';
205
+ }
206
+ }
207
+ switch (outputType) {
208
+ case 'boolean':
209
+ output = trimmedValue === 'true';
210
+ break;
211
+ case 'number':
212
+ output = Number(trimmedValue);
213
+ break;
214
+ default:
215
+ output = value;
216
+ }
217
+ return output;
218
+ }
219
+ function normaliseDataset(Component, dataset) {
220
+ if (!isObject(Component.schema)) {
221
+ throw new ConfigError(formatErrorMessage(Component, 'Config passed as parameter into constructor but no schema defined'));
222
+ }
223
+ const out = {};
224
+ const entries = Object.entries(Component.schema.properties);
225
+ for (const entry of entries) {
226
+ const [namespace, property] = entry;
227
+ const field = namespace.toString();
228
+ if (field in dataset) {
229
+ out[field] = normaliseString(dataset[field], property);
230
+ }
231
+ if ((property == null ? void 0 : property.type) === 'object') {
232
+ out[field] = extractConfigByNamespace(Component.schema, dataset, namespace);
233
+ }
234
+ }
235
+ return out;
236
+ }
237
+ function mergeConfigs(...configObjects) {
238
+ const formattedConfigObject = {};
239
+ for (const configObject of configObjects) {
240
+ for (const key of Object.keys(configObject)) {
241
+ const option = formattedConfigObject[key];
242
+ const override = configObject[key];
243
+ if (isObject(option) && isObject(override)) {
244
+ formattedConfigObject[key] = mergeConfigs(option, override);
245
+ } else {
246
+ formattedConfigObject[key] = override;
247
+ }
248
+ }
249
+ }
250
+ return formattedConfigObject;
251
+ }
252
+ function extractConfigByNamespace(schema, dataset, namespace) {
253
+ const property = schema.properties[namespace];
254
+ if ((property == null ? void 0 : property.type) !== 'object') {
255
+ return;
256
+ }
257
+ const newObject = {
258
+ [namespace]: {}
259
+ };
260
+ for (const [key, value] of Object.entries(dataset)) {
261
+ let current = newObject;
262
+ const keyParts = key.split('.');
263
+ for (const [index, name] of keyParts.entries()) {
264
+ if (isObject(current)) {
265
+ if (index < keyParts.length - 1) {
266
+ if (!isObject(current[name])) {
267
+ current[name] = {};
268
+ }
269
+ current = current[name];
270
+ } else if (key !== namespace) {
271
+ current[name] = normaliseString(value);
272
+ }
273
+ }
274
+ }
275
+ }
276
+ return newObject[namespace];
277
+ }
278
+ /**
279
+ * Schema for component config
280
+ *
281
+ * @template {Partial<Record<keyof ConfigurationType, unknown>>} ConfigurationType
282
+ * @typedef {object} Schema
283
+ * @property {Record<keyof ConfigurationType, SchemaProperty | undefined>} properties - Schema properties
284
+ * @property {SchemaCondition<ConfigurationType>[]} [anyOf] - List of schema conditions
285
+ */
286
+ /**
287
+ * Schema property for component config
288
+ *
289
+ * @typedef {object} SchemaProperty
290
+ * @property {'string' | 'boolean' | 'number' | 'object'} type - Property type
291
+ */
292
+ /**
293
+ * Schema condition for component config
294
+ *
295
+ * @template {Partial<Record<keyof ConfigurationType, unknown>>} ConfigurationType
296
+ * @typedef {object} SchemaCondition
297
+ * @property {(keyof ConfigurationType)[]} required - List of required config fields
298
+ * @property {string} errorMessage - Error message when required config fields not provided
299
+ */
300
+ /**
301
+ * @template {Partial<Record<keyof ConfigurationType, unknown>>} [ConfigurationType=ObjectNested]
302
+ * @typedef ChildClass
303
+ * @property {string} moduleName - The module name that'll be looked for in the DOM when initialising the component
304
+ * @property {Schema<ConfigurationType>} [schema] - The schema of the component configuration
305
+ * @property {ConfigurationType} [defaults] - The default values of the configuration of the component
306
+ */
307
+ /**
308
+ * @template {Partial<Record<keyof ConfigurationType, unknown>>} [ConfigurationType=ObjectNested]
309
+ * @typedef {typeof Component & ChildClass<ConfigurationType>} ChildClassConstructor<ConfigurationType>
263
310
  */
264
- GOVUKFrontendComponent.elementType = HTMLElement;
265
311
 
266
312
  /**
267
313
  * Notification Banner component
268
314
  *
269
315
  * @preserve
316
+ * @augments ConfigurableComponent<NotificationBannerConfig>
270
317
  */
271
- class NotificationBanner extends GOVUKFrontendComponent {
318
+ class NotificationBanner extends ConfigurableComponent {
272
319
  /**
273
320
  * @param {Element | null} $root - HTML element to use for notification banner
274
321
  * @param {NotificationBannerConfig} [config] - Notification banner config
275
322
  */
276
323
  constructor($root, config = {}) {
277
- super($root);
278
- this.config = void 0;
279
- this.config = mergeConfigs(NotificationBanner.defaults, config, normaliseDataset(NotificationBanner, this.$root.dataset));
324
+ super($root, config);
280
325
  if (this.$root.getAttribute('role') === 'alert' && !this.config.disableAutoFocus) {
281
326
  setFocus(this.$root);
282
327
  }
@@ -294,7 +339,7 @@ class NotificationBanner extends GOVUKFrontendComponent {
294
339
  */
295
340
 
296
341
  /**
297
- * @typedef {import('../../common/index.mjs').Schema} Schema
342
+ * @import { Schema } from '../../common/configuration.mjs'
298
343
  */
299
344
  NotificationBanner.moduleName = 'govuk-notification-banner';
300
345
  NotificationBanner.defaults = Object.freeze({
@@ -1,21 +1,19 @@
1
- import { mergeConfigs, setFocus } from '../../common/index.mjs';
2
- import { normaliseDataset } from '../../common/normalise-dataset.mjs';
3
- import { GOVUKFrontendComponent } from '../../govuk-frontend-component.mjs';
1
+ import { ConfigurableComponent } from '../../common/configuration.mjs';
2
+ import { setFocus } from '../../common/index.mjs';
4
3
 
5
4
  /**
6
5
  * Notification Banner component
7
6
  *
8
7
  * @preserve
8
+ * @augments ConfigurableComponent<NotificationBannerConfig>
9
9
  */
10
- class NotificationBanner extends GOVUKFrontendComponent {
10
+ class NotificationBanner extends ConfigurableComponent {
11
11
  /**
12
12
  * @param {Element | null} $root - HTML element to use for notification banner
13
13
  * @param {NotificationBannerConfig} [config] - Notification banner config
14
14
  */
15
15
  constructor($root, config = {}) {
16
- super($root);
17
- this.config = void 0;
18
- this.config = mergeConfigs(NotificationBanner.defaults, config, normaliseDataset(NotificationBanner, this.$root.dataset));
16
+ super($root, config);
19
17
  if (this.$root.getAttribute('role') === 'alert' && !this.config.disableAutoFocus) {
20
18
  setFocus(this.$root);
21
19
  }
@@ -33,7 +31,7 @@ class NotificationBanner extends GOVUKFrontendComponent {
33
31
  */
34
32
 
35
33
  /**
36
- * @typedef {import('../../common/index.mjs').Schema} Schema
34
+ * @import { Schema } from '../../common/configuration.mjs'
37
35
  */
38
36
  NotificationBanner.moduleName = 'govuk-notification-banner';
39
37
  NotificationBanner.defaults = Object.freeze({