-
Notifications
You must be signed in to change notification settings - Fork 81
/
KDropdownMenu.vue
147 lines (139 loc) · 4.25 KB
/
KDropdownMenu.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
<template>
<UiPopover
ref="popover"
:z-index="12"
:containFocus="true"
:dropdownPosition="position"
@close="handleClose"
@open="handleOpen"
>
<UiMenu
ref="menu"
:options="options"
:hasIcons="hasIcons"
@select="handleSelection"
/>
</UiPopover>
</template>
<script>
import UiPopover from './keen/UiPopover';
import UiMenu from './keen/UiMenu';
/**
* The KDropdownMenu component is used to contain multiple actions
*/
export default {
name: 'KDropdownMenu',
components: {
UiPopover,
UiMenu,
},
props: {
/**
* An array of options objects, with one object per dropdown item
*/
options: {
type: Array,
required: true,
},
/**
* Whether or not the options display an icon
*/
hasIcons: {
type: Boolean,
default: false,
},
/**
* The position of the dropdown relative to the button
*/
position: {
type: String,
default: 'bottom right',
validator(val) {
return [
'bottom left',
'bottom center',
'bottom right',
'top left',
'top center',
'top right',
'left top',
'left middle',
'left bottom',
'right top',
'right middle',
'right bottom',
].includes(val);
},
},
},
beforeDestroy() {
window.removeEventListener('keydown', this.handleOpenMenuNavigation, true);
},
methods: {
handleOpen() {
this.$nextTick(() => this.$nextTick(() => this.setFocus()));
window.addEventListener('keydown', this.handleOpenMenuNavigation, true);
},
setFocus() {
this.$refs.menu.$el.querySelector('li').focus();
},
handleClose() {
const focusedElement = document.activeElement;
const popover = this.$refs.popover.$el;
if (
popover.contains(focusedElement) &&
(focusedElement.classList.contains('ui-popover') ||
focusedElement.classList.contains('ui-popover-focus-redirector') ||
focusedElement.classList.contains('ui-menu-option'))
) {
this.focusOnButton();
}
window.removeEventListener('keyup', this.handleKeyUp, true);
},
handleOpenMenuNavigation(event) {
// identify the menu state and length
if (!this.$refs.popover && !this.$refs.popover.$el) {
return;
}
const popover = this.$refs.popover.$el;
const menuElements = this.$refs.menu.$el.querySelector('div').children;
const lastChild = menuElements[menuElements.length - 1];
const popoverIsOpen = popover.clientWidth > 0 && popover.clientHeight > 0;
// set current element and its siblings
let focusedElement = document.activeElement;
let sibling = focusedElement.nextElementSibling;
let prevSibling = focusedElement.previousElementSibling;
// manage rotating through the options using arrow keys
// UP arrow: .keyCode is depricated and should used only as a fallback
if ((event.key == 'ArrowUp' || event.keyCode == 38) && popoverIsOpen) {
event.preventDefault();
prevSibling
? this.$nextTick(() => prevSibling.focus())
: this.$nextTick(() => lastChild.focus());
// DOWN arrow
} else if ((event.key == 'ArrowDown' || event.keyCode == 40) && popoverIsOpen) {
event.preventDefault();
sibling ? this.$nextTick(() => sibling.focus()) : this.$nextTick(() => this.setFocus());
// if a TAB key, not an arrow key, close the popover and advance to next item in the tab index
} else if ((event.key == 'Tab' || event.keyCode == 9) && popoverIsOpen) {
this.closePopover();
}
},
handleSelection(selection) {
/**
* Emitted when an option is selected
*/
this.$emit('select', selection);
this.closePopover();
},
closePopover() {
this.$refs.popover.close();
},
focusOnButton() {
if (this.$refs.button) {
this.$refs.button.$el.focus();
}
},
},
};
</script>