govuk_tech_docs 4.2.0 → 4.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +8 -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/lib/source/layouts/core.erb +16 -5
  7. data/node_modules/govuk-frontend/dist/govuk/all.bundle.js +508 -209
  8. data/node_modules/govuk-frontend/dist/govuk/all.bundle.mjs +505 -208
  9. data/node_modules/govuk-frontend/dist/govuk/all.mjs +3 -1
  10. data/node_modules/govuk-frontend/dist/govuk/all.scss +6 -0
  11. data/node_modules/govuk-frontend/dist/govuk/common/configuration.mjs +169 -0
  12. data/node_modules/govuk-frontend/dist/govuk/common/govuk-frontend-version.mjs +1 -1
  13. data/node_modules/govuk-frontend/dist/govuk/common/index.mjs +4 -87
  14. data/node_modules/govuk-frontend/dist/govuk/{govuk-frontend-component.mjs → component.mjs} +5 -5
  15. data/node_modules/govuk-frontend/dist/govuk/components/accordion/accordion.bundle.js +161 -116
  16. data/node_modules/govuk-frontend/dist/govuk/components/accordion/accordion.bundle.mjs +160 -115
  17. data/node_modules/govuk-frontend/dist/govuk/components/accordion/accordion.mjs +5 -8
  18. data/node_modules/govuk-frontend/dist/govuk/components/button/button.bundle.js +161 -116
  19. data/node_modules/govuk-frontend/dist/govuk/components/button/button.bundle.mjs +160 -115
  20. data/node_modules/govuk-frontend/dist/govuk/components/button/button.mjs +5 -8
  21. data/node_modules/govuk-frontend/dist/govuk/components/character-count/_index.scss +8 -0
  22. data/node_modules/govuk-frontend/dist/govuk/components/character-count/character-count.bundle.js +187 -145
  23. data/node_modules/govuk-frontend/dist/govuk/components/character-count/character-count.bundle.mjs +186 -144
  24. data/node_modules/govuk-frontend/dist/govuk/components/character-count/character-count.mjs +18 -17
  25. data/node_modules/govuk-frontend/dist/govuk/components/checkboxes/checkboxes.bundle.js +9 -29
  26. data/node_modules/govuk-frontend/dist/govuk/components/checkboxes/checkboxes.bundle.mjs +8 -28
  27. data/node_modules/govuk-frontend/dist/govuk/components/checkboxes/checkboxes.mjs +2 -2
  28. data/node_modules/govuk-frontend/dist/govuk/components/error-summary/error-summary.bundle.js +161 -116
  29. data/node_modules/govuk-frontend/dist/govuk/components/error-summary/error-summary.bundle.mjs +160 -115
  30. data/node_modules/govuk-frontend/dist/govuk/components/error-summary/error-summary.mjs +6 -8
  31. data/node_modules/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.bundle.js +161 -116
  32. data/node_modules/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.bundle.mjs +160 -115
  33. data/node_modules/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.mjs +5 -8
  34. data/node_modules/govuk-frontend/dist/govuk/components/file-upload/_index.scss +167 -0
  35. data/node_modules/govuk-frontend/dist/govuk/components/file-upload/file-upload.bundle.js +754 -0
  36. data/node_modules/govuk-frontend/dist/govuk/components/file-upload/file-upload.bundle.mjs +746 -0
  37. data/node_modules/govuk-frontend/dist/govuk/components/file-upload/file-upload.mjs +267 -0
  38. data/node_modules/govuk-frontend/dist/govuk/components/header/_index.scss +14 -10
  39. data/node_modules/govuk-frontend/dist/govuk/components/header/header.bundle.js +9 -29
  40. data/node_modules/govuk-frontend/dist/govuk/components/header/header.bundle.mjs +8 -28
  41. data/node_modules/govuk-frontend/dist/govuk/components/header/header.mjs +2 -2
  42. data/node_modules/govuk-frontend/dist/govuk/components/notification-banner/notification-banner.bundle.js +161 -116
  43. data/node_modules/govuk-frontend/dist/govuk/components/notification-banner/notification-banner.bundle.mjs +160 -115
  44. data/node_modules/govuk-frontend/dist/govuk/components/notification-banner/notification-banner.mjs +6 -8
  45. data/node_modules/govuk-frontend/dist/govuk/components/password-input/password-input.bundle.js +161 -117
  46. data/node_modules/govuk-frontend/dist/govuk/components/password-input/password-input.bundle.mjs +160 -116
  47. data/node_modules/govuk-frontend/dist/govuk/components/password-input/password-input.mjs +5 -9
  48. data/node_modules/govuk-frontend/dist/govuk/components/radios/radios.bundle.js +9 -29
  49. data/node_modules/govuk-frontend/dist/govuk/components/radios/radios.bundle.mjs +8 -28
  50. data/node_modules/govuk-frontend/dist/govuk/components/radios/radios.mjs +2 -2
  51. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/service-navigation.bundle.js +9 -29
  52. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/service-navigation.bundle.mjs +8 -28
  53. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/service-navigation.mjs +2 -2
  54. data/node_modules/govuk-frontend/dist/govuk/components/skip-link/skip-link.bundle.js +10 -30
  55. data/node_modules/govuk-frontend/dist/govuk/components/skip-link/skip-link.bundle.mjs +9 -29
  56. data/node_modules/govuk-frontend/dist/govuk/components/skip-link/skip-link.mjs +3 -3
  57. data/node_modules/govuk-frontend/dist/govuk/components/summary-list/_index.scss +12 -21
  58. data/node_modules/govuk-frontend/dist/govuk/components/tabs/tabs.bundle.js +9 -29
  59. data/node_modules/govuk-frontend/dist/govuk/components/tabs/tabs.bundle.mjs +8 -28
  60. data/node_modules/govuk-frontend/dist/govuk/components/tabs/tabs.mjs +2 -2
  61. data/node_modules/govuk-frontend/dist/govuk/core/_govuk-frontend-properties.scss +1 -1
  62. data/node_modules/govuk-frontend/dist/govuk/errors/index.mjs +1 -1
  63. data/node_modules/govuk-frontend/dist/govuk/govuk-frontend.min.js +1 -1
  64. data/node_modules/govuk-frontend/dist/govuk/helpers/_colour.scss +2 -2
  65. data/node_modules/govuk-frontend/dist/govuk/init.mjs +28 -24
  66. data/node_modules/govuk-frontend/dist/govuk/settings/_colours-organisations.scss +18 -5
  67. data/node_modules/govuk-frontend/dist/govuk/settings/_typography-responsive.scss +5 -10
  68. data/node_modules/govuk-frontend/dist/govuk-prototype-kit/init.scss +1 -1
  69. data/package-lock.json +8 -7
  70. data/package.json +1 -1
  71. metadata +12 -10
  72. data/node_modules/govuk-frontend/dist/govuk/common/normalise-dataset.mjs +0 -18
  73. 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 isInitialised($root, moduleName) {
78
8
  return $root instanceof HTMLElement && $root.hasAttribute(`data-${moduleName}-init`);
79
9
  }
