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,75 +1,5 @@
1
- const version = '5.7.1';
1
+ const version = '5.9.0';
2
2
 
3
- function normaliseString(value, property) {
4
- const trimmedValue = value ? value.trim() : '';
5
- let output;
6
- let outputType = property == null ? void 0 : property.type;
7
- if (!outputType) {
8
- if (['true', 'false'].includes(trimmedValue)) {
9
- outputType = 'boolean';
10
- }
11
- if (trimmedValue.length > 0 && isFinite(Number(trimmedValue))) {
12
- outputType = 'number';
13
- }
14
- }
15
- switch (outputType) {
16
- case 'boolean':
17
- output = trimmedValue === 'true';
18
- break;
19
- case 'number':
20
- output = Number(trimmedValue);
21
- break;
22
- default:
23
- output = value;
24
- }
25
- return output;
26
- }
27
-
28
- /**
29
- * @typedef {import('./index.mjs').SchemaProperty} SchemaProperty
30
- */
31
-
32
- function mergeConfigs(...configObjects) {
33
- const formattedConfigObject = {};
34
- for (const configObject of configObjects) {
35
- for (const key of Object.keys(configObject)) {
36
- const option = formattedConfigObject[key];
37
- const override = configObject[key];
38
- if (isObject(option) && isObject(override)) {
39
- formattedConfigObject[key] = mergeConfigs(option, override);
40
- } else {
41
- formattedConfigObject[key] = override;
42
- }
43
- }
44
- }
45
- return formattedConfigObject;
46
- }
47
- function extractConfigByNamespace(Component, dataset, namespace) {
48
- const property = Component.schema.properties[namespace];
49
- if ((property == null ? void 0 : property.type) !== 'object') {
50
- return;
51
- }
52
- const newObject = {
53
- [namespace]: ({})
54
- };
55
- for (const [key, value] of Object.entries(dataset)) {
56
- let current = newObject;
57
- const keyParts = key.split('.');
58
- for (const [index, name] of keyParts.entries()) {
59
- if (typeof current === 'object') {
60
- if (index < keyParts.length - 1) {
61
- if (!isObject(current[name])) {
62
- current[name] = {};
63
- }
64
- current = current[name];
65
- } else if (key !== namespace) {
66
- current[name] = normaliseString(value);
67
- }
68
- }
69
- }
70
- }
71
- return newObject[namespace];
72
- }
73
3
  function getFragmentFromUrl(url) {
74
4
  if (!url.includes('#')) {
75
5
  return undefined;
@@ -127,26 +57,6 @@ function isSupported($scope = document.body) {
127
57
  }
128
58
  return $scope.classList.contains('govuk-frontend-supported');
129
59
  }
130
- function validateConfig(schema, config) {
131
- const validationErrors = [];
132
- for (const [name, conditions] of Object.entries(schema)) {
133
- const errors = [];
134
- if (Array.isArray(conditions)) {
135
- for (const {
136
- required,
137
- errorMessage
138
- } of conditions) {
139
- if (!required.every(key => !!config[key])) {
140
- errors.push(errorMessage);
141
- }
142
- }
143
- if (name === 'anyOf' && !(conditions.length - errors.length >= 1)) {
144
- validationErrors.push(...errors);
145
- }
146
- }
147
- }
148
- return validationErrors;
149
- }
150
60
  function isArray(option) {
151
61
  return Array.isArray(option);
152
62
  }
@@ -156,46 +66,13 @@ function isObject(option) {
156
66
  function formatErrorMessage(Component, message) {
157
67
  return `${Component.moduleName}: ${message}`;
158
68
  }
159
-
160
- /**
161
- * Schema for component config
162
- *
163
- * @typedef {object} Schema
164
- * @property {{ [field: string]: SchemaProperty | undefined }} properties - Schema properties
165
- * @property {SchemaCondition[]} [anyOf] - List of schema conditions
166
- */
167
-
168
- /**
169
- * Schema property for component config
170
- *
171
- * @typedef {object} SchemaProperty
172
- * @property {'string' | 'boolean' | 'number' | 'object'} type - Property type
173
- */
174
-
175
- /**
176
- * Schema condition for component config
177
- *
178
- * @typedef {object} SchemaCondition
179
- * @property {string[]} required - List of required config fields
180
- * @property {string} errorMessage - Error message when required config fields not provided
181
- */
182
69
  /**
183
70
  * @typedef ComponentWithModuleName
184
71
  * @property {string} moduleName - Name of the component
185
72
  */
186
-
187
- function normaliseDataset(Component, dataset) {
188
- const out = {};
189
- for (const [field, property] of Object.entries(Component.schema.properties)) {
190
- if (field in dataset) {
191
- out[field] = normaliseString(dataset[field], property);
192
- }
193
- if ((property == null ? void 0 : property.type) === 'object') {
194
- out[field] = extractConfigByNamespace(Component, dataset, field);
195
- }
196
- }
197
- return out;
198
- }
73
+ /**
74
+ * @import { ObjectNested } from './configuration.mjs'
75
+ */
199
76
 
200
77
  class GOVUKFrontendError extends Error {
201
78
  constructor(...args) {
@@ -247,10 +124,10 @@ class InitError extends GOVUKFrontendError {
247
124
  }
248
125
  }
249
126
  /**
250
- * @typedef {import('../common/index.mjs').ComponentWithModuleName} ComponentWithModuleName
127
+ * @import { ComponentWithModuleName } from '../common/index.mjs'
251
128
  */
252
129
 
253
- class GOVUKFrontendComponent {
130
+ class Component {
254
131
  /**
255
132
  * Returns the root element of the component
256
133
  *
@@ -301,9 +178,172 @@ class GOVUKFrontendComponent {
301
178
  */
302
179
 
303
180
  /**
304
- * @typedef {typeof GOVUKFrontendComponent & ChildClass} ChildClassConstructor
181
+ * @typedef {typeof Component & ChildClass} ChildClassConstructor
182
+ */
183
+ Component.elementType = HTMLElement;
184
+
185
+ const configOverride = Symbol.for('configOverride');
186
+ class ConfigurableComponent extends Component {
187
+ [configOverride](param) {
188
+ return {};
189
+ }
190
+
191
+ /**
192
+ * Returns the root element of the component
193
+ *
194
+ * @protected
195
+ * @returns {ConfigurationType} - the root element of component
196
+ */
197
+ get config() {
198
+ return this._config;
199
+ }
200
+ constructor($root, config) {
201
+ super($root);
202
+ this._config = void 0;
203
+ const childConstructor = this.constructor;
204
+ if (!isObject(childConstructor.defaults)) {
205
+ throw new ConfigError(formatErrorMessage(childConstructor, 'Config passed as parameter into constructor but no defaults defined'));
206
+ }
207
+ const datasetConfig = normaliseDataset(childConstructor, this._$root.dataset);
208
+ this._config = mergeConfigs(childConstructor.defaults, config != null ? config : {}, this[configOverride](datasetConfig), datasetConfig);
209
+ }
210
+ }
211
+ function normaliseString(value, property) {
212
+ const trimmedValue = value ? value.trim() : '';
213
+ let output;
214
+ let outputType = property == null ? void 0 : property.type;
215
+ if (!outputType) {
216
+ if (['true', 'false'].includes(trimmedValue)) {
217
+ outputType = 'boolean';
218
+ }
219
+ if (trimmedValue.length > 0 && isFinite(Number(trimmedValue))) {
220
+ outputType = 'number';
221
+ }
222
+ }
223
+ switch (outputType) {
224
+ case 'boolean':
225
+ output = trimmedValue === 'true';
226
+ break;
227
+ case 'number':
228
+ output = Number(trimmedValue);
229
+ break;
230
+ default:
231
+ output = value;
232
+ }
233
+ return output;
234
+ }
235
+ function normaliseDataset(Component, dataset) {
236
+ if (!isObject(Component.schema)) {
237
+ throw new ConfigError(formatErrorMessage(Component, 'Config passed as parameter into constructor but no schema defined'));
238
+ }
239
+ const out = {};
240
+ const entries = Object.entries(Component.schema.properties);
241
+ for (const entry of entries) {
242
+ const [namespace, property] = entry;
243
+ const field = namespace.toString();
244
+ if (field in dataset) {
245
+ out[field] = normaliseString(dataset[field], property);
246
+ }
247
+ if ((property == null ? void 0 : property.type) === 'object') {
248
+ out[field] = extractConfigByNamespace(Component.schema, dataset, namespace);
249
+ }
250
+ }
251
+ return out;
252
+ }
253
+ function mergeConfigs(...configObjects) {
254
+ const formattedConfigObject = {};
255
+ for (const configObject of configObjects) {
256
+ for (const key of Object.keys(configObject)) {
257
+ const option = formattedConfigObject[key];
258
+ const override = configObject[key];
259
+ if (isObject(option) && isObject(override)) {
260
+ formattedConfigObject[key] = mergeConfigs(option, override);
261
+ } else {
262
+ formattedConfigObject[key] = override;
263
+ }
264
+ }
265
+ }
266
+ return formattedConfigObject;
267
+ }
268
+ function validateConfig(schema, config) {
269
+ const validationErrors = [];
270
+ for (const [name, conditions] of Object.entries(schema)) {
271
+ const errors = [];
272
+ if (Array.isArray(conditions)) {
273
+ for (const {
274
+ required,
275
+ errorMessage
276
+ } of conditions) {
277
+ if (!required.every(key => !!config[key])) {
278
+ errors.push(errorMessage);
279
+ }
280
+ }
281
+ if (name === 'anyOf' && !(conditions.length - errors.length >= 1)) {
282
+ validationErrors.push(...errors);
283
+ }
284
+ }
285
+ }
286
+ return validationErrors;
287
+ }
288
+ function extractConfigByNamespace(schema, dataset, namespace) {
289
+ const property = schema.properties[namespace];
290
+ if ((property == null ? void 0 : property.type) !== 'object') {
291
+ return;
292
+ }
293
+ const newObject = {
294
+ [namespace]: {}
295
+ };
296
+ for (const [key, value] of Object.entries(dataset)) {
297
+ let current = newObject;
298
+ const keyParts = key.split('.');
299
+ for (const [index, name] of keyParts.entries()) {
300
+ if (isObject(current)) {
301
+ if (index < keyParts.length - 1) {
302
+ if (!isObject(current[name])) {
303
+ current[name] = {};
304
+ }
305
+ current = current[name];
306
+ } else if (key !== namespace) {
307
+ current[name] = normaliseString(value);
308
+ }
309
+ }
310
+ }
311
+ }
312
+ return newObject[namespace];
313
+ }
314
+ /**
315
+ * Schema for component config
316
+ *
317
+ * @template {Partial<Record<keyof ConfigurationType, unknown>>} ConfigurationType
318
+ * @typedef {object} Schema
319
+ * @property {Record<keyof ConfigurationType, SchemaProperty | undefined>} properties - Schema properties
320
+ * @property {SchemaCondition<ConfigurationType>[]} [anyOf] - List of schema conditions
321
+ */
322
+ /**
323
+ * Schema property for component config
324
+ *
325
+ * @typedef {object} SchemaProperty
326
+ * @property {'string' | 'boolean' | 'number' | 'object'} type - Property type
327
+ */
328
+ /**
329
+ * Schema condition for component config
330
+ *
331
+ * @template {Partial<Record<keyof ConfigurationType, unknown>>} ConfigurationType
332
+ * @typedef {object} SchemaCondition
333
+ * @property {(keyof ConfigurationType)[]} required - List of required config fields
334
+ * @property {string} errorMessage - Error message when required config fields not provided
335
+ */
336
+ /**
337
+ * @template {Partial<Record<keyof ConfigurationType, unknown>>} [ConfigurationType=ObjectNested]
338
+ * @typedef ChildClass
339
+ * @property {string} moduleName - The module name that'll be looked for in the DOM when initialising the component
340
+ * @property {Schema<ConfigurationType>} [schema] - The schema of the component configuration
341
+ * @property {ConfigurationType} [defaults] - The default values of the configuration of the component
342
+ */
343
+ /**
344
+ * @template {Partial<Record<keyof ConfigurationType, unknown>>} [ConfigurationType=ObjectNested]
345
+ * @typedef {typeof Component & ChildClass<ConfigurationType>} ChildClassConstructor<ConfigurationType>
305
346
  */
306
- GOVUKFrontendComponent.elementType = HTMLElement;
307
347
 
308
348
  class I18n {
309
349
  constructor(translations = {}, config = {}) {
@@ -511,15 +551,15 @@ I18n.pluralRules = {
511
551
  * attribute, which also provides accessibility.
512
552
  *
513
553
  * @preserve
554
+ * @augments ConfigurableComponent<AccordionConfig>
514
555
  */
515
- class Accordion extends GOVUKFrontendComponent {
556
+ class Accordion extends ConfigurableComponent {
516
557
  /**
517
558
  * @param {Element | null} $root - HTML element to use for accordion
518
559
  * @param {AccordionConfig} [config] - Accordion config
519
560
  */
520
561
  constructor($root, config = {}) {
521
- super($root);
522
- this.config = void 0;
562
+ super($root, config);
523
563
  this.i18n = void 0;
524
564
  this.controlsClass = 'govuk-accordion__controls';
525
565
  this.showAllClass = 'govuk-accordion__show-all';
@@ -544,7 +584,6 @@ class Accordion extends GOVUKFrontendComponent {
544
584
  this.$showAllButton = null;
545
585
  this.$showAllIcon = null;
546
586
  this.$showAllText = null;
547
- this.config = mergeConfigs(Accordion.defaults, config, normaliseDataset(Accordion, this.$root.dataset));
548
587
  this.i18n = new I18n(this.config.i18n);
549
588
  const $sections = this.$root.querySelectorAll(`.${this.sectionClass}`);
550
589
  if (!$sections.length) {
@@ -814,7 +853,7 @@ class Accordion extends GOVUKFrontendComponent {
814
853
  */
815
854
 
816
855
  /**
817
- * @typedef {import('../../common/index.mjs').Schema} Schema
856
+ * @import { Schema } from '../../common/configuration.mjs'
818
857
  */
819
858
  Accordion.moduleName = 'govuk-accordion';
820
859
  Accordion.defaults = Object.freeze({
@@ -845,17 +884,16 @@ const DEBOUNCE_TIMEOUT_IN_SECONDS = 1;
845
884
  * JavaScript enhancements for the Button component
846
885
  *
847
886
  * @preserve
887
+ * @augments ConfigurableComponent<ButtonConfig>
848
888
  */
849
- class Button extends GOVUKFrontendComponent {
889
+ class Button extends ConfigurableComponent {
850
890
  /**
851
891
  * @param {Element | null} $root - HTML element to use for button
852
892
  * @param {ButtonConfig} [config] - Button config
853
893
  */
854
894
  constructor($root, config = {}) {
855
- super($root);
856
- this.config = void 0;
895
+ super($root, config);
857
896
  this.debounceFormSubmitTimer = null;
858
- this.config = mergeConfigs(Button.defaults, config, normaliseDataset(Button, this.$root.dataset));
859
897
  this.$root.addEventListener('keydown', event => this.handleKeyDown(event));
860
898
  this.$root.addEventListener('click', event => this.debounce(event));
861
899
  }
@@ -892,7 +930,7 @@ class Button extends GOVUKFrontendComponent {
892
930
  */
893
931
 
894
932
  /**
895
- * @typedef {import('../../common/index.mjs').Schema} Schema
933
+ * @import { Schema } from '../../common/configuration.mjs'
896
934
  */
897
935
  Button.moduleName = 'govuk-button';
898
936
  Button.defaults = Object.freeze({
@@ -922,22 +960,33 @@ function closestAttributeValue($element, attributeName) {
922
960
  * of the available characters/words has been entered.
923
961
  *
924
962
  * @preserve
963
+ * @augments ConfigurableComponent<CharacterCountConfig>
925
964
  */
926
- class CharacterCount extends GOVUKFrontendComponent {
965
+ class CharacterCount extends ConfigurableComponent {
966
+ [configOverride](datasetConfig) {
967
+ let configOverrides = {};
968
+ if ('maxwords' in datasetConfig || 'maxlength' in datasetConfig) {
969
+ configOverrides = {
970
+ maxlength: undefined,
971
+ maxwords: undefined
972
+ };
973
+ }
974
+ return configOverrides;
975
+ }
976
+
927
977
  /**
928
978
  * @param {Element | null} $root - HTML element to use for character count
929
979
  * @param {CharacterCountConfig} [config] - Character count config
930
980
  */
931
981
  constructor($root, config = {}) {
932
982
  var _ref, _this$config$maxwords;
933
- super($root);
983
+ super($root, config);
934
984
  this.$textarea = void 0;
935
985
  this.$visibleCountMessage = void 0;
936
986
  this.$screenReaderCountMessage = void 0;
937
987
  this.lastInputTimestamp = null;
938
988
  this.lastInputValue = '';
939
989
  this.valueChecker = null;
940
- this.config = void 0;
941
990
  this.i18n = void 0;
942
991
  this.maxLength = void 0;
943
992
  const $textarea = this.$root.querySelector('.govuk-js-character-count');
@@ -949,15 +998,6 @@ class CharacterCount extends GOVUKFrontendComponent {
949
998
  identifier: 'Form field (`.govuk-js-character-count`)'
950
999
  });
951
1000
  }
952
- const datasetConfig = normaliseDataset(CharacterCount, this.$root.dataset);
953
- let configOverrides = {};
954
- if ('maxwords' in datasetConfig || 'maxlength' in datasetConfig) {
955
- configOverrides = {
956
- maxlength: undefined,
957
- maxwords: undefined
958
- };
959
- }
960
- this.config = mergeConfigs(CharacterCount.defaults, config, configOverrides, datasetConfig);
961
1001
  const errors = validateConfig(CharacterCount.schema, this.config);
962
1002
  if (errors[0]) {
963
1003
  throw new ConfigError(formatErrorMessage(CharacterCount, errors[0]));
@@ -1143,8 +1183,8 @@ class CharacterCount extends GOVUKFrontendComponent {
1143
1183
  */
1144
1184
 
1145
1185
  /**
1146
- * @typedef {import('../../common/index.mjs').Schema} Schema
1147
- * @typedef {import('../../i18n.mjs').TranslationPluralForms} TranslationPluralForms
1186
+ * @import { Schema } from '../../common/configuration.mjs'
1187
+ * @import { TranslationPluralForms } from '../../i18n.mjs'
1148
1188
  */
1149
1189
  CharacterCount.moduleName = 'govuk-character-count';
1150
1190
  CharacterCount.defaults = Object.freeze({
@@ -1202,7 +1242,7 @@ CharacterCount.schema = Object.freeze({
1202
1242
  *
1203
1243
  * @preserve
1204
1244
  */
1205
- class Checkboxes extends GOVUKFrontendComponent {
1245
+ class Checkboxes extends Component {
1206
1246
  /**
1207
1247
  * Checkboxes can be associated with a 'conditionally revealed' content block
1208
1248
  * – for example, a checkbox for 'Phone' could reveal an additional form field
@@ -1310,16 +1350,15 @@ Checkboxes.moduleName = 'govuk-checkboxes';
1310
1350
  * configuration.
1311
1351
  *
1312
1352
  * @preserve
1353
+ * @augments ConfigurableComponent<ErrorSummaryConfig>
1313
1354
  */
1314
- class ErrorSummary extends GOVUKFrontendComponent {
1355
+ class ErrorSummary extends ConfigurableComponent {
1315
1356
  /**
1316
1357
  * @param {Element | null} $root - HTML element to use for error summary
1317
1358
  * @param {ErrorSummaryConfig} [config] - Error summary config
1318
1359
  */
1319
1360
  constructor($root, config = {}) {
1320
- super($root);
1321
- this.config = void 0;
1322
- this.config = mergeConfigs(ErrorSummary.defaults, config, normaliseDataset(ErrorSummary, this.$root.dataset));
1361
+ super($root, config);
1323
1362
  if (!this.config.disableAutoFocus) {
1324
1363
  setFocus(this.$root);
1325
1364
  }
@@ -1386,7 +1425,7 @@ class ErrorSummary extends GOVUKFrontendComponent {
1386
1425
  */
1387
1426
 
1388
1427
  /**
1389
- * @typedef {import('../../common/index.mjs').Schema} Schema
1428
+ * @import { Schema } from '../../common/configuration.mjs'
1390
1429
  */
1391
1430
  ErrorSummary.moduleName = 'govuk-error-summary';
1392
1431
  ErrorSummary.defaults = Object.freeze({
@@ -1404,15 +1443,15 @@ ErrorSummary.schema = Object.freeze({
1404
1443
  * Exit this page component
1405
1444
  *
1406
1445
  * @preserve
1446
+ * @augments ConfigurableComponent<ExitThisPageConfig>
1407
1447
  */
1408
- class ExitThisPage extends GOVUKFrontendComponent {
1448
+ class ExitThisPage extends ConfigurableComponent {
1409
1449
  /**
1410
1450
  * @param {Element | null} $root - HTML element that wraps the Exit This Page button
1411
1451
  * @param {ExitThisPageConfig} [config] - Exit This Page config
1412
1452
  */
1413
1453
  constructor($root, config = {}) {
1414
- super($root);
1415
- this.config = void 0;
1454
+ super($root, config);
1416
1455
  this.i18n = void 0;
1417
1456
  this.$button = void 0;
1418
1457
  this.$skiplinkButton = null;
@@ -1433,7 +1472,6 @@ class ExitThisPage extends GOVUKFrontendComponent {
1433
1472
  identifier: 'Button (`.govuk-exit-this-page__button`)'
1434
1473
  });
1435
1474
  }
1436
- this.config = mergeConfigs(ExitThisPage.defaults, config, normaliseDataset(ExitThisPage, this.$root.dataset));
1437
1475
  this.i18n = new I18n(this.config.i18n);
1438
1476
  this.$button = $button;
1439
1477
  const $skiplinkButton = document.querySelector('.govuk-js-exit-this-page-skiplink');
@@ -1599,7 +1637,7 @@ class ExitThisPage extends GOVUKFrontendComponent {
1599
1637
  */
1600
1638
 
1601
1639
  /**
1602
- * @typedef {import('../../common/index.mjs').Schema} Schema
1640
+ * @import { Schema } from '../../common/configuration.mjs'
1603
1641
  */
1604
1642
  ExitThisPage.moduleName = 'govuk-exit-this-page';
1605
1643
  ExitThisPage.defaults = Object.freeze({
@@ -1618,12 +1656,271 @@ ExitThisPage.schema = Object.freeze({
1618
1656
  }
1619
1657
  });
1620
1658
 
1659
+ /**
1660
+ * File upload component
1661
+ *
1662
+ * @preserve
1663
+ * @augments ConfigurableComponent<FileUploadConfig>
1664
+ */
1665
+ class FileUpload extends ConfigurableComponent {
1666
+ /**
1667
+ * @param {Element | null} $root - File input element
1668
+ * @param {FileUploadConfig} [config] - File Upload config
1669
+ */
1670
+ constructor($root, config = {}) {
1671
+ super($root, config);
1672
+ this.$input = void 0;
1673
+ this.$button = void 0;
1674
+ this.$status = void 0;
1675
+ this.i18n = void 0;
1676
+ this.id = void 0;
1677
+ const $input = this.$root.querySelector('input');
1678
+ if ($input === null) {
1679
+ throw new ElementError({
1680
+ component: FileUpload,
1681
+ identifier: 'File inputs (`<input type="file">`)'
1682
+ });
1683
+ }
1684
+ if ($input.type !== 'file') {
1685
+ throw new ElementError(formatErrorMessage(FileUpload, 'File input (`<input type="file">`) attribute (`type`) is not `file`'));
1686
+ }
1687
+ this.$input = $input;
1688
+ this.$input.setAttribute('hidden', 'true');
1689
+ if (!this.$input.id) {
1690
+ throw new ElementError({
1691
+ component: FileUpload,
1692
+ identifier: 'File input (`<input type="file">`) attribute (`id`)'
1693
+ });
1694
+ }
1695
+ this.id = this.$input.id;
1696
+ this.i18n = new I18n(this.config.i18n, {
1697
+ locale: closestAttributeValue(this.$root, 'lang')
1698
+ });
1699
+ const $label = this.findLabel();
1700
+ if (!$label.id) {
1701
+ $label.id = `${this.id}-label`;
1702
+ }
1703
+ this.$input.id = `${this.id}-input`;
1704
+ const $button = document.createElement('button');
1705
+ $button.classList.add('govuk-file-upload-button');
1706
+ $button.type = 'button';
1707
+ $button.id = this.id;
1708
+ $button.classList.add('govuk-file-upload-button--empty');
1709
+ const ariaDescribedBy = this.$input.getAttribute('aria-describedby');
1710
+ if (ariaDescribedBy) {
1711
+ $button.setAttribute('aria-describedby', ariaDescribedBy);
1712
+ }
1713
+ const $status = document.createElement('span');
1714
+ $status.className = 'govuk-body govuk-file-upload-button__status';
1715
+ $status.setAttribute('aria-live', 'polite');
1716
+ $status.innerText = this.i18n.t('noFileChosen');
1717
+ $button.appendChild($status);
1718
+ const commaSpan = document.createElement('span');
1719
+ commaSpan.className = 'govuk-visually-hidden';
1720
+ commaSpan.innerText = ', ';
1721
+ commaSpan.id = `${this.id}-comma`;
1722
+ $button.appendChild(commaSpan);
1723
+ const containerSpan = document.createElement('span');
1724
+ containerSpan.className = 'govuk-file-upload-button__pseudo-button-container';
1725
+ const buttonSpan = document.createElement('span');
1726
+ buttonSpan.className = 'govuk-button govuk-button--secondary govuk-file-upload-button__pseudo-button';
1727
+ buttonSpan.innerText = this.i18n.t('chooseFilesButton');
1728
+ containerSpan.appendChild(buttonSpan);
1729
+ containerSpan.insertAdjacentText('beforeend', ' ');
1730
+ const instructionSpan = document.createElement('span');
1731
+ instructionSpan.className = 'govuk-body govuk-file-upload-button__instruction';
1732
+ instructionSpan.innerText = this.i18n.t('dropInstruction');
1733
+ containerSpan.appendChild(instructionSpan);
1734
+ $button.appendChild(containerSpan);
1735
+ $button.setAttribute('aria-labelledby', `${$label.id} ${commaSpan.id} ${$button.id}`);
1736
+ $button.addEventListener('click', this.onClick.bind(this));
1737
+ $button.addEventListener('dragover', event => {
1738
+ event.preventDefault();
1739
+ });
1740
+ this.$root.insertAdjacentElement('afterbegin', $button);
1741
+ this.$input.setAttribute('tabindex', '-1');
1742
+ this.$input.setAttribute('aria-hidden', 'true');
1743
+ this.$button = $button;
1744
+ this.$status = $status;
1745
+ this.$input.addEventListener('change', this.onChange.bind(this));
1746
+ this.updateDisabledState();
1747
+ this.observeDisabledState();
1748
+ this.$announcements = document.createElement('span');
1749
+ this.$announcements.classList.add('govuk-file-upload-announcements');
1750
+ this.$announcements.classList.add('govuk-visually-hidden');
1751
+ this.$announcements.setAttribute('aria-live', 'assertive');
1752
+ this.$root.insertAdjacentElement('afterend', this.$announcements);
1753
+ this.$button.addEventListener('drop', this.onDrop.bind(this));
1754
+ document.addEventListener('dragenter', this.updateDropzoneVisibility.bind(this));
1755
+ document.addEventListener('dragenter', () => {
1756
+ this.enteredAnotherElement = true;
1757
+ });
1758
+ document.addEventListener('dragleave', () => {
1759
+ if (!this.enteredAnotherElement && !this.$button.disabled) {
1760
+ this.hideDraggingState();
1761
+ this.$announcements.innerText = this.i18n.t('leftDropZone');
1762
+ }
1763
+ this.enteredAnotherElement = false;
1764
+ });
1765
+ }
1766
+
1767
+ /**
1768
+ * Updates the visibility of the dropzone as users enters the various elements on the page
1769
+ *
1770
+ * @param {DragEvent} event - The `dragenter` event
1771
+ */
1772
+ updateDropzoneVisibility(event) {
1773
+ if (this.$button.disabled) return;
1774
+ if (event.target instanceof Node) {
1775
+ if (this.$root.contains(event.target)) {
1776
+ if (event.dataTransfer && isContainingFiles(event.dataTransfer)) {
1777
+ if (!this.$button.classList.contains('govuk-file-upload-button--dragging')) {
1778
+ this.showDraggingState();
1779
+ this.$announcements.innerText = this.i18n.t('enteredDropZone');
1780
+ }
1781
+ }
1782
+ } else {
1783
+ if (this.$button.classList.contains('govuk-file-upload-button--dragging')) {
1784
+ this.hideDraggingState();
1785
+ this.$announcements.innerText = this.i18n.t('leftDropZone');
1786
+ }
1787
+ }
1788
+ }
1789
+ }
1790
+ showDraggingState() {
1791
+ this.$button.classList.add('govuk-file-upload-button--dragging');
1792
+ }
1793
+ hideDraggingState() {
1794
+ this.$button.classList.remove('govuk-file-upload-button--dragging');
1795
+ }
1796
+
1797
+ /**
1798
+ * Handles user dropping on the component
1799
+ *
1800
+ * @param {DragEvent} event - The `dragenter` event
1801
+ */
1802
+ onDrop(event) {
1803
+ event.preventDefault();
1804
+ if (event.dataTransfer && isContainingFiles(event.dataTransfer)) {
1805
+ this.$input.files = event.dataTransfer.files;
1806
+ this.$input.dispatchEvent(new CustomEvent('change'));
1807
+ this.hideDraggingState();
1808
+ }
1809
+ }
1810
+ onChange() {
1811
+ const fileCount = this.$input.files.length;
1812
+ if (fileCount === 0) {
1813
+ this.$status.innerText = this.i18n.t('noFileChosen');
1814
+ this.$button.classList.add('govuk-file-upload-button--empty');
1815
+ } else {
1816
+ if (fileCount === 1) {
1817
+ this.$status.innerText = this.$input.files[0].name;
1818
+ } else {
1819
+ this.$status.innerText = this.i18n.t('multipleFilesChosen', {
1820
+ count: fileCount
1821
+ });
1822
+ }
1823
+ this.$button.classList.remove('govuk-file-upload-button--empty');
1824
+ }
1825
+ }
1826
+ findLabel() {
1827
+ const $label = document.querySelector(`label[for="${this.$input.id}"]`);
1828
+ if (!$label) {
1829
+ throw new ElementError({
1830
+ component: FileUpload,
1831
+ identifier: `Field label (\`<label for=${this.$input.id}>\`)`
1832
+ });
1833
+ }
1834
+ return $label;
1835
+ }
1836
+ onClick() {
1837
+ this.$input.click();
1838
+ }
1839
+ observeDisabledState() {
1840
+ const observer = new MutationObserver(mutationList => {
1841
+ for (const mutation of mutationList) {
1842
+ if (mutation.type === 'attributes' && mutation.attributeName === 'disabled') {
1843
+ this.updateDisabledState();
1844
+ }
1845
+ }
1846
+ });
1847
+ observer.observe(this.$input, {
1848
+ attributes: true
1849
+ });
1850
+ }
1851
+ updateDisabledState() {
1852
+ this.$button.disabled = this.$input.disabled;
1853
+ this.$root.classList.toggle('govuk-drop-zone--disabled', this.$button.disabled);
1854
+ }
1855
+ }
1856
+ FileUpload.moduleName = 'govuk-file-upload';
1857
+ FileUpload.defaults = Object.freeze({
1858
+ i18n: {
1859
+ chooseFilesButton: 'Choose file',
1860
+ dropInstruction: 'or drop file',
1861
+ noFileChosen: 'No file chosen',
1862
+ multipleFilesChosen: {
1863
+ one: '%{count} file chosen',
1864
+ other: '%{count} files chosen'
1865
+ },
1866
+ enteredDropZone: 'Entered drop zone',
1867
+ leftDropZone: 'Left drop zone'
1868
+ }
1869
+ });
1870
+ FileUpload.schema = Object.freeze({
1871
+ properties: {
1872
+ i18n: {
1873
+ type: 'object'
1874
+ }
1875
+ }
1876
+ });
1877
+ function isContainingFiles(dataTransfer) {
1878
+ const hasNoTypesInfo = dataTransfer.types.length === 0;
1879
+ const isDraggingFiles = dataTransfer.types.some(type => type === 'Files');
1880
+ return hasNoTypesInfo || isDraggingFiles;
1881
+ }
1882
+
1883
+ /**
1884
+ * @typedef {HTMLInputElement & {files: FileList}} HTMLFileInputElement
1885
+ */
1886
+
1887
+ /**
1888
+ * File upload config
1889
+ *
1890
+ * @see {@link FileUpload.defaults}
1891
+ * @typedef {object} FileUploadConfig
1892
+ * @property {FileUploadTranslations} [i18n=FileUpload.defaults.i18n] - File upload translations
1893
+ */
1894
+
1895
+ /**
1896
+ * File upload translations
1897
+ *
1898
+ * @see {@link FileUpload.defaults.i18n}
1899
+ * @typedef {object} FileUploadTranslations
1900
+ *
1901
+ * Messages used by the component
1902
+ * @property {string} [chooseFile] - The text of the button that opens the file picker
1903
+ * @property {string} [dropInstruction] - The text informing users they can drop files
1904
+ * @property {TranslationPluralForms} [multipleFilesChosen] - The text displayed when multiple files
1905
+ * have been chosen by the user
1906
+ * @property {string} [noFileChosen] - The text to displayed when no file has been chosen by the user
1907
+ * @property {string} [enteredDropZone] - The text announced by assistive technology
1908
+ * when user drags files and enters the drop zone
1909
+ * @property {string} [leftDropZone] - The text announced by assistive technology
1910
+ * when user drags files and leaves the drop zone without dropping
1911
+ */
1912
+
1913
+ /**
1914
+ * @import { Schema } from '../../common/configuration.mjs'
1915
+ * @import { TranslationPluralForms } from '../../i18n.mjs'
1916
+ */
1917
+
1621
1918
  /**
1622
1919
  * Header component
1623
1920
  *
1624
1921
  * @preserve
1625
1922
  */
1626
- class Header extends GOVUKFrontendComponent {
1923
+ class Header extends Component {
1627
1924
  /**
1628
1925
  * Apply a matchMedia for desktop which will trigger a state sync if the
1629
1926
  * browser viewport moves between states.
@@ -1704,16 +2001,15 @@ Header.moduleName = 'govuk-header';
1704
2001
  * Notification Banner component
1705
2002
  *
1706
2003
  * @preserve
2004
+ * @augments ConfigurableComponent<NotificationBannerConfig>
1707
2005
  */
1708
- class NotificationBanner extends GOVUKFrontendComponent {
2006
+ class NotificationBanner extends ConfigurableComponent {
1709
2007
  /**
1710
2008
  * @param {Element | null} $root - HTML element to use for notification banner
1711
2009
  * @param {NotificationBannerConfig} [config] - Notification banner config
1712
2010
  */
1713
2011
  constructor($root, config = {}) {
1714
- super($root);
1715
- this.config = void 0;
1716
- this.config = mergeConfigs(NotificationBanner.defaults, config, normaliseDataset(NotificationBanner, this.$root.dataset));
2012
+ super($root, config);
1717
2013
  if (this.$root.getAttribute('role') === 'alert' && !this.config.disableAutoFocus) {
1718
2014
  setFocus(this.$root);
1719
2015
  }
@@ -1731,7 +2027,7 @@ class NotificationBanner extends GOVUKFrontendComponent {
1731
2027
  */
1732
2028
 
1733
2029
  /**
1734
- * @typedef {import('../../common/index.mjs').Schema} Schema
2030
+ * @import { Schema } from '../../common/configuration.mjs'
1735
2031
  */
1736
2032
  NotificationBanner.moduleName = 'govuk-notification-banner';
1737
2033
  NotificationBanner.defaults = Object.freeze({
@@ -1749,15 +2045,15 @@ NotificationBanner.schema = Object.freeze({
1749
2045
  * Password input component
1750
2046
  *
1751
2047
  * @preserve
2048
+ * @augments ConfigurableComponent<PasswordInputConfig>
1752
2049
  */
1753
- class PasswordInput extends GOVUKFrontendComponent {
2050
+ class PasswordInput extends ConfigurableComponent {
1754
2051
  /**
1755
2052
  * @param {Element | null} $root - HTML element to use for password input
1756
2053
  * @param {PasswordInputConfig} [config] - Password input config
1757
2054
  */
1758
2055
  constructor($root, config = {}) {
1759
- super($root);
1760
- this.config = void 0;
2056
+ super($root, config);
1761
2057
  this.i18n = void 0;
1762
2058
  this.$input = void 0;
1763
2059
  this.$showHideButton = void 0;
@@ -1788,7 +2084,6 @@ class PasswordInput extends GOVUKFrontendComponent {
1788
2084
  }
1789
2085
  this.$input = $input;
1790
2086
  this.$showHideButton = $showHideButton;
1791
- this.config = mergeConfigs(PasswordInput.defaults, config, normaliseDataset(PasswordInput, this.$root.dataset));
1792
2087
  this.i18n = new I18n(this.config.i18n, {
1793
2088
  locale: closestAttributeValue(this.$root, 'lang')
1794
2089
  });
@@ -1868,8 +2163,7 @@ class PasswordInput extends GOVUKFrontendComponent {
1868
2163
  */
1869
2164
 
1870
2165
  /**
1871
- * @typedef {import('../../common/index.mjs').Schema} Schema
1872
- * @typedef {import('../../i18n.mjs').TranslationPluralForms} TranslationPluralForms
2166
+ * @import { Schema } from '../../common/configuration.mjs'
1873
2167
  */
1874
2168
  PasswordInput.moduleName = 'govuk-password-input';
1875
2169
  PasswordInput.defaults = Object.freeze({
@@ -1895,7 +2189,7 @@ PasswordInput.schema = Object.freeze({
1895
2189
  *
1896
2190
  * @preserve
1897
2191
  */
1898
- class Radios extends GOVUKFrontendComponent {
2192
+ class Radios extends Component {
1899
2193
  /**
1900
2194
  * Radios can be associated with a 'conditionally revealed' content block –
1901
2195
  * for example, a radio for 'Phone' could reveal an additional form field for
@@ -1978,7 +2272,7 @@ Radios.moduleName = 'govuk-radios';
1978
2272
  *
1979
2273
  * @preserve
1980
2274
  */
1981
- class ServiceNavigation extends GOVUKFrontendComponent {
2275
+ class ServiceNavigation extends Component {
1982
2276
  /**
1983
2277
  * @param {Element | null} $root - HTML element to use for header
1984
2278
  */
@@ -2056,9 +2350,9 @@ ServiceNavigation.moduleName = 'govuk-service-navigation';
2056
2350
  * Skip link component
2057
2351
  *
2058
2352
  * @preserve
2059
- * @augments GOVUKFrontendComponent<HTMLAnchorElement>
2353
+ * @augments Component<HTMLAnchorElement>
2060
2354
  */
2061
- class SkipLink extends GOVUKFrontendComponent {
2355
+ class SkipLink extends Component {
2062
2356
  /**
2063
2357
  * @param {Element | null} $root - HTML element to use for skip link
2064
2358
  * @throws {ElementError} when $root is not set or the wrong type
@@ -2109,7 +2403,7 @@ SkipLink.moduleName = 'govuk-skip-link';
2109
2403
  *
2110
2404
  * @preserve
2111
2405
  */
2112
- class Tabs extends GOVUKFrontendComponent {
2406
+ class Tabs extends Component {
2113
2407
  /**
2114
2408
  * @param {Element | null} $root - HTML element to use for tabs
2115
2409
  */
@@ -2402,7 +2696,7 @@ function initAll(config) {
2402
2696
  }
2403
2697
  return;
2404
2698
  }
2405
- const components = [[Accordion, config.accordion], [Button, config.button], [CharacterCount, config.characterCount], [Checkboxes], [ErrorSummary, config.errorSummary], [ExitThisPage, config.exitThisPage], [Header], [NotificationBanner, config.notificationBanner], [PasswordInput, config.passwordInput], [Radios], [ServiceNavigation], [SkipLink], [Tabs]];
2699
+ const components = [[Accordion, config.accordion], [Button, config.button], [CharacterCount, config.characterCount], [Checkboxes], [ErrorSummary, config.errorSummary], [ExitThisPage, config.exitThisPage], [FileUpload, config.fileUpload], [Header], [NotificationBanner, config.notificationBanner], [PasswordInput, config.passwordInput], [Radios], [ServiceNavigation], [SkipLink], [Tabs]];
2406
2700
  const options = {
2407
2701
  scope: (_config$scope = config.scope) != null ? _config$scope : document,
2408
2702
  onError: config.onError
@@ -2421,11 +2715,11 @@ function initAll(config) {
2421
2715
  *
2422
2716
  * Any component errors will be caught and logged to the console.
2423
2717
  *
2424
- * @template {CompatibleClass} T
2425
- * @param {T} Component - class of the component to create
2426
- * @param {T["defaults"]} [config] - Config supplied to component
2427
- * @param {OnErrorCallback<T> | Element | Document | CreateAllOptions<T> } [createAllOptions] - options for createAll including scope of the document to search within and callback function if error throw by component on init
2428
- * @returns {Array<InstanceType<T>>} - array of instantiated components
2718
+ * @template {CompatibleClass} ComponentClass
2719
+ * @param {ComponentClass} Component - class of the component to create
2720
+ * @param {ComponentConfig<ComponentClass>} [config] - Config supplied to component
2721
+ * @param {OnErrorCallback<ComponentClass> | Element | Document | CreateAllOptions<ComponentClass> } [createAllOptions] - options for createAll including scope of the document to search within and callback function if error throw by component on init
2722
+ * @returns {Array<InstanceType<ComponentClass>>} - array of instantiated components
2429
2723
  */
2430
2724
  function createAll(Component, config, createAllOptions) {
2431
2725
  let $scope = document;
@@ -2472,7 +2766,7 @@ function createAll(Component, config, createAllOptions) {
2472
2766
  }).filter(Boolean);
2473
2767
  }
2474
2768
  /**
2475
- * @typedef {{new (...args: any[]): any, defaults?: object, moduleName: string}} CompatibleClass
2769
+ * @typedef {{new (...args: any[]): any, moduleName: string}} CompatibleClass
2476
2770
  */
2477
2771
  /**
2478
2772
  * Config for all components via `initAll()`
@@ -2483,22 +2777,21 @@ function createAll(Component, config, createAllOptions) {
2483
2777
  * @property {CharacterCountConfig} [characterCount] - Character Count config
2484
2778
  * @property {ErrorSummaryConfig} [errorSummary] - Error Summary config
2485
2779
  * @property {ExitThisPageConfig} [exitThisPage] - Exit This Page config
2780
+ * @property {FileUploadConfig} [fileUpload] - File Upload config
2486
2781
  * @property {NotificationBannerConfig} [notificationBanner] - Notification Banner config
2487
2782
  * @property {PasswordInputConfig} [passwordInput] - Password input config
2488
2783
  */
2489
2784
  /**
2490
2785
  * Config for individual components
2491
2786
  *
2492
- * @typedef {import('./components/accordion/accordion.mjs').AccordionConfig} AccordionConfig
2493
- * @typedef {import('./components/accordion/accordion.mjs').AccordionTranslations} AccordionTranslations
2494
- * @typedef {import('./components/button/button.mjs').ButtonConfig} ButtonConfig
2495
- * @typedef {import('./components/character-count/character-count.mjs').CharacterCountConfig} CharacterCountConfig
2496
- * @typedef {import('./components/character-count/character-count.mjs').CharacterCountTranslations} CharacterCountTranslations
2497
- * @typedef {import('./components/error-summary/error-summary.mjs').ErrorSummaryConfig} ErrorSummaryConfig
2498
- * @typedef {import('./components/exit-this-page/exit-this-page.mjs').ExitThisPageConfig} ExitThisPageConfig
2499
- * @typedef {import('./components/exit-this-page/exit-this-page.mjs').ExitThisPageTranslations} ExitThisPageTranslations
2500
- * @typedef {import('./components/notification-banner/notification-banner.mjs').NotificationBannerConfig} NotificationBannerConfig
2501
- * @typedef {import('./components/password-input/password-input.mjs').PasswordInputConfig} PasswordInputConfig
2787
+ * @import { AccordionConfig } from './components/accordion/accordion.mjs'
2788
+ * @import { ButtonConfig } from './components/button/button.mjs'
2789
+ * @import { CharacterCountConfig } from './components/character-count/character-count.mjs'
2790
+ * @import { ErrorSummaryConfig } from './components/error-summary/error-summary.mjs'
2791
+ * @import { ExitThisPageConfig } from './components/exit-this-page/exit-this-page.mjs'
2792
+ * @import { NotificationBannerConfig } from './components/notification-banner/notification-banner.mjs'
2793
+ * @import { PasswordInputConfig } from './components/password-input/password-input.mjs'
2794
+ * @import { FileUploadConfig } from './components/file-upload/file-upload.mjs'
2502
2795
  */
2503
2796
  /**
2504
2797
  * Component config keys, e.g. `accordion` and `characterCount`
@@ -2506,24 +2799,28 @@ function createAll(Component, config, createAllOptions) {
2506
2799
  * @typedef {keyof Config} ConfigKey
2507
2800
  */
2508
2801
  /**
2509
- * @template {CompatibleClass} T
2802
+ * @template {CompatibleClass} ComponentClass
2803
+ * @typedef {ConstructorParameters<ComponentClass>[1]} ComponentConfig
2804
+ */
2805
+ /**
2806
+ * @template {CompatibleClass} ComponentClass
2510
2807
  * @typedef {object} ErrorContext
2511
2808
  * @property {Element} [element] - Element used for component module initialisation
2512
- * @property {T} [component] - Class of component
2513
- * @property {T["defaults"]} config - Config supplied to component
2809
+ * @property {ComponentClass} [component] - Class of component
2810
+ * @property {ComponentConfig<ComponentClass>} config - Config supplied to component
2514
2811
  */
2515
2812
  /**
2516
- * @template {CompatibleClass} T
2813
+ * @template {CompatibleClass} ComponentClass
2517
2814
  * @callback OnErrorCallback
2518
2815
  * @param {unknown} error - Thrown error
2519
- * @param {ErrorContext<T>} context - Object containing the element, component class and configuration
2816
+ * @param {ErrorContext<ComponentClass>} context - Object containing the element, component class and configuration
2520
2817
  */
2521
2818
  /**
2522
- * @template {CompatibleClass} T
2819
+ * @template {CompatibleClass} ComponentClass
2523
2820
  * @typedef {object} CreateAllOptions
2524
2821
  * @property {Element | Document} [scope] - scope of the document to search within
2525
- * @property {OnErrorCallback<T>} [onError] - callback function if error throw by component on init
2822
+ * @property {OnErrorCallback<ComponentClass>} [onError] - callback function if error throw by component on init
2526
2823
  */
2527
2824
 
2528
- export { Accordion, Button, CharacterCount, Checkboxes, GOVUKFrontendComponent as Component, ErrorSummary, ExitThisPage, Header, NotificationBanner, PasswordInput, Radios, ServiceNavigation, SkipLink, Tabs, createAll, initAll, isSupported, version };
2825
+ export { Accordion, Button, CharacterCount, Checkboxes, Component, ConfigurableComponent, ErrorSummary, ExitThisPage, FileUpload, Header, NotificationBanner, PasswordInput, Radios, ServiceNavigation, SkipLink, Tabs, createAll, initAll, isSupported, version };
2529
2826
  //# sourceMappingURL=all.bundle.mjs.map