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
@@ -5,6 +5,7 @@ export { CharacterCount } from './components/character-count/character-count.mjs
5
5
  export { Checkboxes } from './components/checkboxes/checkboxes.mjs';
6
6
  export { ErrorSummary } from './components/error-summary/error-summary.mjs';
7
7
  export { ExitThisPage } from './components/exit-this-page/exit-this-page.mjs';
8
+ export { FileUpload } from './components/file-upload/file-upload.mjs';
8
9
  export { Header } from './components/header/header.mjs';
9
10
  export { NotificationBanner } from './components/notification-banner/notification-banner.mjs';
10
11
  export { PasswordInput } from './components/password-input/password-input.mjs';
@@ -14,5 +15,6 @@ export { SkipLink } from './components/skip-link/skip-link.mjs';
14
15
  export { Tabs } from './components/tabs/tabs.mjs';
15
16
  export { createAll, initAll } from './init.mjs';
16
17
  export { isSupported } from './common/index.mjs';
17
- export { GOVUKFrontendComponent as Component } from './govuk-frontend-component.mjs';
18
+ export { Component } from './component.mjs';
19
+ export { ConfigurableComponent } from './common/configuration.mjs';
18
20
  //# sourceMappingURL=all.mjs.map
@@ -1,3 +1,9 @@
1
1
  @import "index";
2
2
 
3
+ @include _warning(
4
+ "import-using-all",
5
+ "Importing using 'govuk/all' is deprecated. Update your import statement to import 'govuk/index'.",
6
+ $silence-further-warnings: false
7
+ );
8
+
3
9
  /*# sourceMappingURL=all.scss.map */