@@ -102,46 +32,13 @@
102
32
  function formatErrorMessage(Component, message) {
103
33
  return `${Component.moduleName}: ${message}`;
104
34
  }
105
-
106
- /**
107
- * Schema for component config
108
- *
109
- * @typedef {object} Schema
110
- * @property {{ [field: string]: SchemaProperty | undefined }} properties - Schema properties
111
- * @property {SchemaCondition[]} [anyOf] - List of schema conditions
112
- */
113
-
114
- /**
115
- * Schema property for component config
116
- *
117
- * @typedef {object} SchemaProperty
118
- * @property {'string' | 'boolean' | 'number' | 'object'} type - Property type
119
- */
120
-
121
- /**
122
- * Schema condition for component config
123
- *
124
- * @typedef {object} SchemaCondition
125
- * @property {string[]} required - List of required config fields
126
- * @property {string} errorMessage - Error message when required config fields not provided
127
- */
128
35
  /**
129
36
  * @typedef ComponentWithModuleName
130
37
  * @property {string} moduleName - Name of the component
131
38
  */
132
-
133
- function normaliseDataset(Component, dataset) {
134
- const out = {};
135
- for (const [field, property] of Object.entries(Component.schema.properties)) {
136
- if (field in dataset) {
137
- out[field] = normaliseString(dataset[field], property);
138
- }
139
- if ((property == null ? void 0 : property.type) === 'object') {
140
- out[field] = extractConfigByNamespace(Component, dataset, field);
141
- }
142
- }
143
- return out;
144
- }
39
+ /**
40
+ * @import { ObjectNested } from './configuration.mjs'
41
+ */
145
42
 
