@ministryofjustice/frontend 3.3.1 → 3.5.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 (83) hide show
  1. package/README.md +4 -10
  2. package/govuk-prototype-kit.config.json +5 -16
  3. package/moj/all.jquery.min.js +15 -4
  4. package/moj/all.js +2856 -2280
  5. package/moj/all.scss +2 -0
  6. package/moj/components/_all.scss +1 -0
  7. package/moj/components/action-bar/_action-bar.scss +4 -6
  8. package/moj/components/add-another/_add-another.scss +9 -7
  9. package/moj/components/add-another/add-another.js +128 -76
  10. package/moj/components/alert/README.md +0 -0
  11. package/moj/components/alert/_alert.scss +142 -0
  12. package/moj/components/alert/alert.js +482 -0
  13. package/moj/components/alert/alert.spec.helper.js +92 -0
  14. package/moj/components/alert/macro.njk +3 -0
  15. package/moj/components/alert/template.njk +83 -0
  16. package/moj/components/badge/_badge.scss +3 -4
  17. package/moj/components/banner/_banner.scss +5 -10
  18. package/moj/components/button-menu/_button-menu.scss +10 -9
  19. package/moj/components/button-menu/button-menu.js +348 -318
  20. package/moj/components/cookie-banner/_cookie-banner.scss +6 -5
  21. package/moj/components/currency-input/_currency-input.scss +4 -4
  22. package/moj/components/date-picker/README.md +14 -17
  23. package/moj/components/date-picker/_date-picker.scss +122 -106
  24. package/moj/components/date-picker/date-picker.js +927 -900
  25. package/moj/components/filter/README.md +1 -1
  26. package/moj/components/filter/_filter.scss +53 -75
  27. package/moj/components/filter-toggle-button/filter-toggle-button.js +122 -87
  28. package/moj/components/form-validator/form-validator.js +399 -156
  29. package/moj/components/header/_header.scss +17 -19
  30. package/moj/components/identity-bar/_identity-bar.scss +5 -5
  31. package/moj/components/interruption-card/_interruption-card.scss +2 -2
  32. package/moj/components/messages/_messages.scss +12 -19
  33. package/moj/components/multi-file-upload/README.md +1 -1
  34. package/moj/components/multi-file-upload/_multi-file-upload.scss +34 -30
  35. package/moj/components/multi-file-upload/multi-file-upload.js +454 -183
  36. package/moj/components/multi-select/_multi-select.scss +4 -3
  37. package/moj/components/multi-select/multi-select.js +106 -70
  38. package/moj/components/notification-badge/_notification-badge.scss +12 -12
  39. package/moj/components/organisation-switcher/_organisation-switcher.scss +1 -1
  40. package/moj/components/page-header-actions/_page-header-actions.scss +3 -2
  41. package/moj/components/pagination/_pagination.scss +26 -31
  42. package/moj/components/password-reveal/_password-reveal.scss +1 -2
  43. package/moj/components/password-reveal/password-reveal.js +63 -31
  44. package/moj/components/primary-navigation/_primary-navigation.scss +26 -29
  45. package/moj/components/progress-bar/_progress-bar.scss +21 -26
  46. package/moj/components/rich-text-editor/_rich-text-editor.scss +17 -16
  47. package/moj/components/rich-text-editor/rich-text-editor.js +186 -139
  48. package/moj/components/search/_search.scss +6 -4
  49. package/moj/components/search-toggle/search-toggle.js +83 -53
  50. package/moj/components/search-toggle/search-toggle.scss +21 -15
  51. package/moj/components/side-navigation/_side-navigation.scss +12 -21
  52. package/moj/components/sortable-table/_sortable-table.scss +25 -23
  53. package/moj/components/sortable-table/sortable-table.js +162 -119
  54. package/moj/components/sub-navigation/_sub-navigation.scss +24 -28
  55. package/moj/components/tag/_tag.scss +8 -9
  56. package/moj/components/task-list/_task-list.scss +8 -7
  57. package/moj/components/ticket-panel/_ticket-panel.scss +14 -6
  58. package/moj/components/timeline/_timeline.scss +18 -20
  59. package/moj/filters/all.js +28 -30
  60. package/moj/filters/prototype-kit-13-filters.js +2 -1
  61. package/moj/helpers/_all.scss +1 -0
  62. package/moj/helpers/_hidden.scss +1 -1
  63. package/moj/helpers/_links.scss +20 -0
  64. package/moj/helpers.js +218 -51
  65. package/moj/init.js +2 -2
  66. package/moj/moj-frontend.min.css +2 -2
  67. package/moj/moj-frontend.min.js +15 -4
  68. package/moj/objects/_filter-layout.scss +11 -10
  69. package/moj/objects/_scrollable-pane.scss +11 -14
  70. package/moj/settings/_colours.scss +5 -0
  71. package/moj/settings/_measurements.scss +0 -2
  72. package/moj/utilities/_hidden.scss +3 -3
  73. package/moj/utilities/_width-container.scss +1 -1
  74. package/moj/version.js +28 -1
  75. package/package.json +1 -1
  76. package/moj/all.spec.js +0 -22
  77. package/moj/components/button-menu/button-menu.spec.js +0 -361
  78. package/moj/components/date-picker/date-picker.spec.js +0 -1130
  79. package/moj/components/filter-toggle-button/filter-toggle-button.spec.js +0 -304
  80. package/moj/components/multi-select/multi-select.spec.js +0 -135
  81. package/moj/components/password-reveal/password-reveal.spec.js +0 -55
  82. package/moj/components/search-toggle/search-toggle.spec.js +0 -134
  83. package/moj/namespace.js +0 -1
