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