@@ -0,0 +1,169 @@
1
+ import { Component } from '../component.mjs';
2
+ import { ConfigError } from '../errors/index.mjs';
3
+ import { isObject, formatErrorMessage } from './index.mjs';
4
+
5
+ const configOverride = Symbol.for('configOverride');
6
+ class ConfigurableComponent extends Component {
7
+ [configOverride](param) {
8
+ return {};
9
+ }
10
+
11
+ /**
12
+ * Returns the root element of the component
13
+ *
14
+ * @protected
15
+ * @returns {ConfigurationType} - the root element of component
16
+ */
17
+ get config() {
18
+ return this._config;
19
+ }
20
+ constructor($root, config) {
21
+ super($root);
22
+ this._config = void 0;
23
+ const childConstructor = this.constructor;
24
+ if (!isObject(childConstructor.defaults)) {
25
+ throw new ConfigError(formatErrorMessage(childConstructor, 'Config passed as parameter into constructor but no defaults defined'));
26
+ }
27
+ const datasetConfig = normaliseDataset(childConstructor, this._$root.dataset);
28
+ this._config = mergeConfigs(childConstructor.defaults, config != null ? config : {}, this[configOverride](datasetConfig), datasetConfig);
29
+ }
30
+ }
31
+ function normaliseString(value, property) {
32
+ const trimmedValue = value ? value.trim() : '';
33
+ let output;
34
+ let outputType = property == null ? void 0 : property.type;
35
+ if (!outputType) {
36
+ if (['true', 'false'].includes(trimmedValue)) {
37
+ outputType = 'boolean';
38
+ }
39
+ if (trimmedValue.length > 0 && isFinite(Number(trimmedValue))) {
40
+ outputType = 'number';
41
+ }
42
+ }
43
+ switch (outputType) {
44
+ case 'boolean':
45
+ output = trimmedValue === 'true';
46
+ break;
47
+ case 'number':
48
+ output = Number(trimmedValue);
49
+ break;
50
+ default:
51
+ output = value;
52
+ }
53
+ return output;
54
+ }
55
+ function normaliseDataset(Component, dataset) {
56
+ if (!isObject(Component.schema)) {
57
+ throw new ConfigError(formatErrorMessage(Component, 'Config passed as parameter into constructor but no schema defined'));
58
+ }
59
+ const out = {};
60
+ const entries = Object.entries(Component.schema.properties);
61
+ for (const entry of entries) {
62
+ const [namespace, property] = entry;
63
+ const field = namespace.toString();
64
+ if (field in dataset) {
65
+ out[field] = normaliseString(dataset[field], property);
66
+ }
67
+ if ((property == null ? void 0 : property.type) === 'object') {
68
+ out[field] = extractConfigByNamespace(Component.schema, dataset, namespace);
69
+ }
70
+ }
71
+ return out;
72
+ }
73
+ function mergeConfigs(...configObjects) {
74
+ const formattedConfigObject = {};
75
+ for (const configObject of configObjects) {
76
+ for (const key of Object.keys(configObject)) {
77
+ const option = formattedConfigObject[key];
78
+ const override = configObject[key];
79
+ if (isObject(option) && isObject(override)) {
80
+ formattedConfigObject[key] = mergeConfigs(option, override);
81
+ } else {
82
+ formattedConfigObject[key] = override;
83
+ }
84
+ }
85
+ }
86
+ return formattedConfigObject;
87
+ }
88
+ function validateConfig(schema, config) {
89
+ const validationErrors = [];
90
+ for (const [name, conditions] of Object.entries(schema)) {
91
+ const errors = [];
92
+ if (Array.isArray(conditions)) {
93
+ for (const {
94
+ required,
95
+ errorMessage
96
+ } of conditions) {
97
+ if (!required.every(key => !!config[key])) {
98
+ errors.push(errorMessage);
99
+ }
100
+ }
101
+ if (name === 'anyOf' && !(conditions.length - errors.length >= 1)) {
102
+ validationErrors.push(...errors);
103
+ }
104
+ }
105
+ }
106
+ return validationErrors;
107
+ }
108
+ function extractConfigByNamespace(schema, dataset, namespace) {
109
+ const property = schema.properties[namespace];
110
+ if ((property == null ? void 0 : property.type) !== 'object') {
111
+ return;
112
+ }
113
+ const newObject = {
114
+ [namespace]: {}
115
+ };
116
+ for (const [key, value] of Object.entries(dataset)) {
117
+ let current = newObject;
118
+ const keyParts = key.split('.');
119
+ for (const [index, name] of keyParts.entries()) {
120
+ if (isObject(current)) {
121
+ if (index < keyParts.length - 1) {
122
+ if (!isObject(current[name])) {
123
+ current[name] = {};
124
+ }
125
+ current = current[name];
126
+ } else if (key !== namespace) {
127
+ current[name] = normaliseString(value);
128
+ }
129
+ }
130
+ }
131
+ }
132
+ return newObject[namespace];
133
+ }
134
+ /**
135
+ * Schema for component config
136
+ *
137
+ * @template {Partial<Record<keyof ConfigurationType, unknown>>} ConfigurationType
138
+ * @typedef {object} Schema
139
+ * @property {Record<keyof ConfigurationType, SchemaProperty | undefined>} properties - Schema properties
140
+ * @property {SchemaCondition<ConfigurationType>[]} [anyOf] - List of schema conditions
141
+ */
142
+ /**
143
+ * Schema property for component config
144
+ *
145
+ * @typedef {object} SchemaProperty
146
+ * @property {'string' | 'boolean' | 'number' | 'object'} type - Property type
147
+ */
148
+ /**
149
+ * Schema condition for component config
150
+ *
151
+ * @template {Partial<Record<keyof ConfigurationType, unknown>>} ConfigurationType
152
+ * @typedef {object} SchemaCondition
153
+ * @property {(keyof ConfigurationType)[]} required - List of required config fields
154
+ * @property {string} errorMessage - Error message when required config fields not provided
155
+ */
156
+ /**
157
+ * @template {Partial<Record<keyof ConfigurationType, unknown>>} [ConfigurationType=ObjectNested]
158
+ * @typedef ChildClass
159
+ * @property {string} moduleName - The module name that'll be looked for in the DOM when initialising the component
160
+ * @property {Schema<ConfigurationType>} [schema] - The schema of the component configuration
161
+ * @property {ConfigurationType} [defaults] - The default values of the configuration of the component
162
+ */
163
+ /**
164
+ * @template {Partial<Record<keyof ConfigurationType, unknown>>} [ConfigurationType=ObjectNested]
165
+ * @typedef {typeof Component & ChildClass<ConfigurationType>} ChildClassConstructor<ConfigurationType>
166
+ */
167
+
168
+ export { ConfigurableComponent, configOverride, extractConfigByNamespace, mergeConfigs, normaliseDataset, normaliseString, validateConfig };
169
+ //# sourceMappingURL=configuration.mjs.map
@@ -1,4 +1,4 @@
1
- const version = '5.7.1';
1
+ const version = '5.9.0';
2
2
 
