govuk_tech_docs 4.1.2 → 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 (74) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +18 -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/meta_tags.rb +5 -1
  6. data/lib/govuk_tech_docs/version.rb +1 -1
  7. data/lib/source/layouts/core.erb +21 -5
  8. data/node_modules/govuk-frontend/dist/govuk/all.bundle.js +508 -209
  9. data/node_modules/govuk-frontend/dist/govuk/all.bundle.mjs +505 -208
  10. data/node_modules/govuk-frontend/dist/govuk/all.mjs +3 -1
  11. data/node_modules/govuk-frontend/dist/govuk/all.scss +6 -0
  12. data/node_modules/govuk-frontend/dist/govuk/common/configuration.mjs +169 -0
  13. data/node_modules/govuk-frontend/dist/govuk/common/govuk-frontend-version.mjs +1 -1
  14. data/node_modules/govuk-frontend/dist/govuk/common/index.mjs +4 -87
  15. data/node_modules/govuk-frontend/dist/govuk/{govuk-frontend-component.mjs → component.mjs} +5 -5
  16. data/node_modules/govuk-frontend/dist/govuk/components/accordion/accordion.bundle.js +161 -116
  17. data/node_modules/govuk-frontend/dist/govuk/components/accordion/accordion.bundle.mjs +160 -115
  18. data/node_modules/govuk-frontend/dist/govuk/components/accordion/accordion.mjs +5 -8
  19. data/node_modules/govuk-frontend/dist/govuk/components/button/button.bundle.js +161 -116
  20. data/node_modules/govuk-frontend/dist/govuk/components/button/button.bundle.mjs +160 -115
  21. data/node_modules/govuk-frontend/dist/govuk/components/button/button.mjs +5 -8
  22. data/node_modules/govuk-frontend/dist/govuk/components/character-count/_index.scss +8 -0
  23. data/node_modules/govuk-frontend/dist/govuk/components/character-count/character-count.bundle.js +187 -145
  24. data/node_modules/govuk-frontend/dist/govuk/components/character-count/character-count.bundle.mjs +186 -144
  25. data/node_modules/govuk-frontend/dist/govuk/components/character-count/character-count.mjs +18 -17
  26. data/node_modules/govuk-frontend/dist/govuk/components/checkboxes/checkboxes.bundle.js +9 -29
  27. data/node_modules/govuk-frontend/dist/govuk/components/checkboxes/checkboxes.bundle.mjs +8 -28
  28. data/node_modules/govuk-frontend/dist/govuk/components/checkboxes/checkboxes.mjs +2 -2
  29. data/node_modules/govuk-frontend/dist/govuk/components/error-summary/error-summary.bundle.js +161 -116
  30. data/node_modules/govuk-frontend/dist/govuk/components/error-summary/error-summary.bundle.mjs +160 -115
  31. data/node_modules/govuk-frontend/dist/govuk/components/error-summary/error-summary.mjs +6 -8
  32. data/node_modules/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.bundle.js +161 -116
  33. data/node_modules/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.bundle.mjs +160 -115
  34. data/node_modules/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.mjs +5 -8
  35. data/node_modules/govuk-frontend/dist/govuk/components/file-upload/_index.scss +167 -0
  36. data/node_modules/govuk-frontend/dist/govuk/components/file-upload/file-upload.bundle.js +754 -0
  37. data/node_modules/govuk-frontend/dist/govuk/components/file-upload/file-upload.bundle.mjs +746 -0
  38. data/node_modules/govuk-frontend/dist/govuk/components/file-upload/file-upload.mjs +267 -0
  39. data/node_modules/govuk-frontend/dist/govuk/components/header/_index.scss +14 -10
  40. data/node_modules/govuk-frontend/dist/govuk/components/header/header.bundle.js +9 -29
  41. data/node_modules/govuk-frontend/dist/govuk/components/header/header.bundle.mjs +8 -28
  42. data/node_modules/govuk-frontend/dist/govuk/components/header/header.mjs +2 -2
  43. data/node_modules/govuk-frontend/dist/govuk/components/notification-banner/notification-banner.bundle.js +161 -116
  44. data/node_modules/govuk-frontend/dist/govuk/components/notification-banner/notification-banner.bundle.mjs +160 -115
  45. data/node_modules/govuk-frontend/dist/govuk/components/notification-banner/notification-banner.mjs +6 -8
  46. data/node_modules/govuk-frontend/dist/govuk/components/password-input/password-input.bundle.js +161 -117
  47. data/node_modules/govuk-frontend/dist/govuk/components/password-input/password-input.bundle.mjs +160 -116
  48. data/node_modules/govuk-frontend/dist/govuk/components/password-input/password-input.mjs +5 -9
  49. data/node_modules/govuk-frontend/dist/govuk/components/radios/radios.bundle.js +9 -29
  50. data/node_modules/govuk-frontend/dist/govuk/components/radios/radios.bundle.mjs +8 -28
  51. data/node_modules/govuk-frontend/dist/govuk/components/radios/radios.mjs +2 -2
  52. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/service-navigation.bundle.js +9 -29
  53. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/service-navigation.bundle.mjs +8 -28
  54. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/service-navigation.mjs +2 -2
  55. data/node_modules/govuk-frontend/dist/govuk/components/skip-link/skip-link.bundle.js +10 -30
  56. data/node_modules/govuk-frontend/dist/govuk/components/skip-link/skip-link.bundle.mjs +9 -29
  57. data/node_modules/govuk-frontend/dist/govuk/components/skip-link/skip-link.mjs +3 -3
  58. data/node_modules/govuk-frontend/dist/govuk/components/summary-list/_index.scss +12 -21
  59. data/node_modules/govuk-frontend/dist/govuk/components/tabs/tabs.bundle.js +9 -29
  60. data/node_modules/govuk-frontend/dist/govuk/components/tabs/tabs.bundle.mjs +8 -28
  61. data/node_modules/govuk-frontend/dist/govuk/components/tabs/tabs.mjs +2 -2
  62. data/node_modules/govuk-frontend/dist/govuk/core/_govuk-frontend-properties.scss +1 -1
  63. data/node_modules/govuk-frontend/dist/govuk/errors/index.mjs +1 -1
  64. data/node_modules/govuk-frontend/dist/govuk/govuk-frontend.min.js +1 -1
  65. data/node_modules/govuk-frontend/dist/govuk/helpers/_colour.scss +2 -2
  66. data/node_modules/govuk-frontend/dist/govuk/init.mjs +28 -24
  67. data/node_modules/govuk-frontend/dist/govuk/settings/_colours-organisations.scss +18 -5
  68. data/node_modules/govuk-frontend/dist/govuk/settings/_typography-responsive.scss +5 -10
  69. data/node_modules/govuk-frontend/dist/govuk-prototype-kit/init.scss +1 -1
  70. data/package-lock.json +8 -7
  71. data/package.json +1 -1
  72. metadata +12 -10
  73. data/node_modules/govuk-frontend/dist/govuk/common/normalise-dataset.mjs +0 -18
  74. data/node_modules/govuk-frontend/dist/govuk/common/normalise-string.mjs +0 -31