146
43
  class GOVUKFrontendError extends Error {
147
44
  constructor(...args) {
@@ -161,6 +58,12 @@
161
58
  this.name = 'SupportError';
162
59
  }
163
60
  }
61
+ class ConfigError extends GOVUKFrontendError {
62
+ constructor(...args) {
63
+ super(...args);
64
+ this.name = 'ConfigError';
65
+ }
66
+ }
164
67
  class ElementError extends GOVUKFrontendError {
165
68
  constructor(messageOrOptions) {
166
69
  let message = typeof messageOrOptions === 'string' ? messageOrOptions : '';
@@ -187,10 +90,10 @@
187
90
  }
188
91
  }
189
92
  /**
190
- * @typedef {import('../common/index.mjs').ComponentWithModuleName} ComponentWithModuleName
93
+ * @import { ComponentWithModuleName } from '../common/index.mjs'
191
94
  */
192
95
 
193
- class GOVUKFrontendComponent {
96
+ class Component {
194
97
  /**
195
98
  * Returns the root element of the component
196
99
  *
@@ -241,9 +144,152 @@
241
144
  */
242
145
 
243
146
  /**
244
- * @typedef {typeof GOVUKFrontendComponent & ChildClass} ChildClassConstructor
147
+ * @typedef {typeof Component & ChildClass} ChildClassConstructor
148
+ */
149
+ Component.elementType = HTMLElement;
150
+
151
+ const configOverride = Symbol.for('configOverride');
152
+ class ConfigurableComponent extends Component {
153
+ [configOverride](param) {
154
+ return {};
155
+ }
156
+
157
+ /**
158
+ * Returns the root element of the component
159
+ *
160
+ * @protected
161
+ * @returns {ConfigurationType} - the root element of component
162
+ */
163
+ get config() {
164
+ return this._config;
165
+ }
166
+ constructor($root, config) {
167
+ super($root);
168
+ this._config = void 0;
169
+ const childConstructor = this.constructor;
170
+ if (!isObject(childConstructor.defaults)) {
171
+ throw new ConfigError(formatErrorMessage(childConstructor, 'Config passed as parameter into constructor but no defaults defined'));
172
+ }
173
+ const datasetConfig = normaliseDataset(childConstructor, this._$root.dataset);
174
+ this._config = mergeConfigs(childConstructor.defaults, config != null ? config : {}, this[configOverride](datasetConfig), datasetConfig);
175
+ }
176
+ }
177
+ function normaliseString(value, property) {
178
+ const trimmedValue = value ? value.trim() : '';
179
+ let output;
180
+ let outputType = property == null ? void 0 : property.type;
181
+ if (!outputType) {
182
+ if (['true', 'false'].includes(trimmedValue)) {
183
+ outputType = 'boolean';
184
+ }
185
+ if (trimmedValue.length > 0 && isFinite(Number(trimmedValue))) {
186
+ outputType = 'number';
187
+ }
188
+ }
189
+ switch (outputType) {
190
+ case 'boolean':
191
+ output = trimmedValue === 'true';
192
+ break;
193
+ case 'number':
194
+ output = Number(trimmedValue);
195
+ break;
196
+ default:
197
+ output = value;
198
+ }
199
+ return output;
200
+ }
201
+ function normaliseDataset(Component, dataset) {
202
+ if (!isObject(Component.schema)) {
203
+ throw new ConfigError(formatErrorMessage(Component, 'Config passed as parameter into constructor but no schema defined'));
204
+ }
205
+ const out = {};
206
+ const entries = Object.entries(Component.schema.properties);
207
+ for (const entry of entries) {
208
+ const [namespace, property] = entry;
209
+ const field = namespace.toString();
210
+ if (field in dataset) {
211
+ out[field] = normaliseString(dataset[field], property);
212
+ }
213
+ if ((property == null ? void 0 : property.type) === 'object') {
214
+ out[field] = extractConfigByNamespace(Component.schema, dataset, namespace);
215
+ }
216
+ }
217
+ return out;
218
+ }
219
+ function mergeConfigs(...configObjects) {
220
+ const formattedConfigObject = {};
221
+ for (const configObject of configObjects) {
222
+ for (const key of Object.keys(configObject)) {
223
+ const option = formattedConfigObject[key];
224
+ const override = configObject[key];
225
+ if (isObject(option) && isObject(override)) {
226
+ formattedConfigObject[key] = mergeConfigs(option, override);
227
+ } else {
228
+ formattedConfigObject[key] = override;
229
+ }
230
+ }
231
+ }
232
+ return formattedConfigObject;
233
+ }
234
+ function extractConfigByNamespace(schema, dataset, namespace) {
235
+ const property = schema.properties[namespace];
236
+ if ((property == null ? void 0 : property.type) !== 'object') {
237
+ return;
238
+ }
239
+ const newObject = {
240
+ [namespace]: {}
241
+ };
242
+ for (const [key, value] of Object.entries(dataset)) {
243
+ let current = newObject;
244
+ const keyParts = key.split('.');
245
+ for (const [index, name] of keyParts.entries()) {
246
+ if (isObject(current)) {
247
+ if (index < keyParts.length - 1) {
248
+ if (!isObject(current[name])) {
249
+ current[name] = {};
250
+ }
251
+ current = current[name];
252
+ } else if (key !== namespace) {
253
+ current[name] = normaliseString(value);
254
+ }
255
+ }
256
+ }
257
+ }
258
+ return newObject[namespace];
259
+ }
260
+ /**
261
+ * Schema for component config
262
+ *
263
+ * @template {Partial<Record<keyof ConfigurationType, unknown>>} ConfigurationType
264
+ * @typedef {object} Schema
265
+ * @property {Record<keyof ConfigurationType, SchemaProperty | undefined>} properties - Schema properties
266
+ * @property {SchemaCondition<ConfigurationType>[]} [anyOf] - List of schema conditions
267
+ */
268
+ /**
269
+ * Schema property for component config
270
+ *
271
+ * @typedef {object} SchemaProperty
272
+ * @property {'string' | 'boolean' | 'number' | 'object'} type - Property type
273
+ */
274
+ /**
275
+ * Schema condition for component config
276
+ *
277
+ * @template {Partial<Record<keyof ConfigurationType, unknown>>} ConfigurationType
278
+ * @typedef {object} SchemaCondition
279
+ * @property {(keyof ConfigurationType)[]} required - List of required config fields
280
+ * @property {string} errorMessage - Error message when required config fields not provided
281
+ */
282
+ /**
283
+ * @template {Partial<Record<keyof ConfigurationType, unknown>>} [ConfigurationType=ObjectNested]
284
+ * @typedef ChildClass
285
+ * @property {string} moduleName - The module name that'll be looked for in the DOM when initialising the component
286
+ * @property {Schema<ConfigurationType>} [schema] - The schema of the component configuration
287
+ * @property {ConfigurationType} [defaults] - The default values of the configuration of the component
288
+ */
289
+ /**
290
+ * @template {Partial<Record<keyof ConfigurationType, unknown>>} [ConfigurationType=ObjectNested]
291
+ * @typedef {typeof Component & ChildClass<ConfigurationType>} ChildClassConstructor<ConfigurationType>
245
292
  */
246
- GOVUKFrontendComponent.elementType = HTMLElement;
247
293
 
248
294
  class I18n {
249
295
  constructor(translations = {}, config = {}) {
@@ -442,15 +488,15 @@
442
488
  * Exit this page component
443
489
  *
444
490
  * @preserve
491
+ * @augments ConfigurableComponent<ExitThisPageConfig>
445
492
  */
446
- class ExitThisPage extends GOVUKFrontendComponent {
493
+ class ExitThisPage extends ConfigurableComponent {
447
494
  /**
448
495
  * @param {Element | null} $root - HTML element that wraps the Exit This Page button
449
496
  * @param {ExitThisPageConfig} [config] - Exit This Page config
450
497
  */
451
498
  constructor($root, config = {}) {
452
- super($root);
453
- this.config = void 0;
499
+ super($root, config);
454
500
  this.i18n = void 0;
455
501
  this.$button = void 0;
456
502
  this.$skiplinkButton = null;
@@ -471,7 +517,6 @@
471
517
  identifier: 'Button (`.govuk-exit-this-page__button`)'
472
518
  });
473
519
  }
474
- this.config = mergeConfigs(ExitThisPage.defaults, config, normaliseDataset(ExitThisPage, this.$root.dataset));
475
520
  this.i18n = new I18n(this.config.i18n);
476
521
  this.$button = $button;
477
522
  const $skiplinkButton = document.querySelector('.govuk-js-exit-this-page-skiplink');
@@ -637,7 +682,7 @@
637
682
  */
638
683
 
639
684
  /**
640
- * @typedef {import('../../common/index.mjs').Schema} Schema
685
+ * @import { Schema } from '../../common/configuration.mjs'
641
686
  */
642
687
  ExitThisPage.moduleName = 'govuk-exit-this-page';
643
688
  ExitThisPage.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 isInitialised($root, moduleName) {
72
2
  return $root instanceof HTMLElement && $root.hasAttribute(`data-${moduleName}-init`);
73
3
  }
@@ -96,46 +26,13 @@ function isObject(option) {
96
26
  function formatErrorMessage(Component, message) {
97
27
  return `${Component.moduleName}: ${message}`;
98
28
  }
99
-
100
- /**
101
- * Schema for component config
102
- *
103
- * @typedef {object} Schema
104
- * @property {{ [field: string]: SchemaProperty | undefined }} properties - Schema properties
105
- * @property {SchemaCondition[]} [anyOf] - List of schema conditions
106
- */
107
-
108
- /**
109
- * Schema property for component config
110
- *
111
- * @typedef {object} SchemaProperty
112
- * @property {'string' | 'boolean' | 'number' | 'object'} type - Property type
113
- */
114
-
115
- /**
116
- * Schema condition for component config
117
- *
118
- * @typedef {object} SchemaCondition
119
- * @property {string[]} required - List of required config fields
120
- * @property {string} errorMessage - Error message when required config fields not provided
121
- */
122
29
  /**
123
30
  * @typedef ComponentWithModuleName
124
31
  * @property {string} moduleName - Name of the component
125
32
  */
126
-
127
- function normaliseDataset(Component, dataset) {
128
- const out = {};
129
- for (const [field, property] of Object.entries(Component.schema.properties)) {
130
- if (field in dataset) {
131
- out[field] = normaliseString(dataset[field], property);
132
- }
133
- if ((property == null ? void 0 : property.type) === 'object') {
134
- out[field] = extractConfigByNamespace(Component, dataset, field);
135
- }
136
- }
137
- return out;
138
- }
33
+ /**
34
+ * @import { ObjectNested } from './configuration.mjs'
35
+ */
139
36
 
140
37
  class GOVUKFrontendError extends Error {
141
38
  constructor(...args) {
@@ -155,6 +52,12 @@ class SupportError extends GOVUKFrontendError {
155
52
  this.name = 'SupportError';
156
53
  }
157
54
  }
55
+ class ConfigError extends GOVUKFrontendError {
56
+ constructor(...args) {
57
+ super(...args);
58
+ this.name = 'ConfigError';
59
+ }
60
+ }
158
61
  class ElementError extends GOVUKFrontendError {
159
62
  constructor(messageOrOptions) {
160
63
  let message = typeof messageOrOptions === 'string' ? messageOrOptions : '';
@@ -181,10 +84,10 @@ class InitError extends GOVUKFrontendError {
181
84
  }
182
85
  }
183
86
  /**
184
- * @typedef {import('../common/index.mjs').ComponentWithModuleName} ComponentWithModuleName
87
+ * @import { ComponentWithModuleName } from '../common/index.mjs'
185
88
  */
186
89
 
187
- class GOVUKFrontendComponent {
90
+ class Component {
188
91
  /**
189
92
  * Returns the root element of the component
190
93
  *
@@ -235,9 +138,152 @@ class GOVUKFrontendComponent {
235
138
  */
236
139
 
237
140
  /**
238
- * @typedef {typeof GOVUKFrontendComponent & ChildClass} ChildClassConstructor
141
+ * @typedef {typeof Component & ChildClass} ChildClassConstructor
142
+ */
143
+ Component.elementType = HTMLElement;
144
+
145
+ const configOverride = Symbol.for('configOverride');
146
+ class ConfigurableComponent extends Component {
147
+ [configOverride](param) {
148
+ return {};
149
+ }
150
+
151
+ /**
152
+ * Returns the root element of the component
153
+ *
154
+ * @protected
155
+ * @returns {ConfigurationType} - the root element of component
156
+ */
157
+ get config() {
158
+ return this._config;
159
+ }
160
+ constructor($root, config) {
161
+ super($root);
162
+ this._config = void 0;
163
+ const childConstructor = this.constructor;
164
+ if (!isObject(childConstructor.defaults)) {
165
+ throw new ConfigError(formatErrorMessage(childConstructor, 'Config passed as parameter into constructor but no defaults defined'));
166
+ }
167
+ const datasetConfig = normaliseDataset(childConstructor, this._$root.dataset);
168
+ this._config = mergeConfigs(childConstructor.defaults, config != null ? config : {}, this[configOverride](datasetConfig), datasetConfig);
169
+ }
170
+ }
171
+ function normaliseString(value, property) {
172
+ const trimmedValue = value ? value.trim() : '';
173
+ let output;
174
+ let outputType = property == null ? void 0 : property.type;
175
+ if (!outputType) {
176
+ if (['true', 'false'].includes(trimmedValue)) {
177
+ outputType = 'boolean';
178
+ }
179
+ if (trimmedValue.length > 0 && isFinite(Number(trimmedValue))) {
180
+ outputType = 'number';
181
+ }
182
+ }
183
+ switch (outputType) {
184
+ case 'boolean':
185
+ output = trimmedValue === 'true';
186
+ break;
187
+ case 'number':
188
+ output = Number(trimmedValue);
189
+ break;
190
+ default:
191
+ output = value;
192
+ }
193
+ return output;
194
+ }
195
+ function normaliseDataset(Component, dataset) {
196
+ if (!isObject(Component.schema)) {
197
+ throw new ConfigError(formatErrorMessage(Component, 'Config passed as parameter into constructor but no schema defined'));
198
+ }
199
+ const out = {};
200
+ const entries = Object.entries(Component.schema.properties);
201
+ for (const entry of entries) {
202
+ const [namespace, property] = entry;
203
+ const field = namespace.toString();
204
+ if (field in dataset) {
205
+ out[field] = normaliseString(dataset[field], property);
206
+ }
207
+ if ((property == null ? void 0 : property.type) === 'object') {
208
+ out[field] = extractConfigByNamespace(Component.schema, dataset, namespace);
209
+ }
210
+ }
211
+ return out;
212
+ }
213
+ function mergeConfigs(...configObjects) {
214
+ const formattedConfigObject = {};
215
+ for (const configObject of configObjects) {
216
+ for (const key of Object.keys(configObject)) {
217
+ const option = formattedConfigObject[key];
218
+ const override = configObject[key];
219
+ if (isObject(option) && isObject(override)) {
220
+ formattedConfigObject[key] = mergeConfigs(option, override);
221
+ } else {
222
+ formattedConfigObject[key] = override;
223
+ }
224
+ }
225
+ }
226
+ return formattedConfigObject;
227
+ }
228
+ function extractConfigByNamespace(schema, dataset, namespace) {
229
+ const property = schema.properties[namespace];
230
+ if ((property == null ? void 0 : property.type) !== 'object') {
231
+ return;
232
+ }
233
+ const newObject = {
234
+ [namespace]: {}
235
+ };
236
+ for (const [key, value] of Object.entries(dataset)) {
237
+ let current = newObject;
238
+ const keyParts = key.split('.');
239
+ for (const [index, name] of keyParts.entries()) {
240
+ if (isObject(current)) {
241
+ if (index < keyParts.length - 1) {
242
+ if (!isObject(current[name])) {
243
+ current[name] = {};
244
+ }
245
+ current = current[name];
246
+ } else if (key !== namespace) {
247
+ current[name] = normaliseString(value);
248
+ }
249
+ }
250
+ }
251
+ }
252
+ return newObject[namespace];
253
+ }
254
+ /**
255
+ * Schema for component config
256
+ *
257
+ * @template {Partial<Record<keyof ConfigurationType, unknown>>} ConfigurationType
258
+ * @typedef {object} Schema
259
+ * @property {Record<keyof ConfigurationType, SchemaProperty | undefined>} properties - Schema properties
260
+ * @property {SchemaCondition<ConfigurationType>[]} [anyOf] - List of schema conditions
261
+ */
262
+ /**
263
+ * Schema property for component config
264
+ *
265
+ * @typedef {object} SchemaProperty
266
+ * @property {'string' | 'boolean' | 'number' | 'object'} type - Property type
267
+ */
268
+ /**
269
+ * Schema condition for component config
270
+ *
271
+ * @template {Partial<Record<keyof ConfigurationType, unknown>>} ConfigurationType
272
+ * @typedef {object} SchemaCondition
273
+ * @property {(keyof ConfigurationType)[]} required - List of required config fields
274
+ * @property {string} errorMessage - Error message when required config fields not provided
275
+ */
276
+ /**
277
+ * @template {Partial<Record<keyof ConfigurationType, unknown>>} [ConfigurationType=ObjectNested]
278
+ * @typedef ChildClass
279
+ * @property {string} moduleName - The module name that'll be looked for in the DOM when initialising the component
280
+ * @property {Schema<ConfigurationType>} [schema] - The schema of the component configuration
281
+ * @property {ConfigurationType} [defaults] - The default values of the configuration of the component
282
+ */
283
+ /**
284
+ * @template {Partial<Record<keyof ConfigurationType, unknown>>} [ConfigurationType=ObjectNested]
285
+ * @typedef {typeof Component & ChildClass<ConfigurationType>} ChildClassConstructor<ConfigurationType>
239
286
  */
240
- GOVUKFrontendComponent.elementType = HTMLElement;
241
287
 
242
288
  class I18n {
243
289
  constructor(translations = {}, config = {}) {
@@ -436,15 +482,15 @@ I18n.pluralRules = {
436
482
  * Exit this page component
437
483
  *
438
484
  * @preserve
485
+ * @augments ConfigurableComponent<ExitThisPageConfig>
439
486
  */
440
- class ExitThisPage extends GOVUKFrontendComponent {
487
+ class ExitThisPage extends ConfigurableComponent {
441
488
  /**
442
489
  * @param {Element | null} $root - HTML element that wraps the Exit This Page button
443
490
  * @param {ExitThisPageConfig} [config] - Exit This Page config
444
491
  */
445
492
  constructor($root, config = {}) {
446
- super($root);
447
- this.config = void 0;
493
+ super($root, config);
448
494
  this.i18n = void 0;
449
495
  this.$button = void 0;
450
496
  this.$skiplinkButton = null;
@@ -465,7 +511,6 @@ class ExitThisPage extends GOVUKFrontendComponent {
465
511
  identifier: 'Button (`.govuk-exit-this-page__button`)'
466
512
  });
467
513
  }
468
- this.config = mergeConfigs(ExitThisPage.defaults, config, normaliseDataset(ExitThisPage, this.$root.dataset));
469
514
  this.i18n = new I18n(this.config.i18n);
470
515
  this.$button = $button;
471
516
  const $skiplinkButton = document.querySelector('.govuk-js-exit-this-page-skiplink');
@@ -631,7 +676,7 @@ class ExitThisPage extends GOVUKFrontendComponent {
631
676
  */
632
677
 
633
678
  /**
634
- * @typedef {import('../../common/index.mjs').Schema} Schema
679
+ * @import { Schema } from '../../common/configuration.mjs'
635
680
  */
636
681
  ExitThisPage.moduleName = 'govuk-exit-this-page';
637
682
  ExitThisPage.defaults = Object.freeze({