@ministryofjustice/frontend 5.0.0 → 5.1.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 (112) hide show
  1. package/moj/all.bundle.js +1549 -1062
  2. package/moj/all.bundle.js.map +1 -1
  3. package/moj/all.bundle.mjs +1845 -1054
  4. package/moj/all.bundle.mjs.map +1 -1
  5. package/moj/all.mjs +7 -90
  6. package/moj/all.mjs.map +1 -1
  7. package/moj/all.scss +1 -0
  8. package/moj/all.scss.map +1 -1
  9. package/moj/common/index.mjs +57 -0
  10. package/moj/common/index.mjs.map +1 -0
  11. package/moj/common/moj-frontend-version.mjs +14 -0
  12. package/moj/common/moj-frontend-version.mjs.map +1 -0
  13. package/moj/components/add-another/add-another.bundle.js +105 -76
  14. package/moj/components/add-another/add-another.bundle.js.map +1 -1
  15. package/moj/components/add-another/add-another.bundle.mjs +222 -71
  16. package/moj/components/add-another/add-another.bundle.mjs.map +1 -1
  17. package/moj/components/add-another/add-another.mjs +103 -72
  18. package/moj/components/add-another/add-another.mjs.map +1 -1
  19. package/moj/components/alert/alert.bundle.js +115 -191
  20. package/moj/components/alert/alert.bundle.js.map +1 -1
  21. package/moj/components/alert/alert.bundle.mjs +354 -186
  22. package/moj/components/alert/alert.bundle.mjs.map +1 -1
  23. package/moj/components/alert/alert.mjs +55 -140
  24. package/moj/components/alert/alert.mjs.map +1 -1
  25. package/moj/components/button-menu/README.md +3 -1
  26. package/moj/components/button-menu/button-menu.bundle.js +91 -120
  27. package/moj/components/button-menu/button-menu.bundle.js.map +1 -1
  28. package/moj/components/button-menu/button-menu.bundle.mjs +329 -114
  29. package/moj/components/button-menu/button-menu.bundle.mjs.map +1 -1
  30. package/moj/components/button-menu/button-menu.mjs +89 -116
  31. package/moj/components/button-menu/button-menu.mjs.map +1 -1
  32. package/moj/components/date-picker/date-picker.bundle.js +174 -154
  33. package/moj/components/date-picker/date-picker.bundle.js.map +1 -1
  34. package/moj/components/date-picker/date-picker.bundle.mjs +411 -147
  35. package/moj/components/date-picker/date-picker.bundle.mjs.map +1 -1
  36. package/moj/components/date-picker/date-picker.mjs +172 -150
  37. package/moj/components/date-picker/date-picker.mjs.map +1 -1
  38. package/moj/components/filter/template.njk +1 -1
  39. package/moj/components/filter-toggle-button/filter-toggle-button.bundle.js +133 -44
  40. package/moj/components/filter-toggle-button/filter-toggle-button.bundle.js.map +1 -1
  41. package/moj/components/filter-toggle-button/filter-toggle-button.bundle.mjs +374 -41
  42. package/moj/components/filter-toggle-button/filter-toggle-button.bundle.mjs.map +1 -1
  43. package/moj/components/filter-toggle-button/filter-toggle-button.mjs +131 -40
  44. package/moj/components/filter-toggle-button/filter-toggle-button.mjs.map +1 -1
  45. package/moj/components/form-validator/form-validator.bundle.js +159 -69
  46. package/moj/components/form-validator/form-validator.bundle.js.map +1 -1
  47. package/moj/components/form-validator/form-validator.bundle.mjs +399 -65
  48. package/moj/components/form-validator/form-validator.bundle.mjs.map +1 -1
  49. package/moj/components/form-validator/form-validator.mjs +134 -54
  50. package/moj/components/form-validator/form-validator.mjs.map +1 -1
  51. package/moj/components/multi-file-upload/multi-file-upload.bundle.js +291 -117
  52. package/moj/components/multi-file-upload/multi-file-upload.bundle.js.map +1 -1
  53. package/moj/components/multi-file-upload/multi-file-upload.bundle.mjs +527 -109
  54. package/moj/components/multi-file-upload/multi-file-upload.bundle.mjs.map +1 -1
  55. package/moj/components/multi-file-upload/multi-file-upload.mjs +288 -101
  56. package/moj/components/multi-file-upload/multi-file-upload.mjs.map +1 -1
  57. package/moj/components/multi-file-upload/template.njk +1 -1
  58. package/moj/components/multi-select/multi-select.bundle.js +106 -41
  59. package/moj/components/multi-select/multi-select.bundle.js.map +1 -1
  60. package/moj/components/multi-select/multi-select.bundle.mjs +346 -37
  61. package/moj/components/multi-select/multi-select.bundle.mjs.map +1 -1
  62. package/moj/components/multi-select/multi-select.mjs +104 -37
  63. package/moj/components/multi-select/multi-select.mjs.map +1 -1
  64. package/moj/components/password-reveal/_password-reveal.scss +3 -1
  65. package/moj/components/password-reveal/_password-reveal.scss.map +1 -1
  66. package/moj/components/password-reveal/password-reveal.bundle.js +32 -29
  67. package/moj/components/password-reveal/password-reveal.bundle.js.map +1 -1
  68. package/moj/components/password-reveal/password-reveal.bundle.mjs +149 -24
  69. package/moj/components/password-reveal/password-reveal.bundle.mjs.map +1 -1
  70. package/moj/components/password-reveal/password-reveal.mjs +30 -25
  71. package/moj/components/password-reveal/password-reveal.mjs.map +1 -1
  72. package/moj/components/rich-text-editor/README.md +4 -3
  73. package/moj/components/rich-text-editor/rich-text-editor.bundle.js +127 -62
  74. package/moj/components/rich-text-editor/rich-text-editor.bundle.js.map +1 -1
  75. package/moj/components/rich-text-editor/rich-text-editor.bundle.mjs +367 -58
  76. package/moj/components/rich-text-editor/rich-text-editor.bundle.mjs.map +1 -1
  77. package/moj/components/rich-text-editor/rich-text-editor.mjs +125 -58
  78. package/moj/components/rich-text-editor/rich-text-editor.mjs.map +1 -1
  79. package/moj/components/search-toggle/search-toggle.bundle.js +94 -26
  80. package/moj/components/search-toggle/search-toggle.bundle.js.map +1 -1
  81. package/moj/components/search-toggle/search-toggle.bundle.mjs +334 -22
  82. package/moj/components/search-toggle/search-toggle.bundle.mjs.map +1 -1
  83. package/moj/components/search-toggle/search-toggle.mjs +92 -22
  84. package/moj/components/search-toggle/search-toggle.mjs.map +1 -1
  85. package/moj/components/sortable-table/sortable-table.bundle.js +151 -83
  86. package/moj/components/sortable-table/sortable-table.bundle.js.map +1 -1
  87. package/moj/components/sortable-table/sortable-table.bundle.mjs +390 -78
  88. package/moj/components/sortable-table/sortable-table.bundle.mjs.map +1 -1
  89. package/moj/components/sortable-table/sortable-table.mjs +149 -79
  90. package/moj/components/sortable-table/sortable-table.mjs.map +1 -1
  91. package/moj/core/_all.scss +3 -0
  92. package/moj/core/_all.scss.map +1 -0
  93. package/moj/core/_moj-frontend-properties.scss +7 -0
  94. package/moj/core/_moj-frontend-properties.scss.map +1 -0
  95. package/moj/filters/prototype-kit-13-filters.js +4 -3
  96. package/moj/helpers.bundle.js +22 -77
  97. package/moj/helpers.bundle.js.map +1 -1
  98. package/moj/helpers.bundle.mjs +23 -74
  99. package/moj/helpers.bundle.mjs.map +1 -1
  100. package/moj/helpers.mjs +23 -74
  101. package/moj/helpers.mjs.map +1 -1
  102. package/moj/moj-frontend.min.css +1 -1
  103. package/moj/moj-frontend.min.css.map +1 -1
  104. package/moj/moj-frontend.min.js +1 -1
  105. package/moj/moj-frontend.min.js.map +1 -1
  106. package/package.json +1 -1
  107. package/moj/version.bundle.js +0 -12
  108. package/moj/version.bundle.js.map +0 -1
  109. package/moj/version.bundle.mjs +0 -4
  110. package/moj/version.bundle.mjs.map +0 -1
  111. package/moj/version.mjs +0 -4
  112. package/moj/version.mjs.map +0 -1