@@ -1,7 +1,7 @@
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
7
  function closestAttributeValue($element, attributeName) {
@@ -9,76 +9,6 @@
9
9
  return $closestElementWithAttribute ? $closestElementWithAttribute.getAttribute(attributeName) : null;
10
10
  }
11
11
 
12
- function normaliseString(value, property) {
13
- const trimmedValue = value ? value.trim() : '';
14
- let output;
15
- let outputType = property == null ? void 0 : property.type;
16
- if (!outputType) {
17
- if (['true', 'false'].includes(trimmedValue)) {
18
- outputType = 'boolean';
19
- }
20
- if (trimmedValue.length > 0 && isFinite(Number(trimmedValue))) {
21
- outputType = 'number';
22
- }
23
- }
24
- switch (outputType) {
25
- case 'boolean':
26
- output = trimmedValue === 'true';
27
- break;
28
- case 'number':
29
- output = Number(trimmedValue);
30
- break;
31
- default:
32
- output = value;
33
- }
34
- return output;
35
- }
36
-
37
- /**
38
- * @typedef {import('./index.mjs').SchemaProperty} SchemaProperty
39
- */
40
-
41
- function mergeConfigs(...configObjects) {
42
- const formattedConfigObject = {};
43
- for (const configObject of configObjects) {
44
- for (const key of Object.keys(configObject)) {
45
- const option = formattedConfigObject[key];
46
- const override = configObject[key];
47
- if (isObject(option) && isObject(override)) {
48
- formattedConfigObject[key] = mergeConfigs(option, override);
49
- } else {
50
- formattedConfigObject[key] = override;
51
- }
52
- }
53
- }
54
- return formattedConfigObject;
55
- }
56
- function extractConfigByNamespace(Component, dataset, namespace) {
57
- const property = Component.schema.properties[namespace];
58
- if ((property == null ? void 0 : property.type) !== 'object') {
59
- return;
60
- }
61
- const newObject = {
62
- [namespace]: ({})
63
- };
64
- for (const [key, value] of Object.entries(dataset)) {
65
- let current = newObject;
66
- const keyParts = key.split('.');
67
- for (const [index, name] of keyParts.entries()) {
68
- if (typeof current === 'object') {
69
- if (index < keyParts.length - 1) {
70
- if (!isObject(current[name])) {
71
- current[name] = {};
72
- }
73
- current = current[name];
74
- } else if (key !== namespace) {
75
- current[name] = normaliseString(value);
76
- }
77
- }
78
- }
79
- }
80
- return newObject[namespace];
81
- }
82
12
  function isInitialised($root, moduleName) {
83
13
  return $root instanceof HTMLElement && $root.hasAttribute(`data-${moduleName}-init`);
84
14
  }
