@ministryofjustice/frontend 5.0.0 → 5.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/moj/all.bundle.js +1598 -1062
  2. package/moj/all.bundle.js.map +1 -1
  3. package/moj/all.bundle.mjs +1894 -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.scss +3 -42
  86. package/moj/components/sortable-table/_sortable-table.scss.map +1 -1
  87. package/moj/components/sortable-table/sortable-table.bundle.js +200 -83
  88. package/moj/components/sortable-table/sortable-table.bundle.js.map +1 -1
  89. package/moj/components/sortable-table/sortable-table.bundle.mjs +439 -78
  90. package/moj/components/sortable-table/sortable-table.bundle.mjs.map +1 -1
  91. package/moj/components/sortable-table/sortable-table.mjs +198 -79
  92. package/moj/components/sortable-table/sortable-table.mjs.map +1 -1
  93. package/moj/core/_all.scss +3 -0
  94. package/moj/core/_all.scss.map +1 -0
  95. package/moj/core/_moj-frontend-properties.scss +7 -0
  96. package/moj/core/_moj-frontend-properties.scss.map +1 -0
  97. package/moj/filters/prototype-kit-13-filters.js +4 -3
  98. package/moj/helpers.bundle.js +22 -77
  99. package/moj/helpers.bundle.js.map +1 -1
  100. package/moj/helpers.bundle.mjs +23 -74
  101. package/moj/helpers.bundle.mjs.map +1 -1
  102. package/moj/helpers.mjs +23 -74
  103. package/moj/helpers.mjs.map +1 -1
  104. package/moj/moj-frontend.min.css +1 -1
  105. package/moj/moj-frontend.min.css.map +1 -1
  106. package/moj/moj-frontend.min.js +1 -1
  107. package/moj/moj-frontend.min.js.map +1 -1
  108. package/package.json +1 -1
  109. package/moj/version.bundle.js +0 -12
  110. package/moj/version.bundle.js.map +0 -1
  111. package/moj/version.bundle.mjs +0 -4
  112. package/moj/version.bundle.mjs.map +0 -1
  113. package/moj/version.mjs +0 -4
  114. package/moj/version.mjs.map +0 -1