@@ -1,73 +1,267 @@
1
+ function isInitialised($root, moduleName) {
2
+ return $root instanceof HTMLElement && $root.hasAttribute(`data-${moduleName}-init`);
3
+ }
4
+
1
5
  /**
2
- * Find an elements preceding sibling
6
+ * Checks if GOV.UK Frontend is supported on this page
3
7
  *
4
- * Utility function to find an elements previous sibling matching the provided
5
- * selector.
8
+ * Some browsers will load and run our JavaScript but GOV.UK Frontend
9
+ * won't be supported.
6
10
  *
7
- * @param {Element | null} $element - Element to find siblings for
8
- * @param {string} [selector] - selector for required sibling
11
+ * @param {HTMLElement | null} [$scope] - (internal) `<body>` HTML element checked for browser support
12
+ * @returns {boolean} Whether GOV.UK Frontend is supported on this page
9
13
  */
10
- function getPreviousSibling($element, selector) {
11
- if (!$element || !($element instanceof HTMLElement)) {
12
- return;
14
+ function isSupported($scope = document.body) {
15
+ if (!$scope) {
16
+ return false;
13
17
  }
18
+ return $scope.classList.contains('govuk-frontend-supported');
19
+ }
20
+ function isArray(option) {
21
+ return Array.isArray(option);
22
+ }
23
+ function isObject(option) {
24
+ return !!option && typeof option === 'object' && !isArray(option);
25
+ }
26
+ function formatErrorMessage(Component, message) {
27
+ return `${Component.moduleName}: ${message}`;
28
+ }
14
29
 
15
- // Get the previous sibling element
16
- let $sibling = $element.previousElementSibling;
30
+ class GOVUKFrontendError extends Error {
31
+ constructor(...args) {
32
+ super(...args);
33
+ this.name = 'GOVUKFrontendError';
34
+ }
35
+ }
36
+ class SupportError extends GOVUKFrontendError {
37
+ /**
38
+ * Checks if GOV.UK Frontend is supported on this page
39
+ *
40
+ * @param {HTMLElement | null} [$scope] - HTML element `<body>` checked for browser support
41
+ */
42
+ constructor($scope = document.body) {
43
+ const supportMessage = 'noModule' in HTMLScriptElement.prototype ? 'GOV.UK Frontend initialised without `<body class="govuk-frontend-supported">` from template `<script>` snippet' : 'GOV.UK Frontend is not supported in this browser';
44
+ super($scope ? supportMessage : 'GOV.UK Frontend initialised without `<script type="module">`');
45
+ this.name = 'SupportError';
46
+ }
47
+ }
48
+ class ConfigError extends GOVUKFrontendError {
49
+ constructor(...args) {
50
+ super(...args);
51
+ this.name = 'ConfigError';
52
+ }
53
+ }
54
+ class ElementError extends GOVUKFrontendError {
55
+ constructor(messageOrOptions) {
56
+ let message = typeof messageOrOptions === 'string' ? messageOrOptions : '';
57
+ if (typeof messageOrOptions === 'object') {
58
+ const {
59
+ component,
60
+ identifier,
61
+ element,
62
+ expectedType
63
+ } = messageOrOptions;
64
+ message = identifier;
65
+ message += element ? ` is not of type ${expectedType != null ? expectedType : 'HTMLElement'}` : ' not found';
66
+ message = formatErrorMessage(component, message);
67
+ }
68
+ super(message);
69
+ this.name = 'ElementError';
70
+ }
71
+ }
72
+ class InitError extends GOVUKFrontendError {
73
+ constructor(componentOrMessage) {
74
+ const message = typeof componentOrMessage === 'string' ? componentOrMessage : formatErrorMessage(componentOrMessage, `Root element (\`$root\`) already initialised`);
75
+ super(message);
76
+ this.name = 'InitError';
77
+ }
78
+ }
17
79
 
18
- // If the sibling matches our selector, use it
19
- // If not, jump to the next sibling and continue the loop
20
- while ($sibling) {
21
- if ($sibling.matches(selector)) return $sibling;
22
- $sibling = $sibling.previousElementSibling;
80
+ class Component {
81
+ /**
82
+ * Returns the root element of the component
83
+ *
84
+ * @protected
85
+ * @returns {RootElementType} - the root element of component
86
+ */
87
+ get $root() {
88
+ return this._$root;
89
+ }
90
+ constructor($root) {
91
+ this._$root = void 0;
92
+ const childConstructor = this.constructor;
93
+ if (typeof childConstructor.moduleName !== 'string') {
94
+ throw new InitError(`\`moduleName\` not defined in component`);
95
+ }
96
+ if (!($root instanceof childConstructor.elementType)) {
97
+ throw new ElementError({
98
+ element: $root,
99
+ component: childConstructor,
100
+ identifier: 'Root element (`$root`)',
101
+ expectedType: childConstructor.elementType.name
102
+ });
103
+ } else {
104
+ this._$root = $root;
105
+ }
106
+ childConstructor.checkSupport();
107
+ this.checkInitialised();
108
+ const moduleName = childConstructor.moduleName;
109
+ this.$root.setAttribute(`data-${moduleName}-init`, '');
110
+ }
111
+ checkInitialised() {
112
+ const constructor = this.constructor;
113
+ const moduleName = constructor.moduleName;
114
+ if (moduleName && isInitialised(this.$root, moduleName)) {
115
+ throw new InitError(constructor);
116
+ }
117
+ }
118
+ static checkSupport() {
119
+ if (!isSupported()) {
120
+ throw new SupportError();
121
+ }
23
122
  }
24
123
  }
25
124
 
26
125
  /**
27
- * @param {Element | null} $element
28
- * @param {string} [selector]
126
+ * @typedef ChildClass
127
+ * @property {string} moduleName - The module name that'll be looked for in the DOM when initialising the component
29
128
  */
30
- function findNearestMatchingElement($element, selector) {
31
- // If no element or selector is provided, return
32
- if (!$element || !($element instanceof HTMLElement) || false) {
33
- return;
129
+
130
+ /**
131
+ * @typedef {typeof Component & ChildClass} ChildClassConstructor
132
+ */
133
+ Component.elementType = HTMLElement;
134
+
135
+ const configOverride = Symbol.for('configOverride');
136
+ class ConfigurableComponent extends Component {
137
+ [configOverride](param) {
138
+ return {};
34
139
  }
35
140
 
36
- // Start with the current element
37
- let $currentElement = $element;
38
- while ($currentElement) {
39
- // First check the current element
40
- if ($currentElement.matches(selector)) {
41
- return $currentElement;
141
+ /**
142
+ * Returns the root element of the component
143
+ *
144
+ * @protected
145
+ * @returns {ConfigurationType} - the root element of component
146
+ */
147
+ get config() {
148
+ return this._config;
149
+ }
150
+ constructor($root, config) {
151
+ super($root);
152
+ this._config = void 0;
153
+ const childConstructor = this.constructor;
154
+ if (!isObject(childConstructor.defaults)) {
155
+ throw new ConfigError(formatErrorMessage(childConstructor, 'Config passed as parameter into constructor but no defaults defined'));
42
156
  }
43
-
44
- // Check all previous siblings
45
- let $sibling = $currentElement.previousElementSibling;
46
- while ($sibling) {
47
- // Check if the sibling itself is a heading
48
- if ($sibling.matches(selector)) {
49
- return $sibling;
157
+ const datasetConfig = normaliseDataset(childConstructor, this._$root.dataset);
158
+ this._config = mergeConfigs(childConstructor.defaults, config != null ? config : {}, this[configOverride](datasetConfig), datasetConfig);
159
+ }
160
+ }
161
+ function normaliseString(value, property) {
162
+ const trimmedValue = value ? value.trim() : '';
163
+ let output;
164
+ let outputType = property == null ? void 0 : property.type;
165
+ if (!outputType) {
166
+ if (['true', 'false'].includes(trimmedValue)) {
167
+ outputType = 'boolean';
168
+ }
169
+ if (trimmedValue.length > 0 && isFinite(Number(trimmedValue))) {
170
+ outputType = 'number';
171
+ }
172
+ }
173
+ switch (outputType) {
174
+ case 'boolean':
175
+ output = trimmedValue === 'true';
176
+ break;
177
+ case 'number':
178
+ output = Number(trimmedValue);
179
+ break;
180
+ default:
181
+ output = value;
182
+ }
183
+ return output;
184
+ }
185
+ function normaliseDataset(Component, dataset) {
186
+ if (!isObject(Component.schema)) {
187
+ throw new ConfigError(formatErrorMessage(Component, 'Config passed as parameter into constructor but no schema defined'));
188
+ }
189
+ const out = {};
190
+ const entries = Object.entries(Component.schema.properties);
191
+ for (const entry of entries) {
192
+ const [namespace, property] = entry;
193
+ const field = namespace.toString();
194
+ if (field in dataset) {
195
+ out[field] = normaliseString(dataset[field], property);
196
+ }
197
+ if ((property == null ? void 0 : property.type) === 'object') {
198
+ out[field] = extractConfigByNamespace(Component.schema, dataset, namespace);
199
+ }
200
+ }
201
+ return out;
202
+ }
203
+ function mergeConfigs(...configObjects) {
204
+ const formattedConfigObject = {};
205
+ for (const configObject of configObjects) {
206
+ for (const key of Object.keys(configObject)) {
207
+ const option = formattedConfigObject[key];
208
+ const override = configObject[key];
209
+ if (isObject(option) && isObject(override)) {
210
+ formattedConfigObject[key] = mergeConfigs(option, override);
211
+ } else {
212
+ formattedConfigObject[key] = override;
213
+ }
214
+ }
215
+ }
216
+ return formattedConfigObject;
217
+ }
218
+ function extractConfigByNamespace(schema, dataset, namespace) {
219
+ const property = schema.properties[namespace];
220
+ if ((property == null ? void 0 : property.type) !== 'object') {
221
+ return;
222
+ }
223
+ const newObject = {
224
+ [namespace]: {}
225
+ };
226
+ for (const [key, value] of Object.entries(dataset)) {
227
+ let current = newObject;
228
+ const keyParts = key.split('.');
229
+ for (const [index, name] of keyParts.entries()) {
230
+ if (isObject(current)) {
231
+ if (index < keyParts.length - 1) {
232
+ if (!isObject(current[name])) {
233
+ current[name] = {};
234
+ }
235
+ current = current[name];
236
+ } else if (key !== namespace) {
237
+ current[name] = normaliseString(value);
238
+ }
50
239
  }
51
- $sibling = $sibling.previousElementSibling;
52
240
  }
53
-
54
- // If no match found in siblings, move up to parent
55
- $currentElement = $currentElement.parentElement;
56
241
  }
242
+ return newObject[namespace];
57
243
  }
58
244
 
245
+ /**
246
+ * GOV.UK Frontend helpers
247
+ *
248
+ * @todo Import from GOV.UK Frontend
249
+ */
250
+
59
251
  /**
60
252
  * Move focus to element
61
253
  *
62
254
  * Sets tabindex to -1 to make the element programmatically focusable,
63
255
  * but removes it on blur as the element doesn't need to be focused again.
64
256
  *
65
- * @param {HTMLElement} $element - HTML element
257
+ * @template {HTMLElement} FocusElement
258
+ * @param {FocusElement} $element - HTML element
66
259
  * @param {object} [options] - Handler options
67
- * @param {function(this: HTMLElement): void} [options.onBeforeFocus] - Callback before focus
68
- * @param {function(this: HTMLElement): void} [options.onBlur] - Callback on blur
260
+ * @param {function(this: FocusElement): void} [options.onBeforeFocus] - Callback before focus
261
+ * @param {function(this: FocusElement): void} [options.onBlur] - Callback on blur
69
262
  */
70
263
  function setFocus($element, options = {}) {
264
+ var _options$onBeforeFocu;
71
265
  const isFocusable = $element.getAttribute('tabindex');
72
266
  if (!isFocusable) {
73
267
  $element.setAttribute('tabindex', '-1');
@@ -86,9 +280,8 @@ function setFocus($element, options = {}) {
86
280
  * Handle element blur
87
281
  */
88
282
  function onBlur() {
89
- if (options.onBlur) {
90
- options.onBlur.call($element);
91
- }
283
+ var _options$onBlur;
284
+ (_options$onBlur = options.onBlur) == null || _options$onBlur.call($element);
92
285
  if (!isFocusable) {
93
286
  $element.removeAttribute('tabindex');
94
287
  }
@@ -100,46 +293,84 @@ function setFocus($element, options = {}) {
100
293
  });
101
294
 
102
295
  // Focus element
103
- if (options.onBeforeFocus) {
104
- options.onBeforeFocus.call($element);
105
- }
296
+ (_options$onBeforeFocu = options.onBeforeFocus) == null || _options$onBeforeFocu.call($element);
106
297
  $element.focus();
107
298
  }
108
299
 
109
- class Alert {
110
- /**
111
- * @param {Element | null} $module - HTML element to use for alert
112
- * @param {AlertConfig} [config] - Alert config
113
- */
114
- constructor($module, config = {}) {
115
- if (!$module || !($module instanceof HTMLElement)) {
116
- return this;
300
+ /**
301
+ * @param {Element} $element - Element to remove attribute value from
302
+ * @param {string} attr - Attribute name
303
+ * @param {string} value - Attribute value
304
+ */
305
+
306
+ /**
307
+ * Find an elements preceding sibling
308
+ *
309
+ * Utility function to find an elements previous sibling matching the provided
310
+ * selector.
311
+ *
312
+ * @param {Element | null} $element - Element to find siblings for
313
+ * @param {string} [selector] - selector for required sibling
314
+ */
315
+ function getPreviousSibling($element, selector) {
316
+ if (!$element || !($element instanceof HTMLElement)) {
317
+ return;
318
+ }
319
+
320
+ // Get the previous sibling element
321
+ let $sibling = $element.previousElementSibling;
322
+
323
+ // If the sibling matches our selector, use it
324
+ // If not, jump to the next sibling and continue the loop
325
+ while ($sibling) {
326
+ if ($sibling.matches(selector)) return $sibling;
327
+ $sibling = $sibling.previousElementSibling;
328
+ }
329
+ }
330
+
331
+ /**
332
+ * @param {Element | null} $element
333
+ * @param {string} [selector]
334
+ */
335
+ function findNearestMatchingElement($element, selector) {
336
+ // If no element or selector is provided, return
337
+ if (!$element || !($element instanceof HTMLElement) || false) {
338
+ return;
339
+ }
340
+
341
+ // Start with the current element
342
+ let $currentElement = $element;
343
+ while ($currentElement) {
344
+ // First check the current element
345
+ if ($currentElement.matches(selector)) {
346
+ return $currentElement;
117
347
  }
118
- const schema = Object.freeze({
119
- properties: {
120
- dismissible: {
121
- type: 'boolean'
122
- },
123
- dismissText: {
124
- type: 'string'
125
- },
126
- disableAutoFocus: {
127
- type: 'boolean'
128
- },
129
- focusOnDismissSelector: {
130
- type: 'string'
131
- }
348
+
349
+ // Check all previous siblings
350
+ let $sibling = $currentElement.previousElementSibling;
351
+ while ($sibling) {
352
+ // Check if the sibling itself is a heading
353
+ if ($sibling.matches(selector)) {
354
+ return $sibling;
132
355
  }
133
- });
134
- const defaults = {
135
- dismissible: false,
136
- dismissText: 'Dismiss',
137
- disableAutoFocus: false
138
- };
356
+ $sibling = $sibling.previousElementSibling;
357
+ }
139
358
 
140
- // data attributes override JS config, which overrides defaults
141
- this.config = this.mergeConfigs(defaults, config, this.parseDataset(schema, $module.dataset));
142
- this.$module = $module;
359
+ // If no match found in siblings, move up to parent
360
+ $currentElement = $currentElement.parentElement;
361
+ }
362
+ }
363
+
364
+ /**
365
+ * @augments {ConfigurableComponent<AlertConfig>}
366
+ */
367
+ class Alert extends ConfigurableComponent {
368
+ /**
369
+ * @param {Element | null} $root - HTML element to use for alert
370
+ * @param {AlertConfig} [config] - Alert config
371
+ */
372
+ constructor($root, config = {}) {
373
+ super($root, config);
143
374
 
144
375
  /**
145
376
  * Focus the alert
@@ -152,14 +383,14 @@ class Alert {
152
383
  * do this based on user research findings, or to avoid a clash with another
153
384
  * element which should be focused when the page loads.
154
385
  */
155
- if (this.$module.getAttribute('role') === 'alert' && !this.config.disableAutoFocus) {
156
- setFocus(this.$module);
386
+ if (this.$root.getAttribute('role') === 'alert' && !this.config.disableAutoFocus) {
387
+ setFocus(this.$root);
157
388
  }
158
- this.$dismissButton = this.$module.querySelector('.moj-alert__dismiss');
389
+ this.$dismissButton = this.$root.querySelector('.moj-alert__dismiss');
159
390
  if (this.config.dismissible && this.$dismissButton) {
160
391
  this.$dismissButton.innerHTML = this.config.dismissText;
161
392
  this.$dismissButton.removeAttribute('hidden');
162
- this.$module.addEventListener('click', event => {
393
+ this.$root.addEventListener('click', event => {
163
394
  if (event.target instanceof Node && this.$dismissButton.contains(event.target)) {
164
395
  this.dimiss();
165
396
  }
@@ -180,7 +411,7 @@ class Alert {
180
411
 
181
412
  // Is the next sibling another alert
182
413
  if (!$elementToRecieveFocus) {
183
- const $nextSibling = this.$module.nextElementSibling;
414
+ const $nextSibling = this.$root.nextElementSibling;
184
415
  if ($nextSibling && $nextSibling.matches('.moj-alert')) {
185
416
  $elementToRecieveFocus = $nextSibling;
186
417
  }
@@ -188,13 +419,13 @@ class Alert {
188
419
 
189
420
  // Else try to find any preceding sibling alert or heading
190
421
  if (!$elementToRecieveFocus) {
191
- $elementToRecieveFocus = getPreviousSibling(this.$module, '.moj-alert, h1, h2, h3, h4, h5, h6');
422
+ $elementToRecieveFocus = getPreviousSibling(this.$root, '.moj-alert, h1, h2, h3, h4, h5, h6');
192
423
  }
193
424
 
194
425
  // Else find the closest ancestor heading, or fallback to main, or last resort
195
426
  // use the body element
196
427
  if (!$elementToRecieveFocus) {
197
- $elementToRecieveFocus = findNearestMatchingElement(this.$module, 'h1, h2, h3, h4, h5, h6, main, body');
428
+ $elementToRecieveFocus = findNearestMatchingElement(this.$root, 'h1, h2, h3, h4, h5, h6, main, body');
198
429
  }
199
430
 
200
431
  // If we have an element, place focus on it
@@ -203,111 +434,12 @@ class Alert {
203
434
  }
204
435
 
205
436
  // Remove the alert
206
- this.$module.remove();
207
- }
208
-
209
- /**
210
- * Normalise string
211
- *
212
- * 'If it looks like a duck, and it quacks like a duck…' 🦆
213
- *
214
- * If the passed value looks like a boolean or a number, convert it to a boolean
215
- * or number.
216
- *
217
- * Designed to be used to convert config passed via data attributes (which are
218
- * always strings) into something sensible.
219
- *
220
- * @internal
221
- * @param {DOMStringMap[string]} value - The value to normalise
222
- * @param {SchemaProperty} [property] - Component schema property
223
- * @returns {string | boolean | number | undefined} Normalised data
224
- */
225
- normaliseString(value, property) {
226
- const trimmedValue = value ? value.trim() : '';
227
- let output;
228
- let outputType;
229
- if (property && property.type) {
230
- outputType = property.type;
231
- }
232
-
233
- // No schema type set? Determine automatically
234
- if (!outputType) {
235
- if (['true', 'false'].includes(trimmedValue)) {
236
- outputType = 'boolean';
237
- }
238
-
239
- // Empty / whitespace-only strings are considered finite so we need to check
240
- // the length of the trimmed string as well
241
- if (trimmedValue.length > 0 && Number.isFinite(Number(trimmedValue))) {
242
- outputType = 'number';
243
- }
244
- }
245
- switch (outputType) {
246
- case 'boolean':
247
- output = trimmedValue === 'true';
248
- break;
249
- case 'number':
250
- output = Number(trimmedValue);
251
- break;
252
- default:
253
- output = value;
254
- }
255
- return output;
437
+ this.$root.remove();
256
438
  }
257
439
 
258
440
  /**
259
- * Parse dataset
260
- *
261
- * Loop over an object and normalise each value using {@link normaliseString},
262
- * optionally expanding nested `i18n.field`
263
- *
264
- * @param {Schema} schema - component schema
265
- * @param {DOMStringMap} dataset - HTML element dataset
266
- * @returns {object} Normalised dataset
441
+ * Name for the component used when initialising using data-module attributes.
267
442
  */
268
- parseDataset(schema, dataset) {
269
- const parsed = {};
270
- for (const [field, property] of Object.entries(schema.properties)) {
271
- if (field in dataset) {
272
- if (dataset[field]) {
273
- parsed[field] = this.normaliseString(dataset[field], property);
274
- }
275
- }
276
- }
277
- return parsed;
278
- }
279
-
280
- /**
281
- * Config merging function
282
- *
283
- * Takes any number of objects and combines them together, with
284
- * greatest priority on the LAST item passed in.
285
- *
286
- * @param {...{ [key: string]: unknown }} configObjects - Config objects to merge
287
- * @returns {{ [key: string]: unknown }} A merged config object
288
- */
289
- mergeConfigs(...configObjects) {
290
- const formattedConfigObject = {};
291
-
292
- // Loop through each of the passed objects
293
- for (const configObject of configObjects) {
294
- for (const key of Object.keys(configObject)) {
295
- const option = formattedConfigObject[key];
296
- const override = configObject[key];
297
-
298
- // Push their keys one-by-one into formattedConfigObject. Any duplicate
299
- // keys with object values will be merged, otherwise the new value will
300
- // override the existing value.
301
- if (typeof option === 'object' && typeof override === 'object') {
302
- // @ts-expect-error Index signature for type 'string' is missing
303
- formattedConfigObject[key] = this.mergeConfigs(option, override);
304
- } else {
305
- formattedConfigObject[key] = override;
306
- }
307
- }
308
- }
309
- return formattedConfigObject;
310
- }
311
443
  }
312
444
 
313
445
  /**
@@ -318,5 +450,41 @@ class Alert {
318
450
  * @property {string} [focusOnDismissSelector] - CSS Selector for element to be focused on dismiss
319
451
  */
320
452
 
453
+ /**
454
+ * @import { Schema } from 'govuk-frontend/dist/govuk/common/configuration.mjs'
455
+ */
456
+ Alert.moduleName = 'moj-alert';
457
+ /**
458
+ * Alert default config
459
+ *
460
+ * @type {AlertConfig}
461
+ */
462
+ Alert.defaults = Object.freeze({
463
+ dismissible: false,
464
+ dismissText: 'Dismiss',
465
+ disableAutoFocus: false
466
+ });
467
+ /**
468
+ * Alert config schema
469
+ *
470
+ * @satisfies {Schema<AlertConfig>}
471
+ */
472
+ Alert.schema = Object.freeze(/** @type {const} */{
473
+ properties: {
474
+ dismissible: {
475
+ type: 'boolean'
476
+ },
477
+ dismissText: {
478
+ type: 'string'
479
+ },
480
+ disableAutoFocus: {
481
+ type: 'boolean'
482
+ },
483
+ focusOnDismissSelector: {
484
+ type: 'string'
485
+ }
486
+ }
487
+ });
488
+
321
489
  export { Alert };
322
490
  //# sourceMappingURL=alert.bundle.mjs.map