@@ -107,46 +37,13 @@
107
37
  function formatErrorMessage(Component, message) {
108
38
  return `${Component.moduleName}: ${message}`;
109
39
  }
110
-
111
- /**
112
- * Schema for component config
113
- *
114
- * @typedef {object} Schema
115
- * @property {{ [field: string]: SchemaProperty | undefined }} properties - Schema properties
116
- * @property {SchemaCondition[]} [anyOf] - List of schema conditions
117
- */
118
-
119
- /**
120
- * Schema property for component config
121
- *
122
- * @typedef {object} SchemaProperty
123
- * @property {'string' | 'boolean' | 'number' | 'object'} type - Property type
124
- */
125
-
126
- /**
127
- * Schema condition for component config
128
- *
129
- * @typedef {object} SchemaCondition
130
- * @property {string[]} required - List of required config fields
131
- * @property {string} errorMessage - Error message when required config fields not provided
132
- */
133
40
  /**
134
41
  * @typedef ComponentWithModuleName
135
42
  * @property {string} moduleName - Name of the component
136
43
  */
137
-
138
- function normaliseDataset(Component, dataset) {
139
- const out = {};
140
- for (const [field, property] of Object.entries(Component.schema.properties)) {
141
- if (field in dataset) {
142
- out[field] = normaliseString(dataset[field], property);
143
- }
144
- if ((property == null ? void 0 : property.type) === 'object') {
145
- out[field] = extractConfigByNamespace(Component, dataset, field);
146
- }
147
- }
148
- return out;
149
- }
44
+ /**
45
+ * @import { ObjectNested } from './configuration.mjs'
46
+ */
150
47
 
151
48
  class GOVUKFrontendError extends Error {
152
49
  constructor(...args) {
@@ -166,6 +63,12 @@
166
63
  this.name = 'SupportError';
167
64
  }
168
65
  }
66
+ class ConfigError extends GOVUKFrontendError {
67
+ constructor(...args) {
68
+ super(...args);
69
+ this.name = 'ConfigError';
70
+ }
71
+ }
169
72
  class ElementError extends GOVUKFrontendError {
170
73
  constructor(messageOrOptions) {
171
74
  let message = typeof messageOrOptions === 'string' ? messageOrOptions : '';
@@ -192,10 +95,10 @@
192
95
  }
193
96
  }
194
97
  /**
195
- * @typedef {import('../common/index.mjs').ComponentWithModuleName} ComponentWithModuleName
98
+ * @import { ComponentWithModuleName } from '../common/index.mjs'
196
99
  */
197
100
 