@@ -1,68 +1,292 @@
1
- class ButtonMenu {
1
+ function isInitialised($root, moduleName) {
2
+ return $root instanceof HTMLElement && $root.hasAttribute(`data-${moduleName}-init`);
3
+ }
4
+
5
+ /**
6
+ * Checks if GOV.UK Frontend is supported on this page
7
+ *
8
+ * Some browsers will load and run our JavaScript but GOV.UK Frontend
9
+ * won't be supported.
10
+ *
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
13
+ */
14
+ function isSupported($scope = document.body) {
15
+ if (!$scope) {
16
+ return false;
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
+ }
29
+
30
+ class GOVUKFrontendError extends Error {
31
+ constructor(...args) {
32
+ super(...args);
33
+ this.name = 'GOVUKFrontendError';
34
+ }
35
+ }
36
+ class SupportError extends GOVUKFrontendError {
2
37
  /**
3
- * @param {Element | null} $module - HTML element to use for button menu
4
- * @param {ButtonMenuConfig} [config] - Button menu config
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
+ }
79
+
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
+ }
122
+ }
123
+ }
124
+
125
+ /**
126
+ * @typedef ChildClass
127
+ * @property {string} moduleName - The module name that'll be looked for in the DOM when initialising the component
128
+ */
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 {};
139
+ }
140
+
141
+ /**
142
+ * Returns the root element of the component
143
+ *
144
+ * @protected
145
+ * @returns {ConfigurationType} - the root element of component
5
146
  */
6
- constructor($module, config = {}) {
7
- if (!$module || !($module instanceof HTMLElement)) {
8
- return this;
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'));
9
156
  }
10
- const schema = Object.freeze({
11
- properties: {
12
- buttonText: {
13
- type: 'string'
14
- },
15
- buttonClasses: {
16
- type: 'string'
17
- },
18
- alignMenu: {
19
- type: 'string'
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);
20
238
  }
21
239
  }
22
- });
23
- const defaults = {
24
- buttonText: 'Actions',
25
- alignMenu: 'left',
26
- buttonClasses: ''
27
- };
28
- // data attributes override JS config, which overrides defaults
29
- this.config = this.mergeConfigs(defaults, config, this.parseDataset(schema, $module.dataset));
30
- this.$module = $module;
240
+ }
241
+ }
242
+ return newObject[namespace];
243
+ }
244
+
245
+ /**
246
+ * @augments {ConfigurableComponent<ButtonMenuConfig>}
247
+ */
248
+ class ButtonMenu extends ConfigurableComponent {
249
+ /**
250
+ * @param {Element | null} $root - HTML element to use for button menu
251
+ * @param {ButtonMenuConfig} [config] - Button menu config
252
+ */
253
+ constructor($root, config = {}) {
254
+ super($root, config);
31
255
 
32
256
  // If only one button is provided, don't initiate a menu and toggle button
33
257
  // if classes have been provided for the toggleButton, apply them to the single item
34
- if (this.$module.children.length === 1) {
35
- const button = this.$module.children[0];
36
- button.classList.forEach(className => {
258
+ if (this.$root.children.length === 1) {
259
+ const $button = this.$root.children[0];
260
+ $button.classList.forEach(className => {
37
261
  if (className.startsWith('govuk-button-')) {
38
- button.classList.remove(className);
262
+ $button.classList.remove(className);
39
263
  }
40
- button.classList.remove('moj-button-menu__item');
41
- button.classList.add('moj-button-menu__single-button');
264
+ $button.classList.remove('moj-button-menu__item');
265
+ $button.classList.add('moj-button-menu__single-button');
42
266
  });
43
267
  if (this.config.buttonClasses) {
44
- button.classList.add(...this.config.buttonClasses.split(' '));
268
+ $button.classList.add(...this.config.buttonClasses.split(' '));
45
269
  }
46
270
  }
47
- // Otherwise intialise a button menu
48
- if (this.$module.children.length > 1) {
271
+ // Otherwise initialise a button menu
272
+ if (this.$root.children.length > 1) {
49
273
  this.initMenu();
50
274
  }
51
275
  }
52
276
  initMenu() {
53
277
  this.$menu = this.createMenu();
54
- this.$module.insertAdjacentHTML('afterbegin', this.toggleTemplate());
278
+ this.$root.insertAdjacentHTML('afterbegin', this.toggleTemplate());
55
279
  this.setupMenuItems();
56
- this.$menuToggle = this.$module.querySelector(':scope > button');
57
- this.items = this.$menu.querySelectorAll('a, button');
280
+ this.$menuToggle = this.$root.querySelector(':scope > button');
281
+ this.$items = this.$menu.querySelectorAll('a, button');
58
282
  this.$menuToggle.addEventListener('click', event => {
59
283
  this.toggleMenu(event);
60
284
  });
61
- this.$module.addEventListener('keydown', event => {
285
+ this.$root.addEventListener('keydown', event => {
62
286
  this.handleKeyDown(event);
63
287
  });
64
288
  document.addEventListener('click', event => {
65
- if (!this.$module.contains(event.target)) {
289
+ if (event.target instanceof Node && !this.$root.contains(event.target)) {
66
290
  this.closeMenu(false);
67
291
  }
68
292
  });
@@ -75,30 +299,30 @@ class ButtonMenu {
75
299
  if (this.config.alignMenu === 'right') {
76
300
  $menu.classList.add('moj-button-menu__wrapper--right');
77
301
  }
78
- this.$module.appendChild($menu);
79
- while (this.$module.firstChild !== $menu) {
80
- $menu.appendChild(this.$module.firstChild);
302
+ this.$root.appendChild($menu);
303
+ while (this.$root.firstChild !== $menu) {
304
+ $menu.appendChild(this.$root.firstChild);
81
305
  }
82
306
  return $menu;
83
307
  }
84
308
  setupMenuItems() {
85
- Array.from(this.$menu.children).forEach(item => {
309
+ Array.from(this.$menu.children).forEach($menuItem => {
86
310
  // wrap item in li tag
87
- const listItem = document.createElement('li');
88
- this.$menu.insertBefore(listItem, item);
89
- listItem.appendChild(item);
90
- item.setAttribute('tabindex', '-1');
91
- if (item.tagName === 'BUTTON') {
92
- item.setAttribute('type', 'button');
311
+ const $listItem = document.createElement('li');
312
+ this.$menu.insertBefore($listItem, $menuItem);
313
+ $listItem.appendChild($menuItem);
314
+ $menuItem.setAttribute('tabindex', '-1');
315
+ if ($menuItem.tagName === 'BUTTON') {
316
+ $menuItem.setAttribute('type', 'button');
93
317
  }
94
- item.classList.forEach(className => {
318
+ $menuItem.classList.forEach(className => {
95
319
  if (className.startsWith('govuk-button')) {
96
- item.classList.remove(className);
320
+ $menuItem.classList.remove(className);
97
321
  }
98
322
  });
99
323
 
100
324
  // add a slight delay after click before closing the menu, makes it *feel* better
101
- item.addEventListener('click', () => {
325
+ $menuItem.addEventListener('click', () => {
102
326
  setTimeout(() => {
103
327
  this.closeMenu(false);
104
328
  }, 50);
@@ -123,6 +347,10 @@ class ButtonMenu {
123
347
  isOpen() {
124
348
  return this.$menuToggle.getAttribute('aria-expanded') === 'true';
125
349
  }
350
+
351
+ /**
352
+ * @param {MouseEvent} event - Click event
353
+ */
126
354
  toggleMenu(event) {
127
355
  event.preventDefault();
128
356
 
@@ -168,18 +396,22 @@ class ButtonMenu {
168
396
  * @param {number} index - the index of the item to focus
169
397
  */
170
398
  focusItem(index) {
171
- if (index >= this.items.length) index = 0;
172
- if (index < 0) index = this.items.length - 1;
173
- const menuItem = this.items.item(index);
174
- if (menuItem) {
175
- menuItem.focus();
399
+ if (index >= this.$items.length) index = 0;
400
+ if (index < 0) index = this.$items.length - 1;
401
+ const $menuItem = this.$items.item(index);
402
+ if ($menuItem) {
403
+ $menuItem.focus();
176
404
  }
177
405
  }
178
406
  currentFocusIndex() {
179
- const activeElement = document.activeElement;
180
- const menuItems = Array.from(this.items);
181
- return menuItems.indexOf(activeElement);
407
+ const $activeElement = document.activeElement;
408
+ const $menuItems = Array.from(this.$items);
409
+ return ($activeElement instanceof HTMLAnchorElement || $activeElement instanceof HTMLButtonElement) && $menuItems.indexOf($activeElement);
182
410
  }
411
+
412
+ /**
413
+ * @param {KeyboardEvent} event - Keydown event
414
+ */
183
415
  handleKeyDown(event) {
184
416
  if (event.target === this.$menuToggle) {
185
417
  switch (event.key) {
@@ -189,11 +421,11 @@ class ButtonMenu {
189
421
  break;
190
422
  case 'ArrowUp':
191
423
  event.preventDefault();
192
- this.openMenu(this.items.length - 1);
424
+ this.openMenu(this.$items.length - 1);
193
425
  break;
194
426
  }
195
427
  }
196
- if (this.$menu.contains(event.target) && this.isOpen()) {
428
+ if (event.target instanceof Node && this.$menu.contains(event.target) && this.isOpen()) {
197
429
  switch (event.key) {
198
430
  case 'ArrowDown':
199
431
  event.preventDefault();
@@ -213,7 +445,7 @@ class ButtonMenu {
213
445
  break;
214
446
  case 'End':
215
447
  event.preventDefault();
216
- this.focusItem(this.items.length - 1);
448
+ this.focusItem(this.$items.length - 1);
217
449
  break;
218
450
  }
219
451
  }
@@ -226,58 +458,8 @@ class ButtonMenu {
226
458
  }
227
459
 
228
460
  /**
229
- * Parse dataset
230
- *
231
- * Loop over an object and normalise each value using {@link normaliseString},
232
- * optionally expanding nested `i18n.field`
233
- *
234
- * @param {Schema} schema - component schema
235
- * @param {DOMStringMap} dataset - HTML element dataset
236
- * @returns {object} Normalised dataset
237
- */
238
- parseDataset(schema, dataset) {
239
- const parsed = {};
240
- for (const [field,,] of Object.entries(schema.properties)) {
241
- if (field in dataset) {
242
- if (dataset[field]) {
243
- parsed[field] = dataset[field];
244
- }
245
- }
246
- }
247
- return parsed;
248
- }
249
-
250
- /**
251
- * Config merging function
252
- *
253
- * Takes any number of objects and combines them together, with
254
- * greatest priority on the LAST item passed in.
255
- *
256
- * @param {...{ [key: string]: unknown }} configObjects - Config objects to merge
257
- * @returns {{ [key: string]: unknown }} A merged config object
461
+ * Name for the component used when initialising using data-module attributes.
258
462
  */
259
- mergeConfigs(...configObjects) {
260
- const formattedConfigObject = {};
261
-
262
- // Loop through each of the passed objects
263
- for (const configObject of configObjects) {
264
- for (const key of Object.keys(configObject)) {
265
- const option = formattedConfigObject[key];
266
- const override = configObject[key];
267
-
268
- // Push their keys one-by-one into formattedConfigObject. Any duplicate
269
- // keys with object values will be merged, otherwise the new value will
270
- // override the existing value.
271
- if (typeof option === 'object' && typeof override === 'object') {
272
- // @ts-expect-error Index signature for type 'string' is missing
273
- formattedConfigObject[key] = this.mergeConfigs(option, override);
274
- } else {
275
- formattedConfigObject[key] = override;
276
- }
277
- }
278
- }
279
- return formattedConfigObject;
280
- }
281
463
  }
282
464
 
283
465
  /**
@@ -287,5 +469,38 @@ class ButtonMenu {
287
469
  * @property {string} [buttonClasses='govuk-button--secondary'] - css classes applied to the toggle button
288
470
  */
289
471
 
472
+ /**
473
+ * @import { Schema } from 'govuk-frontend/dist/govuk/common/configuration.mjs'
474
+ */
475
+ ButtonMenu.moduleName = 'moj-button-menu';
476
+ /**
477
+ * Button menu config
478
+ *
479
+ * @type {ButtonMenuConfig}
480
+ */
481
+ ButtonMenu.defaults = Object.freeze({
482
+ buttonText: 'Actions',
483
+ alignMenu: 'left',
484
+ buttonClasses: ''
485
+ });
486
+ /**
487
+ * Button menu config schema
488
+ *
489
+ * @type {Schema<ButtonMenuConfig>}
490
+ */
491
+ ButtonMenu.schema = Object.freeze(/** @type {const} */{
492
+ properties: {
493
+ buttonText: {
494
+ type: 'string'
495
+ },
496
+ buttonClasses: {
497
+ type: 'string'
498
+ },
499
+ alignMenu: {
500
+ type: 'string'
501
+ }
502
+ }
503
+ });
504
+
290
505
  export { ButtonMenu };
291
506
  //# sourceMappingURL=button-menu.bundle.mjs.map