@ministryofjustice/frontend 3.3.0 → 3.4.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.
- package/README.md +4 -10
- package/govuk-prototype-kit.config.json +5 -16
- package/moj/all.jquery.min.js +77 -3
- package/moj/all.js +2022 -1444
- package/moj/all.scss +2 -0
- package/moj/all.spec.js +15 -13
- package/moj/components/_all.scss +1 -0
- package/moj/components/action-bar/_action-bar.scss +4 -6
- package/moj/components/add-another/_add-another.scss +9 -7
- package/moj/components/add-another/add-another.js +90 -69
- package/moj/components/add-another/add-another.spec.js +165 -0
- package/moj/components/alert/README.md +0 -0
- package/moj/components/alert/_alert.scss +142 -0
- package/moj/components/alert/alert.js +247 -0
- package/moj/components/alert/alert.spec.helper.js +67 -0
- package/moj/components/alert/alert.spec.js +229 -0
- package/moj/components/alert/macro.njk +3 -0
- package/moj/components/alert/template.njk +83 -0
- package/moj/components/badge/_badge.scss +3 -4
- package/moj/components/banner/_banner.scss +5 -10
- package/moj/components/button-menu/_button-menu.scss +10 -9
- package/moj/components/button-menu/button-menu.js +139 -136
- package/moj/components/button-menu/button-menu.spec.js +295 -296
- package/moj/components/cookie-banner/_cookie-banner.scss +6 -5
- package/moj/components/currency-input/_currency-input.scss +4 -4
- package/moj/components/date-picker/README.md +14 -17
- package/moj/components/date-picker/_date-picker.scss +122 -106
- package/moj/components/date-picker/date-picker.js +473 -471
- package/moj/components/date-picker/date-picker.spec.js +962 -914
- package/moj/components/filter/README.md +1 -1
- package/moj/components/filter/_filter.scss +53 -75
- package/moj/components/filter-toggle-button/filter-toggle-button.js +71 -67
- package/moj/components/filter-toggle-button/filter-toggle-button.spec.js +203 -205
- package/moj/components/form-validator/form-validator.js +117 -109
- package/moj/components/header/_header.scss +17 -19
- package/moj/components/identity-bar/_identity-bar.scss +5 -5
- package/moj/components/interruption-card/_interruption-card.scss +9 -2
- package/moj/components/messages/_messages.scss +12 -19
- package/moj/components/multi-file-upload/README.md +1 -1
- package/moj/components/multi-file-upload/_multi-file-upload.scss +34 -30
- package/moj/components/multi-file-upload/multi-file-upload.js +188 -152
- package/moj/components/multi-file-upload/multi-file-upload.spec.js +510 -0
- package/moj/components/multi-select/_multi-select.scss +4 -3
- package/moj/components/multi-select/multi-select.js +55 -50
- package/moj/components/multi-select/multi-select.spec.js +128 -0
- package/moj/components/notification-badge/_notification-badge.scss +12 -12
- package/moj/components/organisation-switcher/_organisation-switcher.scss +1 -1
- package/moj/components/page-header-actions/_page-header-actions.scss +3 -2
- package/moj/components/pagination/_pagination.scss +26 -31
- package/moj/components/password-reveal/_password-reveal.scss +1 -2
- package/moj/components/password-reveal/password-reveal.js +22 -21
- package/moj/components/password-reveal/password-reveal.spec.js +39 -37
- package/moj/components/primary-navigation/_primary-navigation.scss +26 -29
- package/moj/components/progress-bar/_progress-bar.scss +21 -26
- package/moj/components/rich-text-editor/_rich-text-editor.scss +17 -16
- package/moj/components/rich-text-editor/rich-text-editor.js +117 -103
- package/moj/components/search/_search.scss +6 -4
- package/moj/components/search-toggle/search-toggle.js +29 -30
- package/moj/components/search-toggle/search-toggle.scss +21 -15
- package/moj/components/search-toggle/search-toggle.spec.js +129 -0
- package/moj/components/side-navigation/_side-navigation.scss +12 -21
- package/moj/components/sortable-table/_sortable-table.scss +25 -23
- package/moj/components/sortable-table/sortable-table.js +139 -117
- package/moj/components/sortable-table/sortable-table.spec.js +362 -0
- package/moj/components/sub-navigation/_sub-navigation.scss +24 -28
- package/moj/components/tag/_tag.scss +8 -9
- package/moj/components/task-list/_task-list.scss +8 -7
- package/moj/components/ticket-panel/_ticket-panel.scss +14 -6
- package/moj/components/timeline/_timeline.scss +18 -20
- package/moj/filters/all.js +28 -30
- package/moj/filters/prototype-kit-13-filters.js +2 -1
- package/moj/helpers/_all.scss +1 -0
- package/moj/helpers/_hidden.scss +1 -1
- package/moj/helpers/_links.scss +20 -0
- package/moj/helpers.js +160 -31
- package/moj/helpers.spec.js +235 -0
- package/moj/init.js +2 -2
- package/moj/moj-frontend.min.css +2 -2
- package/moj/moj-frontend.min.js +77 -3
- package/moj/namespace.js +2 -1
- package/moj/objects/_filter-layout.scss +11 -10
- package/moj/objects/_scrollable-pane.scss +11 -14
- package/moj/settings/_colours.scss +5 -0
- package/moj/settings/_measurements.scss +0 -2
- package/moj/utilities/_hidden.scss +3 -3
- package/moj/utilities/_width-container.scss +1 -1
- package/package.json +1 -1
|
@@ -3,43 +3,39 @@
|
|
|
3
3
|
========================================================================== */
|
|
4
4
|
|
|
5
5
|
.moj-banner {
|
|
6
|
+
margin-bottom: govuk-spacing(6);
|
|
7
|
+
padding: govuk-spacing(2);
|
|
6
8
|
border: 5px solid $govuk-brand-colour;
|
|
7
9
|
color: $govuk-brand-colour;
|
|
8
10
|
font-size: 0; // Removes white space when using inline-block on child element.
|
|
9
|
-
margin-bottom: govuk-spacing(6);
|
|
10
|
-
padding: govuk-spacing(2);
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
|
|
14
13
|
.moj-banner__icon {
|
|
15
|
-
fill: currentColor;
|
|
16
|
-
float: left;
|
|
17
14
|
margin-right: govuk-spacing(2);
|
|
15
|
+
float: left;
|
|
16
|
+
fill: currentcolor;
|
|
18
17
|
}
|
|
19
18
|
|
|
20
19
|
.moj-banner__message {
|
|
21
20
|
@include govuk-font($size: 19);
|
|
22
|
-
color: govuk-colour("black");
|
|
23
21
|
display: block;
|
|
24
22
|
overflow: hidden;
|
|
23
|
+
color: govuk-colour("black");
|
|
25
24
|
}
|
|
26
25
|
|
|
27
26
|
.moj-banner__message h2 {
|
|
28
27
|
margin-bottom: govuk-spacing(2);
|
|
29
28
|
}
|
|
30
29
|
|
|
31
|
-
|
|
32
30
|
.moj-banner__message h2:last-child,
|
|
33
31
|
.moj-banner__message p:last-child {
|
|
34
32
|
margin-bottom: 0;
|
|
35
33
|
}
|
|
36
34
|
|
|
37
|
-
|
|
38
35
|
.moj-banner__assistive {
|
|
39
36
|
@include govuk-visually-hidden;
|
|
40
37
|
}
|
|
41
38
|
|
|
42
|
-
|
|
43
39
|
/* Style variants
|
|
44
40
|
========================================================================== */
|
|
45
41
|
|
|
@@ -48,7 +44,6 @@
|
|
|
48
44
|
color: govuk-colour("green");
|
|
49
45
|
}
|
|
50
46
|
|
|
51
|
-
|
|
52
47
|
.moj-banner--warning {
|
|
53
48
|
border-color: govuk-colour("red");
|
|
54
49
|
color: govuk-colour("red");
|
|
@@ -23,8 +23,8 @@ $moj-datepicker-mid-grey: #949494;
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
.moj-button-menu__toggle-button svg {
|
|
26
|
-
transform: rotate(180deg);
|
|
27
26
|
margin-top: 2px;
|
|
27
|
+
transform: rotate(180deg);
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
.moj-button-menu__toggle-button[aria-expanded="true"] svg {
|
|
@@ -32,13 +32,13 @@ $moj-datepicker-mid-grey: #949494;
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
.moj-button-menu__wrapper {
|
|
35
|
-
list-style: none;
|
|
36
35
|
position: absolute;
|
|
36
|
+
z-index: 10;
|
|
37
|
+
top: 43px; // 38px button height, 2px shadow, 3px gap
|
|
38
|
+
width: 200px;
|
|
37
39
|
margin: 0;
|
|
38
40
|
padding: 0;
|
|
39
|
-
|
|
40
|
-
top: 43px; //38px button height, 2px shadow, 3px gap
|
|
41
|
-
z-index: 10;
|
|
41
|
+
list-style: none;
|
|
42
42
|
|
|
43
43
|
&--right {
|
|
44
44
|
right: 0;
|
|
@@ -48,9 +48,10 @@ $moj-datepicker-mid-grey: #949494;
|
|
|
48
48
|
/* Menu items with no JS */
|
|
49
49
|
.moj-button-menu__item {
|
|
50
50
|
display: inline-block;
|
|
51
|
+
width: auto; // Override GDS’s 100% width
|
|
51
52
|
margin-right: govuk-spacing(2);
|
|
52
53
|
margin-bottom: govuk-spacing(2);
|
|
53
|
-
|
|
54
|
+
|
|
54
55
|
&:last-child {
|
|
55
56
|
margin-right: 0;
|
|
56
57
|
}
|
|
@@ -67,12 +68,12 @@ $moj-datepicker-mid-grey: #949494;
|
|
|
67
68
|
width: 100%;
|
|
68
69
|
margin-top: 0;
|
|
69
70
|
margin-right: 0;
|
|
70
|
-
margin-left: 0;
|
|
71
71
|
margin-bottom: 0;
|
|
72
|
+
margin-left: 0;
|
|
72
73
|
padding: govuk-spacing(2);
|
|
73
74
|
border: $govuk-border-width-form-element solid transparent;
|
|
74
|
-
border-radius: 0;
|
|
75
75
|
border-bottom: 1px solid $moj-datepicker-mid-grey;
|
|
76
|
+
border-radius: 0;
|
|
76
77
|
color: $govuk-text-colour;
|
|
77
78
|
background-color: govuk-colour("light-grey");
|
|
78
79
|
text-align: left;
|
|
@@ -105,10 +106,10 @@ $moj-datepicker-mid-grey: #949494;
|
|
|
105
106
|
}
|
|
106
107
|
|
|
107
108
|
&:focus {
|
|
109
|
+
z-index: 10;
|
|
108
110
|
border-color: $govuk-focus-colour;
|
|
109
111
|
outline: $govuk-focus-width solid transparent;
|
|
110
112
|
box-shadow: inset 0 0 0 1px $govuk-focus-colour;
|
|
111
|
-
z-index: 10;
|
|
112
113
|
}
|
|
113
114
|
|
|
114
115
|
&:focus:not(:active):not(:hover) {
|
|
@@ -8,157 +8,157 @@
|
|
|
8
8
|
/**
|
|
9
9
|
* @param {HTMLElement} $module
|
|
10
10
|
* @param {ButtonMenuConfig} config
|
|
11
|
-
* @
|
|
11
|
+
* @class
|
|
12
12
|
*/
|
|
13
13
|
MOJFrontend.ButtonMenu = function ($module, config = {}) {
|
|
14
14
|
if (!$module) {
|
|
15
|
-
return this
|
|
15
|
+
return this
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
const schema = Object.freeze({
|
|
19
19
|
properties: {
|
|
20
|
-
buttonText: { type:
|
|
21
|
-
buttonClasses: { type:
|
|
22
|
-
alignMenu: { type:
|
|
23
|
-
}
|
|
24
|
-
})
|
|
20
|
+
buttonText: { type: 'string' },
|
|
21
|
+
buttonClasses: { type: 'string' },
|
|
22
|
+
alignMenu: { type: 'string' }
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
25
|
|
|
26
26
|
const defaults = {
|
|
27
|
-
buttonText:
|
|
28
|
-
alignMenu:
|
|
29
|
-
buttonClasses:
|
|
30
|
-
}
|
|
27
|
+
buttonText: 'Actions',
|
|
28
|
+
alignMenu: 'left',
|
|
29
|
+
buttonClasses: ''
|
|
30
|
+
}
|
|
31
31
|
// data attributes override JS config, which overrides defaults
|
|
32
32
|
this.config = this.mergeConfigs(
|
|
33
33
|
defaults,
|
|
34
34
|
config,
|
|
35
|
-
this.parseDataset(schema, $module.dataset)
|
|
36
|
-
)
|
|
35
|
+
this.parseDataset(schema, $module.dataset)
|
|
36
|
+
)
|
|
37
37
|
|
|
38
|
-
this.$module = $module
|
|
39
|
-
}
|
|
38
|
+
this.$module = $module
|
|
39
|
+
}
|
|
40
40
|
|
|
41
41
|
MOJFrontend.ButtonMenu.prototype.init = function () {
|
|
42
42
|
// If only one button is provided, don't initiate a menu and toggle button
|
|
43
43
|
// if classes have been provided for the toggleButton, apply them to the single item
|
|
44
|
-
if (this.$module.children.length
|
|
45
|
-
const button = this.$module.children[0]
|
|
44
|
+
if (this.$module.children.length === 1) {
|
|
45
|
+
const button = this.$module.children[0]
|
|
46
46
|
button.classList.forEach((className) => {
|
|
47
|
-
if (className.startsWith(
|
|
48
|
-
button.classList.remove(className)
|
|
47
|
+
if (className.startsWith('govuk-button-')) {
|
|
48
|
+
button.classList.remove(className)
|
|
49
49
|
}
|
|
50
|
-
button.classList.remove(
|
|
51
|
-
})
|
|
50
|
+
button.classList.remove('moj-button-menu__item')
|
|
51
|
+
})
|
|
52
52
|
if (this.config.buttonClasses) {
|
|
53
|
-
button.classList.add(...this.config.buttonClasses.split(
|
|
53
|
+
button.classList.add(...this.config.buttonClasses.split(' '))
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
// Otherwise intialise a button menu
|
|
57
57
|
if (this.$module.children.length > 1) {
|
|
58
|
-
this.initMenu()
|
|
58
|
+
this.initMenu()
|
|
59
59
|
}
|
|
60
|
-
}
|
|
60
|
+
}
|
|
61
61
|
|
|
62
62
|
MOJFrontend.ButtonMenu.prototype.initMenu = function () {
|
|
63
|
-
this.$menu = this.createMenu()
|
|
64
|
-
this.$module.insertAdjacentHTML(
|
|
65
|
-
this.setupMenuItems()
|
|
63
|
+
this.$menu = this.createMenu()
|
|
64
|
+
this.$module.insertAdjacentHTML('afterbegin', this.toggleTemplate())
|
|
65
|
+
this.setupMenuItems()
|
|
66
66
|
|
|
67
|
-
this.$menuToggle = this.$module.querySelector(
|
|
68
|
-
this.items = this.$menu.querySelectorAll(
|
|
67
|
+
this.$menuToggle = this.$module.querySelector(':scope > button')
|
|
68
|
+
this.items = this.$menu.querySelectorAll('a, button')
|
|
69
69
|
|
|
70
|
-
this.$menuToggle.addEventListener(
|
|
71
|
-
this.toggleMenu(event)
|
|
72
|
-
})
|
|
70
|
+
this.$menuToggle.addEventListener('click', (event) => {
|
|
71
|
+
this.toggleMenu(event)
|
|
72
|
+
})
|
|
73
73
|
|
|
74
|
-
this.$module.addEventListener(
|
|
75
|
-
this.handleKeyDown(event)
|
|
76
|
-
})
|
|
74
|
+
this.$module.addEventListener('keydown', (event) => {
|
|
75
|
+
this.handleKeyDown(event)
|
|
76
|
+
})
|
|
77
77
|
|
|
78
|
-
document.addEventListener(
|
|
78
|
+
document.addEventListener('click', (event) => {
|
|
79
79
|
if (!this.$module.contains(event.target)) {
|
|
80
|
-
this.closeMenu(false)
|
|
80
|
+
this.closeMenu(false)
|
|
81
81
|
}
|
|
82
|
-
})
|
|
83
|
-
}
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
84
|
|
|
85
85
|
MOJFrontend.ButtonMenu.prototype.createMenu = function () {
|
|
86
|
-
const $menu = document.createElement(
|
|
87
|
-
$menu.setAttribute(
|
|
88
|
-
$menu.hidden = true
|
|
89
|
-
$menu.classList.add(
|
|
90
|
-
if (this.config.alignMenu
|
|
91
|
-
$menu.classList.add(
|
|
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
92
|
}
|
|
93
93
|
|
|
94
|
-
this.$module.appendChild($menu)
|
|
94
|
+
this.$module.appendChild($menu)
|
|
95
95
|
while (this.$module.firstChild !== $menu) {
|
|
96
|
-
$menu.appendChild(this.$module.firstChild)
|
|
96
|
+
$menu.appendChild(this.$module.firstChild)
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
return $menu
|
|
100
|
-
}
|
|
99
|
+
return $menu
|
|
100
|
+
}
|
|
101
101
|
|
|
102
102
|
MOJFrontend.ButtonMenu.prototype.setupMenuItems = function () {
|
|
103
103
|
Array.from(this.$menu.children).forEach((item) => {
|
|
104
104
|
// wrap item in li tag
|
|
105
|
-
const listItem = document.createElement(
|
|
106
|
-
this.$menu.insertBefore(listItem, item)
|
|
107
|
-
listItem.appendChild(item)
|
|
105
|
+
const listItem = document.createElement('li')
|
|
106
|
+
this.$menu.insertBefore(listItem, item)
|
|
107
|
+
listItem.appendChild(item)
|
|
108
108
|
|
|
109
|
-
item.setAttribute(
|
|
109
|
+
item.setAttribute('tabindex', -1)
|
|
110
110
|
|
|
111
|
-
if (item.tagName
|
|
112
|
-
item.setAttribute(
|
|
111
|
+
if (item.tagName === 'BUTTON') {
|
|
112
|
+
item.setAttribute('type', 'button')
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
item.classList.forEach((className) => {
|
|
116
|
-
if (className.startsWith(
|
|
117
|
-
item.classList.remove(className)
|
|
116
|
+
if (className.startsWith('govuk-button')) {
|
|
117
|
+
item.classList.remove(className)
|
|
118
118
|
}
|
|
119
|
-
})
|
|
119
|
+
})
|
|
120
120
|
|
|
121
121
|
// add a slight delay after click before closing the menu, makes it *feel* better
|
|
122
|
-
item.addEventListener(
|
|
122
|
+
item.addEventListener('click', (event) => {
|
|
123
123
|
setTimeout(() => {
|
|
124
|
-
this.closeMenu(false)
|
|
125
|
-
}, 50)
|
|
126
|
-
})
|
|
127
|
-
})
|
|
128
|
-
}
|
|
124
|
+
this.closeMenu(false)
|
|
125
|
+
}, 50)
|
|
126
|
+
})
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
129
|
|
|
130
130
|
MOJFrontend.ButtonMenu.prototype.toggleTemplate = function () {
|
|
131
131
|
return `
|
|
132
|
-
<button type="button" class="govuk-button moj-button-menu__toggle-button ${this.config.buttonClasses ||
|
|
132
|
+
<button type="button" class="govuk-button moj-button-menu__toggle-button ${this.config.buttonClasses || ''}" aria-haspopup="true" aria-expanded="false">
|
|
133
133
|
<span>
|
|
134
134
|
${this.config.buttonText}
|
|
135
135
|
<svg width="11" height="5" viewBox="0 0 11 5" xmlns="http://www.w3.org/2000/svg">
|
|
136
136
|
<path d="M5.5 0L11 5L0 5L5.5 0Z" fill="currentColor"/>
|
|
137
137
|
</svg>
|
|
138
138
|
</span>
|
|
139
|
-
</button
|
|
140
|
-
}
|
|
139
|
+
</button>`
|
|
140
|
+
}
|
|
141
141
|
|
|
142
142
|
/**
|
|
143
143
|
* @returns {boolean}
|
|
144
144
|
*/
|
|
145
145
|
MOJFrontend.ButtonMenu.prototype.isOpen = function () {
|
|
146
|
-
return this.$menuToggle.getAttribute(
|
|
147
|
-
}
|
|
146
|
+
return this.$menuToggle.getAttribute('aria-expanded') === 'true'
|
|
147
|
+
}
|
|
148
148
|
|
|
149
149
|
MOJFrontend.ButtonMenu.prototype.toggleMenu = function (event) {
|
|
150
|
-
event.preventDefault()
|
|
150
|
+
event.preventDefault()
|
|
151
151
|
|
|
152
152
|
// If menu is triggered with mouse don't move focus to first item
|
|
153
|
-
const keyboardEvent = event.detail
|
|
154
|
-
const focusIndex = keyboardEvent ? 0 : -1
|
|
153
|
+
const keyboardEvent = event.detail === 0
|
|
154
|
+
const focusIndex = keyboardEvent ? 0 : -1
|
|
155
155
|
|
|
156
156
|
if (this.isOpen()) {
|
|
157
|
-
this.closeMenu()
|
|
157
|
+
this.closeMenu()
|
|
158
158
|
} else {
|
|
159
|
-
this.openMenu(focusIndex)
|
|
159
|
+
this.openMenu(focusIndex)
|
|
160
160
|
}
|
|
161
|
-
}
|
|
161
|
+
}
|
|
162
162
|
|
|
163
163
|
/**
|
|
164
164
|
* Opens the menu and optionally sets the focus to the item with given index
|
|
@@ -166,12 +166,12 @@ MOJFrontend.ButtonMenu.prototype.toggleMenu = function (event) {
|
|
|
166
166
|
* @param {number} focusIndex - The index of the item to focus
|
|
167
167
|
*/
|
|
168
168
|
MOJFrontend.ButtonMenu.prototype.openMenu = function (focusIndex = 0) {
|
|
169
|
-
this.$menu.hidden = false
|
|
170
|
-
this.$menuToggle.setAttribute(
|
|
169
|
+
this.$menu.hidden = false
|
|
170
|
+
this.$menuToggle.setAttribute('aria-expanded', 'true')
|
|
171
171
|
if (focusIndex !== -1) {
|
|
172
|
-
this.focusItem(focusIndex)
|
|
172
|
+
this.focusItem(focusIndex)
|
|
173
173
|
}
|
|
174
|
-
}
|
|
174
|
+
}
|
|
175
175
|
|
|
176
176
|
/**
|
|
177
177
|
* Closes the menu and optionally returns focus back to menuToggle
|
|
@@ -179,12 +179,12 @@ MOJFrontend.ButtonMenu.prototype.openMenu = function (focusIndex = 0) {
|
|
|
179
179
|
* @param {boolean} moveFocus - whether to return focus to the toggle button
|
|
180
180
|
*/
|
|
181
181
|
MOJFrontend.ButtonMenu.prototype.closeMenu = function (moveFocus = true) {
|
|
182
|
-
this.$menu.hidden = true
|
|
183
|
-
this.$menuToggle.setAttribute(
|
|
182
|
+
this.$menu.hidden = true
|
|
183
|
+
this.$menuToggle.setAttribute('aria-expanded', 'false')
|
|
184
184
|
if (moveFocus) {
|
|
185
|
-
this.$menuToggle.focus()
|
|
185
|
+
this.$menuToggle.focus()
|
|
186
186
|
}
|
|
187
|
-
}
|
|
187
|
+
}
|
|
188
188
|
|
|
189
189
|
/**
|
|
190
190
|
* Focuses the menu item at the specified index
|
|
@@ -192,65 +192,68 @@ MOJFrontend.ButtonMenu.prototype.closeMenu = function (moveFocus = true) {
|
|
|
192
192
|
* @param {number} index - the index of the item to focus
|
|
193
193
|
*/
|
|
194
194
|
MOJFrontend.ButtonMenu.prototype.focusItem = function (index) {
|
|
195
|
-
if (index >= this.items.length) index = 0
|
|
196
|
-
if (index < 0) index = this.items.length - 1
|
|
195
|
+
if (index >= this.items.length) index = 0
|
|
196
|
+
if (index < 0) index = this.items.length - 1
|
|
197
197
|
|
|
198
|
-
this.items.item(index)
|
|
199
|
-
|
|
198
|
+
const menuItem = this.items.item(index)
|
|
199
|
+
if (menuItem) {
|
|
200
|
+
menuItem.focus()
|
|
201
|
+
}
|
|
202
|
+
}
|
|
200
203
|
|
|
201
204
|
MOJFrontend.ButtonMenu.prototype.currentFocusIndex = function () {
|
|
202
|
-
const activeElement = document.activeElement
|
|
203
|
-
const menuItems = Array.from(this.items)
|
|
205
|
+
const activeElement = document.activeElement
|
|
206
|
+
const menuItems = Array.from(this.items)
|
|
204
207
|
|
|
205
|
-
return menuItems.indexOf(activeElement)
|
|
206
|
-
}
|
|
208
|
+
return menuItems.indexOf(activeElement)
|
|
209
|
+
}
|
|
207
210
|
|
|
208
211
|
MOJFrontend.ButtonMenu.prototype.handleKeyDown = function (event) {
|
|
209
|
-
if (event.target
|
|
212
|
+
if (event.target === this.$menuToggle) {
|
|
210
213
|
switch (event.key) {
|
|
211
|
-
case
|
|
212
|
-
event.preventDefault()
|
|
213
|
-
this.openMenu()
|
|
214
|
-
break
|
|
215
|
-
case
|
|
216
|
-
event.preventDefault()
|
|
217
|
-
this.openMenu(this.items.length - 1)
|
|
218
|
-
break
|
|
214
|
+
case 'ArrowDown':
|
|
215
|
+
event.preventDefault()
|
|
216
|
+
this.openMenu()
|
|
217
|
+
break
|
|
218
|
+
case 'ArrowUp':
|
|
219
|
+
event.preventDefault()
|
|
220
|
+
this.openMenu(this.items.length - 1)
|
|
221
|
+
break
|
|
219
222
|
}
|
|
220
223
|
}
|
|
221
224
|
|
|
222
225
|
if (this.$menu.contains(event.target) && this.isOpen()) {
|
|
223
226
|
switch (event.key) {
|
|
224
|
-
case
|
|
225
|
-
event.preventDefault()
|
|
227
|
+
case 'ArrowDown':
|
|
228
|
+
event.preventDefault()
|
|
226
229
|
if (this.currentFocusIndex() !== -1) {
|
|
227
|
-
this.focusItem(this.currentFocusIndex() + 1)
|
|
230
|
+
this.focusItem(this.currentFocusIndex() + 1)
|
|
228
231
|
}
|
|
229
|
-
break
|
|
230
|
-
case
|
|
231
|
-
event.preventDefault()
|
|
232
|
+
break
|
|
233
|
+
case 'ArrowUp':
|
|
234
|
+
event.preventDefault()
|
|
232
235
|
if (this.currentFocusIndex() !== -1) {
|
|
233
|
-
this.focusItem(this.currentFocusIndex() - 1)
|
|
236
|
+
this.focusItem(this.currentFocusIndex() - 1)
|
|
234
237
|
}
|
|
235
|
-
break
|
|
236
|
-
case
|
|
237
|
-
event.preventDefault()
|
|
238
|
-
this.focusItem(0)
|
|
239
|
-
break
|
|
240
|
-
case
|
|
241
|
-
event.preventDefault()
|
|
242
|
-
this.focusItem(this.items.length - 1)
|
|
243
|
-
break
|
|
238
|
+
break
|
|
239
|
+
case 'Home':
|
|
240
|
+
event.preventDefault()
|
|
241
|
+
this.focusItem(0)
|
|
242
|
+
break
|
|
243
|
+
case 'End':
|
|
244
|
+
event.preventDefault()
|
|
245
|
+
this.focusItem(this.items.length - 1)
|
|
246
|
+
break
|
|
244
247
|
}
|
|
245
248
|
}
|
|
246
249
|
|
|
247
|
-
if (event.key
|
|
248
|
-
this.closeMenu()
|
|
250
|
+
if (event.key === 'Escape' && this.isOpen()) {
|
|
251
|
+
this.closeMenu()
|
|
249
252
|
}
|
|
250
|
-
if (event.key
|
|
251
|
-
this.closeMenu(false)
|
|
253
|
+
if (event.key === 'Tab' && this.isOpen()) {
|
|
254
|
+
this.closeMenu(false)
|
|
252
255
|
}
|
|
253
|
-
}
|
|
256
|
+
}
|
|
254
257
|
|
|
255
258
|
/**
|
|
256
259
|
* Parse dataset
|
|
@@ -258,23 +261,23 @@ MOJFrontend.ButtonMenu.prototype.handleKeyDown = function (event) {
|
|
|
258
261
|
* Loop over an object and normalise each value using {@link normaliseString},
|
|
259
262
|
* optionally expanding nested `i18n.field`
|
|
260
263
|
*
|
|
261
|
-
* @param {
|
|
264
|
+
* @param {Schema} schema - component schema
|
|
262
265
|
* @param {DOMStringMap} dataset - HTML element dataset
|
|
263
|
-
* @returns {
|
|
266
|
+
* @returns {object} Normalised dataset
|
|
264
267
|
*/
|
|
265
268
|
MOJFrontend.ButtonMenu.prototype.parseDataset = function (schema, dataset) {
|
|
266
|
-
const parsed = {}
|
|
269
|
+
const parsed = {}
|
|
267
270
|
|
|
268
|
-
for (const [field,
|
|
271
|
+
for (const [field, ,] of Object.entries(schema.properties)) {
|
|
269
272
|
if (field in dataset) {
|
|
270
273
|
if (dataset[field]) {
|
|
271
|
-
parsed[field] = dataset[field]
|
|
274
|
+
parsed[field] = dataset[field]
|
|
272
275
|
}
|
|
273
276
|
}
|
|
274
277
|
}
|
|
275
278
|
|
|
276
|
-
return parsed
|
|
277
|
-
}
|
|
279
|
+
return parsed
|
|
280
|
+
}
|
|
278
281
|
|
|
279
282
|
/**
|
|
280
283
|
* Config merging function
|
|
@@ -286,28 +289,28 @@ MOJFrontend.ButtonMenu.prototype.parseDataset = function (schema, dataset) {
|
|
|
286
289
|
* @returns {{ [key: string]: unknown }} A merged config object
|
|
287
290
|
*/
|
|
288
291
|
MOJFrontend.ButtonMenu.prototype.mergeConfigs = function (...configObjects) {
|
|
289
|
-
const formattedConfigObject = {}
|
|
292
|
+
const formattedConfigObject = {}
|
|
290
293
|
|
|
291
294
|
// Loop through each of the passed objects
|
|
292
295
|
for (const configObject of configObjects) {
|
|
293
296
|
for (const key of Object.keys(configObject)) {
|
|
294
|
-
const option = formattedConfigObject[key]
|
|
295
|
-
const override = configObject[key]
|
|
297
|
+
const option = formattedConfigObject[key]
|
|
298
|
+
const override = configObject[key]
|
|
296
299
|
|
|
297
300
|
// Push their keys one-by-one into formattedConfigObject. Any duplicate
|
|
298
301
|
// keys with object values will be merged, otherwise the new value will
|
|
299
302
|
// override the existing value.
|
|
300
|
-
if (typeof option ===
|
|
303
|
+
if (typeof option === 'object' && typeof override === 'object') {
|
|
301
304
|
// @ts-expect-error Index signature for type 'string' is missing
|
|
302
|
-
formattedConfigObject[key] = this.mergeConfigs(option, override)
|
|
305
|
+
formattedConfigObject[key] = this.mergeConfigs(option, override)
|
|
303
306
|
} else {
|
|
304
|
-
formattedConfigObject[key] = override
|
|
307
|
+
formattedConfigObject[key] = override
|
|
305
308
|
}
|
|
306
309
|
}
|
|
307
310
|
}
|
|
308
311
|
|
|
309
|
-
return formattedConfigObject
|
|
310
|
-
}
|
|
312
|
+
return formattedConfigObject
|
|
313
|
+
}
|
|
311
314
|
|
|
312
315
|
/**
|
|
313
316
|
* Schema for component config
|