@@ -1,324 +1,354 @@
1
- /**
2
- * @typedef {object} ButtonMenuConfig
3
- * @property {string} [buttonText=Actions] - Label for the toggle button
4
- * @property {"left" | "right"} [alignMenu=left] - the alignment of the menu
5
- * @property {string} [buttonClasses=govuk-button--secondary] - css classes applied to the toggle button
6
- */
7
-
8
- /**
9
- * @param {HTMLElement} $module
10
- * @param {ButtonMenuConfig} config
11
- * @constructor
12
- */
13
- MOJFrontend.ButtonMenu = function ($module, config = {}) {
14
- if (!$module) {
15
- return this;
16
- }
17
-
18
- const schema = Object.freeze({
19
- properties: {
20
- buttonText: { type: "string" },
21
- buttonClasses: { type: "string" },
22
- alignMenu: { type: "string" },
23
- },
24
- });
25
-
26
- const defaults = {
27
- buttonText: "Actions",
28
- alignMenu: "left",
29
- buttonClasses: "",
30
- };
31
- // data attributes override JS config, which overrides defaults
32
- this.config = this.mergeConfigs(
33
- defaults,
34
- config,
35
- this.parseDataset(schema, $module.dataset),
36
- );
37
-
38
- this.$module = $module;
39
- };
40
-
41
- MOJFrontend.ButtonMenu.prototype.init = function () {
42
- // If only one button is provided, don't initiate a menu and toggle button
43
- // if classes have been provided for the toggleButton, apply them to the single item
44
- if (this.$module.children.length == 1) {
45
- const button = this.$module.children[0];
46
- button.classList.forEach((className) => {
47
- if (className.startsWith("govuk-button-")) {
48
- button.classList.remove(className);
49
- }
50
- button.classList.remove("moj-button-menu__item")
51
- });
52
- if (this.config.buttonClasses) {
53
- button.classList.add(...this.config.buttonClasses.split(" "));
54
- }
55
- }
56
- // Otherwise intialise a button menu
57
- if (this.$module.children.length > 1) {
58
- this.initMenu();
59
- }
60
- };
61
-
62
- MOJFrontend.ButtonMenu.prototype.initMenu = function () {
63
- this.$menu = this.createMenu();
64
- this.$module.insertAdjacentHTML("afterbegin", this.toggleTemplate());
65
- this.setupMenuItems();
66
-
67
- this.$menuToggle = this.$module.querySelector(":scope > button");
68
- this.items = this.$menu.querySelectorAll("a, button");
69
-
70
- this.$menuToggle.addEventListener("click", (event) => {
71
- this.toggleMenu(event);
72
- });
73
-
74
- this.$module.addEventListener("keydown", (event) => {
75
- this.handleKeyDown(event);
76
- });
77
-
78
- document.addEventListener("click", (event) => {
79
- if (!this.$module.contains(event.target)) {
80
- this.closeMenu(false);
81
- }
82
- });
83
- };
84
-
85
- MOJFrontend.ButtonMenu.prototype.createMenu = function () {
86
- const $menu = document.createElement("ul");
87
- $menu.setAttribute("role", "list");
88
- $menu.hidden = true;
89
- $menu.classList.add("moj-button-menu__wrapper");
90
- if (this.config.alignMenu == "right") {
91
- $menu.classList.add("moj-button-menu__wrapper--right");
92
- }
93
-
94
- this.$module.appendChild($menu);
95
- while (this.$module.firstChild !== $menu) {
96
- $menu.appendChild(this.$module.firstChild);
97
- }
98
-
99
- return $menu;
100
- };
101
-
102
- MOJFrontend.ButtonMenu.prototype.setupMenuItems = function () {
103
- Array.from(this.$menu.children).forEach((item) => {
104
- // wrap item in li tag
105
- const listItem = document.createElement("li");
106
- this.$menu.insertBefore(listItem, item);
107
- listItem.appendChild(item);
108
-
109
- item.setAttribute("tabindex", -1);
110
-
111
- if (item.tagName == "BUTTON") {
112
- item.setAttribute("type", "button");
113
- }
114
-
115
- item.classList.forEach((className) => {
116
- if (className.startsWith("govuk-button")) {
117
- item.classList.remove(className);
118
- }
119
- });
120
-
121
- // add a slight delay after click before closing the menu, makes it *feel* better
122
- item.addEventListener("click", (event) => {
123
- setTimeout(() => {
124
- this.closeMenu(false);
125
- }, 50);
126
- });
127
- });
128
- };
129
-
130
- MOJFrontend.ButtonMenu.prototype.toggleTemplate = function () {
131
- return `
132
- <button type="button" class="govuk-button moj-button-menu__toggle-button ${this.config.buttonClasses || ""}" aria-haspopup="true" aria-expanded="false">
1
+ (function (global, factory) {
2
+ typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
3
+ typeof define === 'function' && define.amd ? define(factory) :
4
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.MOJFrontend = factory());
5
+ })(this, (function () { 'use strict';
6
+
7
+ function getDefaultExportFromCjs (x) {
8
+ return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
9
+ }
10
+
11
+ /**
12
+ * @typedef {object} ButtonMenuConfig
13
+ * @property {string} [buttonText=Actions] - Label for the toggle button
14
+ * @property {"left" | "right"} [alignMenu=left] - the alignment of the menu
15
+ * @property {string} [buttonClasses=govuk-button--secondary] - css classes applied to the toggle button
16
+ */
17
+
18
+ var buttonMenu$1;
19
+ var hasRequiredButtonMenu;
20
+
21
+ function requireButtonMenu () {
22
+ if (hasRequiredButtonMenu) return buttonMenu$1;
23
+ hasRequiredButtonMenu = 1;
24
+ /**
25
+ * @param {HTMLElement} $module
26
+ * @param {ButtonMenuConfig} config
27
+ * @class
28
+ */
29
+ function ButtonMenu($module, config = {}) {
30
+ if (!$module) {
31
+ return this
32
+ }
33
+
34
+ const schema = Object.freeze({
35
+ properties: {
36
+ buttonText: { type: 'string' },
37
+ buttonClasses: { type: 'string' },
38
+ alignMenu: { type: 'string' }
39
+ }
40
+ });
41
+
42
+ const defaults = {
43
+ buttonText: 'Actions',
44
+ alignMenu: 'left',
45
+ buttonClasses: ''
46
+ };
47
+ // data attributes override JS config, which overrides defaults
48
+ this.config = this.mergeConfigs(
49
+ defaults,
50
+ config,
51
+ this.parseDataset(schema, $module.dataset)
52
+ );
53
+
54
+ this.$module = $module;
55
+ }
56
+
57
+ ButtonMenu.prototype.init = function () {
58
+ // If only one button is provided, don't initiate a menu and toggle button
59
+ // if classes have been provided for the toggleButton, apply them to the single item
60
+ if (this.$module.children.length === 1) {
61
+ const button = this.$module.children[0];
62
+ button.classList.forEach((className) => {
63
+ if (className.startsWith('govuk-button-')) {
64
+ button.classList.remove(className);
65
+ }
66
+ button.classList.remove('moj-button-menu__item');
67
+ });
68
+ if (this.config.buttonClasses) {
69
+ button.classList.add(...this.config.buttonClasses.split(' '));
70
+ }
71
+ }
72
+ // Otherwise intialise a button menu
73
+ if (this.$module.children.length > 1) {
74
+ this.initMenu();
75
+ }
76
+ };
77
+
78
+ ButtonMenu.prototype.initMenu = function () {
79
+ this.$menu = this.createMenu();
80
+ this.$module.insertAdjacentHTML('afterbegin', this.toggleTemplate());
81
+ this.setupMenuItems();
82
+
83
+ this.$menuToggle = this.$module.querySelector(':scope > button');
84
+ this.items = this.$menu.querySelectorAll('a, button');
85
+
86
+ this.$menuToggle.addEventListener('click', (event) => {
87
+ this.toggleMenu(event);
88
+ });
89
+
90
+ this.$module.addEventListener('keydown', (event) => {
91
+ this.handleKeyDown(event);
92
+ });
93
+
94
+ document.addEventListener('click', (event) => {
95
+ if (!this.$module.contains(event.target)) {
96
+ this.closeMenu(false);
97
+ }
98
+ });
99
+ };
100
+
101
+ ButtonMenu.prototype.createMenu = function () {
102
+ const $menu = document.createElement('ul');
103
+ $menu.setAttribute('role', 'list');
104
+ $menu.hidden = true;
105
+ $menu.classList.add('moj-button-menu__wrapper');
106
+ if (this.config.alignMenu === 'right') {
107
+ $menu.classList.add('moj-button-menu__wrapper--right');
108
+ }
109
+
110
+ this.$module.appendChild($menu);
111
+ while (this.$module.firstChild !== $menu) {
112
+ $menu.appendChild(this.$module.firstChild);
113
+ }
114
+
115
+ return $menu
116
+ };
117
+
118
+ ButtonMenu.prototype.setupMenuItems = function () {
119
+ Array.from(this.$menu.children).forEach((item) => {
120
+ // wrap item in li tag
121
+ const listItem = document.createElement('li');
122
+ this.$menu.insertBefore(listItem, item);
123
+ listItem.appendChild(item);
124
+
125
+ item.setAttribute('tabindex', -1);
126
+
127
+ if (item.tagName === 'BUTTON') {
128
+ item.setAttribute('type', 'button');
129
+ }
130
+
131
+ item.classList.forEach((className) => {
132
+ if (className.startsWith('govuk-button')) {
133
+ item.classList.remove(className);
134
+ }
135
+ });
136
+
137
+ // add a slight delay after click before closing the menu, makes it *feel* better
138
+ item.addEventListener('click', (event) => {
139
+ setTimeout(() => {
140
+ this.closeMenu(false);
141
+ }, 50);
142
+ });
143
+ });
144
+ };
145
+
146
+ ButtonMenu.prototype.toggleTemplate = function () {
147
+ return `
148
+ <button type="button" class="govuk-button moj-button-menu__toggle-button ${this.config.buttonClasses || ''}" aria-haspopup="true" aria-expanded="false">
133
149
  <span>
134
150
  ${this.config.buttonText}
135
151
  <svg width="11" height="5" viewBox="0 0 11 5" xmlns="http://www.w3.org/2000/svg">
136
152
  <path d="M5.5 0L11 5L0 5L5.5 0Z" fill="currentColor"/>
137
153
  </svg>
138
154
  </span>
139
- </button>`;
140
- };
141
-
142
- /**
143
- * @returns {boolean}
144
- */
145
- MOJFrontend.ButtonMenu.prototype.isOpen = function () {
146
- return this.$menuToggle.getAttribute("aria-expanded") === "true";
147
- };
148
-
149
- MOJFrontend.ButtonMenu.prototype.toggleMenu = function (event) {
150
- event.preventDefault();
151
-
152
- // If menu is triggered with mouse don't move focus to first item
153
- const keyboardEvent = event.detail == 0;
154
- const focusIndex = keyboardEvent ? 0 : -1;
155
-
156
- if (this.isOpen()) {
157
- this.closeMenu();
158
- } else {
159
- this.openMenu(focusIndex);
160
- }
161
- };
162
-
163
- /**
164
- * Opens the menu and optionally sets the focus to the item with given index
165
- *
166
- * @param {number} focusIndex - The index of the item to focus
167
- */
168
- MOJFrontend.ButtonMenu.prototype.openMenu = function (focusIndex = 0) {
169
- this.$menu.hidden = false;
170
- this.$menuToggle.setAttribute("aria-expanded", "true");
171
- if (focusIndex !== -1) {
172
- this.focusItem(focusIndex);
173
- }
174
- };
175
-
176
- /**
177
- * Closes the menu and optionally returns focus back to menuToggle
178
- *
179
- * @param {boolean} moveFocus - whether to return focus to the toggle button
180
- */
181
- MOJFrontend.ButtonMenu.prototype.closeMenu = function (moveFocus = true) {
182
- this.$menu.hidden = true;
183
- this.$menuToggle.setAttribute("aria-expanded", "false");
184
- if (moveFocus) {
185
- this.$menuToggle.focus();
186
- }
187
- };
188
-
189
- /**
190
- * Focuses the menu item at the specified index
191
- *
192
- * @param {number} index - the index of the item to focus
193
- */
194
- MOJFrontend.ButtonMenu.prototype.focusItem = function (index) {
195
- if (index >= this.items.length) index = 0;
196
- if (index < 0) index = this.items.length - 1;
197
-
198
- this.items.item(index)?.focus();
199
- };
200
-
201
- MOJFrontend.ButtonMenu.prototype.currentFocusIndex = function () {
202
- const activeElement = document.activeElement;
203
- const menuItems = Array.from(this.items);
204
-
205
- return menuItems.indexOf(activeElement);
206
- };
207
-
208
- MOJFrontend.ButtonMenu.prototype.handleKeyDown = function (event) {
209
- if (event.target == this.$menuToggle) {
210
- switch (event.key) {
211
- case "ArrowDown":
212
- event.preventDefault();
213
- this.openMenu();
214
- break;
215
- case "ArrowUp":
216
- event.preventDefault();
217
- this.openMenu(this.items.length - 1);
218
- break;
219
- }
220
- }
221
-
222
- if (this.$menu.contains(event.target) && this.isOpen()) {
223
- switch (event.key) {
224
- case "ArrowDown":
225
- event.preventDefault();
226
- if (this.currentFocusIndex() !== -1) {
227
- this.focusItem(this.currentFocusIndex() + 1);
228
- }
229
- break;
230
- case "ArrowUp":
231
- event.preventDefault();
232
- if (this.currentFocusIndex() !== -1) {
233
- this.focusItem(this.currentFocusIndex() - 1);
234
- }
235
- break;
236
- case "Home":
237
- event.preventDefault();
238
- this.focusItem(0);
239
- break;
240
- case "End":
241
- event.preventDefault();
242
- this.focusItem(this.items.length - 1);
243
- break;
244
- }
245
- }
246
-
247
- if (event.key == "Escape" && this.isOpen()) {
248
- this.closeMenu();
249
- }
250
- if (event.key == "Tab" && this.isOpen()) {
251
- this.closeMenu(false);
252
- }
253
- };
254
-
255
- /**
256
- * Parse dataset
257
- *
258
- * Loop over an object and normalise each value using {@link normaliseString},
259
- * optionally expanding nested `i18n.field`
260
- *
261
- * @param {{ schema: Schema }} Component - Component class
262
- * @param {DOMStringMap} dataset - HTML element dataset
263
- * @returns {Object} Normalised dataset
264
- */
265
- MOJFrontend.ButtonMenu.prototype.parseDataset = function (schema, dataset) {
266
- const parsed = {};
267
-
268
- for (const [field, attributes] of Object.entries(schema.properties)) {
269
- if (field in dataset) {
270
- if (dataset[field]) {
271
- parsed[field] = dataset[field];
272
- }
273
- }
274
- }
275
-
276
- return parsed;
277
- };
278
-
279
- /**
280
- * Config merging function
281
- *
282
- * Takes any number of objects and combines them together, with
283
- * greatest priority on the LAST item passed in.
284
- *
285
- * @param {...{ [key: string]: unknown }} configObjects - Config objects to merge
286
- * @returns {{ [key: string]: unknown }} A merged config object
287
- */
288
- MOJFrontend.ButtonMenu.prototype.mergeConfigs = function (...configObjects) {
289
- const formattedConfigObject = {};
290
-
291
- // Loop through each of the passed objects
292
- for (const configObject of configObjects) {
293
- for (const key of Object.keys(configObject)) {
294
- const option = formattedConfigObject[key];
295
- const override = configObject[key];
296
-
297
- // Push their keys one-by-one into formattedConfigObject. Any duplicate
298
- // keys with object values will be merged, otherwise the new value will
299
- // override the existing value.
300
- if (typeof option === "object" && typeof override === "object") {
301
- // @ts-expect-error Index signature for type 'string' is missing
302
- formattedConfigObject[key] = this.mergeConfigs(option, override);
303
- } else {
304
- formattedConfigObject[key] = override;
305
- }
306
- }
307
- }
308
-
309
- return formattedConfigObject;
310
- };
311
-
312
- /**
313
- * Schema for component config
314
- *
315
- * @typedef {object} Schema
316
- * @property {{ [field: string]: SchemaProperty | undefined }} properties - Schema properties
317
- */
318
-
319
- /**
320
- * Schema property for component config
321
- *
322
- * @typedef {object} SchemaProperty
323
- * @property {'string' | 'boolean' | 'number' | 'object'} type - Property type
324
- */
155
+ </button>`
156
+ };
157
+
158
+ /**
159
+ * @returns {boolean}
160
+ */
161
+ ButtonMenu.prototype.isOpen = function () {
162
+ return this.$menuToggle.getAttribute('aria-expanded') === 'true'
163
+ };
164
+
165
+ ButtonMenu.prototype.toggleMenu = function (event) {
166
+ event.preventDefault();
167
+
168
+ // If menu is triggered with mouse don't move focus to first item
169
+ const keyboardEvent = event.detail === 0;
170
+ const focusIndex = keyboardEvent ? 0 : -1;
171
+
172
+ if (this.isOpen()) {
173
+ this.closeMenu();
174
+ } else {
175
+ this.openMenu(focusIndex);
176
+ }
177
+ };
178
+
179
+ /**
180
+ * Opens the menu and optionally sets the focus to the item with given index
181
+ *
182
+ * @param {number} focusIndex - The index of the item to focus
183
+ */
184
+ ButtonMenu.prototype.openMenu = function (focusIndex = 0) {
185
+ this.$menu.hidden = false;
186
+ this.$menuToggle.setAttribute('aria-expanded', 'true');
187
+ if (focusIndex !== -1) {
188
+ this.focusItem(focusIndex);
189
+ }
190
+ };
191
+
192
+ /**
193
+ * Closes the menu and optionally returns focus back to menuToggle
194
+ *
195
+ * @param {boolean} moveFocus - whether to return focus to the toggle button
196
+ */
197
+ ButtonMenu.prototype.closeMenu = function (moveFocus = true) {
198
+ this.$menu.hidden = true;
199
+ this.$menuToggle.setAttribute('aria-expanded', 'false');
200
+ if (moveFocus) {
201
+ this.$menuToggle.focus();
202
+ }
203
+ };
204
+
205
+ /**
206
+ * Focuses the menu item at the specified index
207
+ *
208
+ * @param {number} index - the index of the item to focus
209
+ */
210
+ ButtonMenu.prototype.focusItem = function (index) {
211
+ if (index >= this.items.length) index = 0;
212
+ if (index < 0) index = this.items.length - 1;
213
+
214
+ const menuItem = this.items.item(index);
215
+ if (menuItem) {
216
+ menuItem.focus();
217
+ }
218
+ };
219
+
220
+ ButtonMenu.prototype.currentFocusIndex = function () {
221
+ const activeElement = document.activeElement;
222
+ const menuItems = Array.from(this.items);
223
+
224
+ return menuItems.indexOf(activeElement)
225
+ };
226
+
227
+ ButtonMenu.prototype.handleKeyDown = function (event) {
228
+ if (event.target === this.$menuToggle) {
229
+ switch (event.key) {
230
+ case 'ArrowDown':
231
+ event.preventDefault();
232
+ this.openMenu();
233
+ break
234
+ case 'ArrowUp':
235
+ event.preventDefault();
236
+ this.openMenu(this.items.length - 1);
237
+ break
238
+ }
239
+ }
240
+
241
+ if (this.$menu.contains(event.target) && this.isOpen()) {
242
+ switch (event.key) {
243
+ case 'ArrowDown':
244
+ event.preventDefault();
245
+ if (this.currentFocusIndex() !== -1) {
246
+ this.focusItem(this.currentFocusIndex() + 1);
247
+ }
248
+ break
249
+ case 'ArrowUp':
250
+ event.preventDefault();
251
+ if (this.currentFocusIndex() !== -1) {
252
+ this.focusItem(this.currentFocusIndex() - 1);
253
+ }
254
+ break
255
+ case 'Home':
256
+ event.preventDefault();
257
+ this.focusItem(0);
258
+ break
259
+ case 'End':
260
+ event.preventDefault();
261
+ this.focusItem(this.items.length - 1);
262
+ break
263
+ }
264
+ }
265
+
266
+ if (event.key === 'Escape' && this.isOpen()) {
267
+ this.closeMenu();
268
+ }
269
+ if (event.key === 'Tab' && this.isOpen()) {
270
+ this.closeMenu(false);
271
+ }
272
+ };
273
+
274
+ /**
275
+ * Parse dataset
276
+ *
277
+ * Loop over an object and normalise each value using {@link normaliseString},
278
+ * optionally expanding nested `i18n.field`
279
+ *
280
+ * @param {Schema} schema - component schema
281
+ * @param {DOMStringMap} dataset - HTML element dataset
282
+ * @returns {object} Normalised dataset
283
+ */
284
+ ButtonMenu.prototype.parseDataset = function (schema, dataset) {
285
+ const parsed = {};
286
+
287
+ for (const [field, ,] of Object.entries(schema.properties)) {
288
+ if (field in dataset) {
289
+ if (dataset[field]) {
290
+ parsed[field] = dataset[field];
291
+ }
292
+ }
293
+ }
294
+
295
+ return parsed
296
+ };
297
+
298
+ /**
299
+ * Config merging function
300
+ *
301
+ * Takes any number of objects and combines them together, with
302
+ * greatest priority on the LAST item passed in.
303
+ *
304
+ * @param {...{ [key: string]: unknown }} configObjects - Config objects to merge
305
+ * @returns {{ [key: string]: unknown }} A merged config object
306
+ */
307
+ ButtonMenu.prototype.mergeConfigs = function (...configObjects) {
308
+ const formattedConfigObject = {};
309
+
310
+ // Loop through each of the passed objects
311
+ for (const configObject of configObjects) {
312
+ for (const key of Object.keys(configObject)) {
313
+ const option = formattedConfigObject[key];
314
+ const override = configObject[key];
315
+
316
+ // Push their keys one-by-one into formattedConfigObject. Any duplicate
317
+ // keys with object values will be merged, otherwise the new value will
318
+ // override the existing value.
319
+ if (typeof option === 'object' && typeof override === 'object') {
320
+ // @ts-expect-error Index signature for type 'string' is missing
321
+ formattedConfigObject[key] = this.mergeConfigs(option, override);
322
+ } else {
323
+ formattedConfigObject[key] = override;
324
+ }
325
+ }
326
+ }
327
+
328
+ return formattedConfigObject
329
+ };
330
+
331
+ buttonMenu$1 = { ButtonMenu };
332
+
333
+ /**
334
+ * Schema for component config
335
+ *
336
+ * @typedef {object} Schema
337
+ * @property {{ [field: string]: SchemaProperty | undefined }} properties - Schema properties
338
+ */
339
+
340
+ /**
341
+ * Schema property for component config
342
+ *
343
+ * @typedef {object} SchemaProperty
344
+ * @property {'string' | 'boolean' | 'number' | 'object'} type - Property type
345
+ */
346
+ return buttonMenu$1;
347
+ }
348
+
349
+ var buttonMenuExports = requireButtonMenu();
350
+ var buttonMenu = /*@__PURE__*/getDefaultExportFromCjs(buttonMenuExports);
351
+
352
+ return buttonMenu;
353
+
354
+ }));