198
- class GOVUKFrontendComponent {
101
+ class Component {
199
102
  /**
200
103
  * Returns the root element of the component
201
104
  *
@@ -246,9 +149,152 @@
246
149
  */
247
150
 
248
151
  /**
249
- * @typedef {typeof GOVUKFrontendComponent & ChildClass} ChildClassConstructor
152
+ * @typedef {typeof Component & ChildClass} ChildClassConstructor
153
+ */
154
+ Component.elementType = HTMLElement;
155
+
156
+ const configOverride = Symbol.for('configOverride');
157
+ class ConfigurableComponent extends Component {
158
+ [configOverride](param) {
159
+ return {};
160
+ }
161
+
162
+ /**
163
+ * Returns the root element of the component
164
+ *
165
+ * @protected
166
+ * @returns {ConfigurationType} - the root element of component
167
+ */
168
+ get config() {
169
+ return this._config;
170
+ }
171
+ constructor($root, config) {
172
+ super($root);
173
+ this._config = void 0;
174
+ const childConstructor = this.constructor;
175
+ if (!isObject(childConstructor.defaults)) {
176
+ throw new ConfigError(formatErrorMessage(childConstructor, 'Config passed as parameter into constructor but no defaults defined'));
177
+ }
178
+ const datasetConfig = normaliseDataset(childConstructor, this._$root.dataset);
179
+ this._config = mergeConfigs(childConstructor.defaults, config != null ? config : {}, this[configOverride](datasetConfig), datasetConfig);
180
+ }
181
+ }
182
+ function normaliseString(value, property) {
183
+ const trimmedValue = value ? value.trim() : '';
184
+ let output;
185
+ let outputType = property == null ? void 0 : property.type;
186
+ if (!outputType) {
187
+ if (['true', 'false'].includes(trimmedValue)) {
188
+ outputType = 'boolean';
189
+ }
190
+ if (trimmedValue.length > 0 && isFinite(Number(trimmedValue))) {
191
+ outputType = 'number';
192
+ }
193
+ }
194
+ switch (outputType) {
195
+ case 'boolean':
196
+ output = trimmedValue === 'true';
197
+ break;
198
+ case 'number':
199
+ output = Number(trimmedValue);
200
+ break;
201
+ default:
202
+ output = value;
203
+ }
204
+ return output;
205
+ }
206
+ function normaliseDataset(Component, dataset) {
207
+ if (!isObject(Component.schema)) {
208
+ throw new ConfigError(formatErrorMessage(Component, 'Config passed as parameter into constructor but no schema defined'));
209
+ }
210
+ const out = {};
211
+ const entries = Object.entries(Component.schema.properties);
212
+ for (const entry of entries) {
213
+ const [namespace, property] = entry;
214
+ const field = namespace.toString();
215
+ if (field in dataset) {
216
+ out[field] = normaliseString(dataset[field], property);
217
+ }
218
+ if ((property == null ? void 0 : property.type) === 'object') {
219
+ out[field] = extractConfigByNamespace(Component.schema, dataset, namespace);
220
+ }
221
+ }
222
+ return out;
223
+ }
224
+ function mergeConfigs(...configObjects) {
225
+ const formattedConfigObject = {};
226
+ for (const configObject of configObjects) {
227
+ for (const key of Object.keys(configObject)) {
228
+ const option = formattedConfigObject[key];
229
+ const override = configObject[key];
230
+ if (isObject(option) && isObject(override)) {
231
+ formattedConfigObject[key] = mergeConfigs(option, override);
232
+ } else {
233
+ formattedConfigObject[key] = override;
234
+ }
235
+ }
236
+ }
237
+ return formattedConfigObject;
238
+ }
239
+ function extractConfigByNamespace(schema, dataset, namespace) {
240
+ const property = schema.properties[namespace];
241
+ if ((property == null ? void 0 : property.type) !== 'object') {
242
+ return;
243
+ }
244
+ const newObject = {
245
+ [namespace]: {}
246
+ };
247
+ for (const [key, value] of Object.entries(dataset)) {
248
+ let current = newObject;
249
+ const keyParts = key.split('.');
250
+ for (const [index, name] of keyParts.entries()) {
251
+ if (isObject(current)) {
252
+ if (index < keyParts.length - 1) {
253
+ if (!isObject(current[name])) {
254
+ current[name] = {};
255
+ }
256
+ current = current[name];
257
+ } else if (key !== namespace) {
258
+ current[name] = normaliseString(value);
259
+ }
260
+ }
261
+ }
262
+ }
263
+ return newObject[namespace];
264
+ }
265
+ /**
266
+ * Schema for component config
267
+ *
268
+ * @template {Partial<Record<keyof ConfigurationType, unknown>>} ConfigurationType
269
+ * @typedef {object} Schema
270
+ * @property {Record<keyof ConfigurationType, SchemaProperty | undefined>} properties - Schema properties
271
+ * @property {SchemaCondition<ConfigurationType>[]} [anyOf] - List of schema conditions
272
+ */
273
+ /**
274
+ * Schema property for component config
275
+ *
276
+ * @typedef {object} SchemaProperty
277
+ * @property {'string' | 'boolean' | 'number' | 'object'} type - Property type
278
+ */
279
+ /**
280
+ * Schema condition for component config
281
+ *
282
+ * @template {Partial<Record<keyof ConfigurationType, unknown>>} ConfigurationType
283
+ * @typedef {object} SchemaCondition
284
+ * @property {(keyof ConfigurationType)[]} required - List of required config fields
285
+ * @property {string} errorMessage - Error message when required config fields not provided
286
+ */
287
+ /**
288
+ * @template {Partial<Record<keyof ConfigurationType, unknown>>} [ConfigurationType=ObjectNested]
289
+ * @typedef ChildClass
290
+ * @property {string} moduleName - The module name that'll be looked for in the DOM when initialising the component
291
+ * @property {Schema<ConfigurationType>} [schema] - The schema of the component configuration
292
+ * @property {ConfigurationType} [defaults] - The default values of the configuration of the component
293
+ */
294
+ /**
295
+ * @template {Partial<Record<keyof ConfigurationType, unknown>>} [ConfigurationType=ObjectNested]
296
+ * @typedef {typeof Component & ChildClass<ConfigurationType>} ChildClassConstructor<ConfigurationType>
250
297
  */
251
- GOVUKFrontendComponent.elementType = HTMLElement;
252
298
 
253
299
  class I18n {
254
300
  constructor(translations = {}, config = {}) {
@@ -447,15 +493,15 @@
447
493
  * Password input component
448
494
  *
449
495
  * @preserve
496
+ * @augments ConfigurableComponent<PasswordInputConfig>
450
497
  */
451
- class PasswordInput extends GOVUKFrontendComponent {
498
+ class PasswordInput extends ConfigurableComponent {
452
499
  /**
453
500
  * @param {Element | null} $root - HTML element to use for password input
454
501
  * @param {PasswordInputConfig} [config] - Password input config
455
502
  */
456
503
  constructor($root, config = {}) {
457
- super($root);
458
- this.config = void 0;
504
+ super($root, config);
459
505
  this.i18n = void 0;
460
506
  this.$input = void 0;
461
507
  this.$showHideButton = void 0;
@@ -486,7 +532,6 @@
486
532
  }
487
533
  this.$input = $input;
488
534
  this.$showHideButton = $showHideButton;
489
- this.config = mergeConfigs(PasswordInput.defaults, config, normaliseDataset(PasswordInput, this.$root.dataset));
490
535
  this.i18n = new I18n(this.config.i18n, {
491
536
  locale: closestAttributeValue(this.$root, 'lang')
492
537
  });
@@ -566,8 +611,7 @@
566
611
  */
567
612
 
568
613
  /**
569
- * @typedef {import('../../common/index.mjs').Schema} Schema
570
- * @typedef {import('../../i18n.mjs').TranslationPluralForms} TranslationPluralForms
614
+ * @import { Schema } from '../../common/configuration.mjs'
571
615
  */
572
616
  PasswordInput.moduleName = 'govuk-password-input';
573
617
  PasswordInput.defaults = Object.freeze({
@@ -3,76 +3,6 @@ function closestAttributeValue($element, attributeName) {
3
3
  return $closestElementWithAttribute ? $closestElementWithAttribute.getAttribute(attributeName) : null;
4
4
  }
5
5
 
6
- function normaliseString(value, property) {
7
- const trimmedValue = value ? value.trim() : '';
8
- let output;
9
- let outputType = property == null ? void 0 : property.type;
10
- if (!outputType) {
11
- if (['true', 'false'].includes(trimmedValue)) {
12
- outputType = 'boolean';
13
- }
14
- if (trimmedValue.length > 0 && isFinite(Number(trimmedValue))) {
15
- outputType = 'number';
16
- }
17
- }
18
- switch (outputType) {
19
- case 'boolean':
20
- output = trimmedValue === 'true';
21
- break;
22
- case 'number':
23
- output = Number(trimmedValue);
24
- break;
25
- default:
26
- output = value;
27
- }
28
- return output;
29
- }
30
-
31
- /**
32
- * @typedef {import('./index.mjs').SchemaProperty} SchemaProperty
33
- */
34
-
35
- function mergeConfigs(...configObjects) {
36
- const formattedConfigObject = {};
37
- for (const configObject of configObjects) {
38
- for (const key of Object.keys(configObject)) {
39
- const option = formattedConfigObject[key];
40
- const override = configObject[key];
41
- if (isObject(option) && isObject(override)) {
42
- formattedConfigObject[key] = mergeConfigs(option, override);
43
- } else {
44
- formattedConfigObject[key] = override;
45
- }
46
- }
47
- }
48
- return formattedConfigObject;
49
- }
50
- function extractConfigByNamespace(Component, dataset, namespace) {
51
- const property = Component.schema.properties[namespace];
52
- if ((property == null ? void 0 : property.type) !== 'object') {
53
- return;
54
- }
55
- const newObject = {
56
- [namespace]: ({})
57
- };
58
- for (const [key, value] of Object.entries(dataset)) {
59
- let current = newObject;
60
- const keyParts = key.split('.');
61
- for (const [index, name] of keyParts.entries()) {
62
- if (typeof current === 'object') {
63
- if (index < keyParts.length - 1) {
64
- if (!isObject(current[name])) {
65
- current[name] = {};
66
- }
67
- current = current[name];
68
- } else if (key !== namespace) {
69
- current[name] = normaliseString(value);
70
- }
71
- }
72
- }
73
- }
74
- return newObject[namespace];
75
- }
76
6
  function isInitialised($root, moduleName) {
77
7
  return $root instanceof HTMLElement && $root.hasAttribute(`data-${moduleName}-init`);
78
8
  }
@@ -101,46 +31,13 @@ function isObject(option) {
101
31
  function formatErrorMessage(Component, message) {
102
32
  return `${Component.moduleName}: ${message}`;
103
33
  }
104
-
105
- /**
106
- * Schema for component config
107
- *
108
- * @typedef {object} Schema
109
- * @property {{ [field: string]: SchemaProperty | undefined }} properties - Schema properties
110
- * @property {SchemaCondition[]} [anyOf] - List of schema conditions
111
- */
112
-
113
- /**
114
- * Schema property for component config
115
- *
116
- * @typedef {object} SchemaProperty
117
- * @property {'string' | 'boolean' | 'number' | 'object'} type - Property type
118
- */
119
-
120
- /**
121
- * Schema condition for component config
122
- *
123
- * @typedef {object} SchemaCondition
124
- * @property {string[]} required - List of required config fields
125
- * @property {string} errorMessage - Error message when required config fields not provided
126
- */
127
34
  /**
128
35
  * @typedef ComponentWithModuleName
129
36
  * @property {string} moduleName - Name of the component
130
37
  */
131
-
132
- function normaliseDataset(Component, dataset) {
133
- const out = {};
134
- for (const [field, property] of Object.entries(Component.schema.properties)) {
135
- if (field in dataset) {
136
- out[field] = normaliseString(dataset[field], property);
137
- }
138
- if ((property == null ? void 0 : property.type) === 'object') {
139
- out[field] = extractConfigByNamespace(Component, dataset, field);
140
- }
141
- }
142
- return out;
143
- }
38
+ /**
39
+ * @import { ObjectNested } from './configuration.mjs'
40
+ */
144
41
 
145
42
  class GOVUKFrontendError extends Error {
146
43
  constructor(...args) {
@@ -160,6 +57,12 @@ class SupportError extends GOVUKFrontendError {
160
57
  this.name = 'SupportError';
161
58
  }
162
59
  }
60
+ class ConfigError extends GOVUKFrontendError {
61
+ constructor(...args) {
62
+ super(...args);
63
+ this.name = 'ConfigError';
64
+ }
65
+ }
163
66
  class ElementError extends GOVUKFrontendError {
164
67
  constructor(messageOrOptions) {
165
68
  let message = typeof messageOrOptions === 'string' ? messageOrOptions : '';
@@ -186,10 +89,10 @@ class InitError extends GOVUKFrontendError {
186
89
  }
187
90
  }
188
91
  /**
189
- * @typedef {import('../common/index.mjs').ComponentWithModuleName} ComponentWithModuleName
92
+ * @import { ComponentWithModuleName } from '../common/index.mjs'
190
93
  */
191
94
 
192
- class GOVUKFrontendComponent {
95
+ class Component {
193
96
  /**
194
97
  * Returns the root element of the component
195
98
  *
@@ -240,9 +143,152 @@ class GOVUKFrontendComponent {
240
143
  */
241
144
 
242
145
  /**
243
- * @typedef {typeof GOVUKFrontendComponent & ChildClass} ChildClassConstructor
146
+ * @typedef {typeof Component & ChildClass} ChildClassConstructor
147
+ */
148
+ Component.elementType = HTMLElement;
149
+
150
+ const configOverride = Symbol.for('configOverride');
151
+ class ConfigurableComponent extends Component {
152
+ [configOverride](param) {
153
+ return {};
154
+ }
155
+
156
+ /**
157
+ * Returns the root element of the component
158
+ *
159
+ * @protected
160
+ * @returns {ConfigurationType} - the root element of component
161
+ */
162
+ get config() {
163
+ return this._config;
164
+ }
165
+ constructor($root, config) {
166
+ super($root);
167
+ this._config = void 0;
168
+ const childConstructor = this.constructor;
169
+ if (!isObject(childConstructor.defaults)) {
170
+ throw new ConfigError(formatErrorMessage(childConstructor, 'Config passed as parameter into constructor but no defaults defined'));
171
+ }
172
+ const datasetConfig = normaliseDataset(childConstructor, this._$root.dataset);
173
+ this._config = mergeConfigs(childConstructor.defaults, config != null ? config : {}, this[configOverride](datasetConfig), datasetConfig);
174
+ }
175
+ }
176
+ function normaliseString(value, property) {
177
+ const trimmedValue = value ? value.trim() : '';
178
+ let output;
179
+ let outputType = property == null ? void 0 : property.type;
180
+ if (!outputType) {
181
+ if (['true', 'false'].includes(trimmedValue)) {
182
+ outputType = 'boolean';
183
+ }
184
+ if (trimmedValue.length > 0 && isFinite(Number(trimmedValue))) {
185
+ outputType = 'number';
186
+ }
187
+ }
188
+ switch (outputType) {
189
+ case 'boolean':
190
+ output = trimmedValue === 'true';
191
+ break;
192
+ case 'number':
193
+ output = Number(trimmedValue);
194
+ break;
195
+ default:
196
+ output = value;
197
+ }
198
+ return output;
199
+ }
200
+ function normaliseDataset(Component, dataset) {
201
+ if (!isObject(Component.schema)) {
202
+ throw new ConfigError(formatErrorMessage(Component, 'Config passed as parameter into constructor but no schema defined'));
203
+ }
204
+ const out = {};
205
+ const entries = Object.entries(Component.schema.properties);
206
+ for (const entry of entries) {
207
+ const [namespace, property] = entry;
208
+ const field = namespace.toString();
209
+ if (field in dataset) {
210
+ out[field] = normaliseString(dataset[field], property);
211
+ }
212
+ if ((property == null ? void 0 : property.type) === 'object') {
213
+ out[field] = extractConfigByNamespace(Component.schema, dataset, namespace);
214
+ }
215
+ }
216
+ return out;
217
+ }
218
+ function mergeConfigs(...configObjects) {
219
+ const formattedConfigObject = {};
220
+ for (const configObject of configObjects) {
221
+ for (const key of Object.keys(configObject)) {
222
+ const option = formattedConfigObject[key];
223
+ const override = configObject[key];
224
+ if (isObject(option) && isObject(override)) {
225
+ formattedConfigObject[key] = mergeConfigs(option, override);
226
+ } else {
227
+ formattedConfigObject[key] = override;
228
+ }
229
+ }
230
+ }
231
+ return formattedConfigObject;
232
+ }
233
+ function extractConfigByNamespace(schema, dataset, namespace) {
234
+ const property = schema.properties[namespace];
235
+ if ((property == null ? void 0 : property.type) !== 'object') {
236
+ return;
237
+ }
238
+ const newObject = {
239
+ [namespace]: {}
240
+ };
241
+ for (const [key, value] of Object.entries(dataset)) {
242
+ let current = newObject;
243
+ const keyParts = key.split('.');
244
+ for (const [index, name] of keyParts.entries()) {
245
+ if (isObject(current)) {
246
+ if (index < keyParts.length - 1) {
247
+ if (!isObject(current[name])) {
248
+ current[name] = {};
249
+ }
250
+ current = current[name];
251
+ } else if (key !== namespace) {
252
+ current[name] = normaliseString(value);
253
+ }
254
+ }
255
+ }
256
+ }
257
+ return newObject[namespace];
258
+ }
259
+ /**
260
+ * Schema for component config
261
+ *
262
+ * @template {Partial<Record<keyof ConfigurationType, unknown>>} ConfigurationType
263
+ * @typedef {object} Schema
264
+ * @property {Record<keyof ConfigurationType, SchemaProperty | undefined>} properties - Schema properties
265
+ * @property {SchemaCondition<ConfigurationType>[]} [anyOf] - List of schema conditions
266
+ */
267
+ /**
268
+ * Schema property for component config
269
+ *
270
+ * @typedef {object} SchemaProperty
271
+ * @property {'string' | 'boolean' | 'number' | 'object'} type - Property type
272
+ */
273
+ /**
274
+ * Schema condition for component config
275
+ *
276
+ * @template {Partial<Record<keyof ConfigurationType, unknown>>} ConfigurationType
277
+ * @typedef {object} SchemaCondition
278
+ * @property {(keyof ConfigurationType)[]} required - List of required config fields
279
+ * @property {string} errorMessage - Error message when required config fields not provided
280
+ */
281
+ /**
282
+ * @template {Partial<Record<keyof ConfigurationType, unknown>>} [ConfigurationType=ObjectNested]
283
+ * @typedef ChildClass
284
+ * @property {string} moduleName - The module name that'll be looked for in the DOM when initialising the component
285
+ * @property {Schema<ConfigurationType>} [schema] - The schema of the component configuration
286
+ * @property {ConfigurationType} [defaults] - The default values of the configuration of the component
287
+ */
288
+ /**
289
+ * @template {Partial<Record<keyof ConfigurationType, unknown>>} [ConfigurationType=ObjectNested]
290
+ * @typedef {typeof Component & ChildClass<ConfigurationType>} ChildClassConstructor<ConfigurationType>
244
291
  */
245
- GOVUKFrontendComponent.elementType = HTMLElement;
246
292
 
247
293
  class I18n {
248
294
  constructor(translations = {}, config = {}) {
@@ -441,15 +487,15 @@ I18n.pluralRules = {
441
487
  * Password input component
442
488
  *
443
489
  * @preserve
490
+ * @augments ConfigurableComponent<PasswordInputConfig>
444
491
  */
445
- class PasswordInput extends GOVUKFrontendComponent {
492
+ class PasswordInput extends ConfigurableComponent {
446
493
  /**
447
494
  * @param {Element | null} $root - HTML element to use for password input
448
495
  * @param {PasswordInputConfig} [config] - Password input config
449
496
  */
450
497
  constructor($root, config = {}) {
451
- super($root);
452
- this.config = void 0;
498
+ super($root, config);
453
499
  this.i18n = void 0;
454
500
  this.$input = void 0;
455
501
  this.$showHideButton = void 0;
@@ -480,7 +526,6 @@ class PasswordInput extends GOVUKFrontendComponent {
480
526
  }
481
527
  this.$input = $input;
482
528
  this.$showHideButton = $showHideButton;
483
- this.config = mergeConfigs(PasswordInput.defaults, config, normaliseDataset(PasswordInput, this.$root.dataset));
484
529
  this.i18n = new I18n(this.config.i18n, {
485
530
  locale: closestAttributeValue(this.$root, 'lang')
486
531
  });
@@ -560,8 +605,7 @@ class PasswordInput extends GOVUKFrontendComponent {
560
605
  */
561
606
 
562
607
  /**
563
- * @typedef {import('../../common/index.mjs').Schema} Schema
564
- * @typedef {import('../../i18n.mjs').TranslationPluralForms} TranslationPluralForms
608
+ * @import { Schema } from '../../common/configuration.mjs'
565
609
  */
566
610
  PasswordInput.moduleName = 'govuk-password-input';
567
611
  PasswordInput.defaults = Object.freeze({