3
3
  export { version };
4
4
  //# sourceMappingURL=govuk-frontend-version.mjs.map
@@ -1,46 +1,3 @@
1
- import { normaliseString } from './normalise-string.mjs';
2
-
3
- function mergeConfigs(...configObjects) {
4
- const formattedConfigObject = {};
5
- for (const configObject of configObjects) {
6
- for (const key of Object.keys(configObject)) {
7
- const option = formattedConfigObject[key];
8
- const override = configObject[key];
9
- if (isObject(option) && isObject(override)) {
10
- formattedConfigObject[key] = mergeConfigs(option, override);
11
- } else {
12
- formattedConfigObject[key] = override;
13
- }
14
- }
15
- }
16
- return formattedConfigObject;
17
- }
18
- function extractConfigByNamespace(Component, dataset, namespace) {
19
- const property = Component.schema.properties[namespace];
20
- if ((property == null ? void 0 : property.type) !== 'object') {
21
- return;
22
- }
23
- const newObject = {
24
- [namespace]: ({})
25
- };
26
- for (const [key, value] of Object.entries(dataset)) {
27
- let current = newObject;
28
- const keyParts = key.split('.');
29
- for (const [index, name] of keyParts.entries()) {
30
- if (typeof current === 'object') {
31
- if (index < keyParts.length - 1) {
32
- if (!isObject(current[name])) {
33
- current[name] = {};
34
- }
35
- current = current[name];
36
- } else if (key !== namespace) {
37
- current[name] = normaliseString(value);
38
- }
39
- }
40
- }
41
- }
42
- return newObject[namespace];
43
- }
44
1
  function getFragmentFromUrl(url) {
45
2
  if (!url.includes('#')) {
46
3
  return undefined;
@@ -98,26 +55,6 @@ function isSupported($scope = document.body) {
98
55
  }
99
56
  return $scope.classList.contains('govuk-frontend-supported');
100
57
  }
101
- function validateConfig(schema, config) {
102
- const validationErrors = [];
103
- for (const [name, conditions] of Object.entries(schema)) {
104
- const errors = [];
105
- if (Array.isArray(conditions)) {
106
- for (const {
107
- required,
108
- errorMessage
109
- } of conditions) {
110
- if (!required.every(key => !!config[key])) {
111
- errors.push(errorMessage);
112
- }
113
- }
114
- if (name === 'anyOf' && !(conditions.length - errors.length >= 1)) {
115
- validationErrors.push(...errors);
116
- }
117
- }
118
- }
119
- return validationErrors;
120
- }
121
58
  function isArray(option) {
122
59
  return Array.isArray(option);
123
60
  }
@@ -127,33 +64,13 @@ function isObject(option) {
127
64
  function formatErrorMessage(Component, message) {
128
65
  return `${Component.moduleName}: ${message}`;
129
66
  }
130
-
131
- /**
132
- * Schema for component config
133
- *
134
- * @typedef {object} Schema
135
- * @property {{ [field: string]: SchemaProperty | undefined }} properties - Schema properties
136
- * @property {SchemaCondition[]} [anyOf] - List of schema conditions
137
- */
138
-
139
- /**
140
- * Schema property for component config
141
- *
142
- * @typedef {object} SchemaProperty
143
- * @property {'string' | 'boolean' | 'number' | 'object'} type - Property type
144
- */
145
-
146
- /**
147
- * Schema condition for component config
148
- *
149
- * @typedef {object} SchemaCondition
150
- * @property {string[]} required - List of required config fields
151
- * @property {string} errorMessage - Error message when required config fields not provided
152
- */
153
67
  /**
154
68
  * @typedef ComponentWithModuleName
155
69
  * @property {string} moduleName - Name of the component
156
70
  */
71
+ /**
72
+ * @import { ObjectNested } from './configuration.mjs'
73
+ */
157
74
 
158
- export { extractConfigByNamespace, formatErrorMessage, getBreakpoint, getFragmentFromUrl, isInitialised, isSupported, mergeConfigs, setFocus, validateConfig };
75
+ export { formatErrorMessage, getBreakpoint, getFragmentFromUrl, isInitialised, isObject, isSupported, setFocus };
159
76
  //# sourceMappingURL=index.mjs.map
@@ -1,7 +1,7 @@
1
1
  import { isInitialised, isSupported } from './common/index.mjs';
2
2
  import { InitError, ElementError, SupportError } from './errors/index.mjs';
3
3
 
4
- class GOVUKFrontendComponent {
4
+ class Component {
5
5
  /**
6
6
  * Returns the root element of the component
7
7
  *
@@ -52,9 +52,9 @@ class GOVUKFrontendComponent {
52
52
  */
53
53
 
54
54
  /**
55
- * @typedef {typeof GOVUKFrontendComponent & ChildClass} ChildClassConstructor
55
+ * @typedef {typeof Component & ChildClass} ChildClassConstructor
56
56
  */
57
- GOVUKFrontendComponent.elementType = HTMLElement;
57
+ Component.elementType = HTMLElement;
58
58
 
59
- export { GOVUKFrontendComponent };
60
- //# sourceMappingURL=govuk-frontend-component.mjs.map
59
+ export { Component };
60
+ //# sourceMappingURL=component.mjs.map
@@ -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 = {}) {
@@ -451,15 +497,15 @@
451
497
  * attribute, which also provides accessibility.
452
498
  *
453
499
  * @preserve
500
+ * @augments ConfigurableComponent<AccordionConfig>
454
501
  */
455
- class Accordion extends GOVUKFrontendComponent {
502
+ class Accordion extends ConfigurableComponent {
456
503
  /**
457
504
  * @param {Element | null} $root - HTML element to use for accordion
458
505
  * @param {AccordionConfig} [config] - Accordion config
459
506
  */
460
507
  constructor($root, config = {}) {
461
- super($root);
462
- this.config = void 0;
508
+ super($root, config);
463
509
  this.i18n = void 0;
464
510
  this.controlsClass = 'govuk-accordion__controls';
465
511
  this.showAllClass = 'govuk-accordion__show-all';
@@ -484,7 +530,6 @@
484
530
  this.$showAllButton = null;
485
531
  this.$showAllIcon = null;
486
532
  this.$showAllText = null;
487
- this.config = mergeConfigs(Accordion.defaults, config, normaliseDataset(Accordion, this.$root.dataset));
488
533
  this.i18n = new I18n(this.config.i18n);
489
534
  const $sections = this.$root.querySelectorAll(`.${this.sectionClass}`);
490
535
  if (!$sections.length) {
@@ -754,7 +799,7 @@
754
799
  */
755
800
 
756
801
  /**
757
- * @typedef {import('../../common/index.mjs').Schema} Schema
802
+ * @import { Schema } from '../../common/configuration.mjs'
758
803
  */
759
804
  Accordion.moduleName = 'govuk-accordion';
760
805
  Accordion.defaults = Object.freeze({