@veritree/ui 0.20.1 → 0.21.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.
- package/mixins/floating-ui.js +68 -0
- package/package.json +2 -1
- package/src/components/DropdownMenu/VTDropdownMenu.vue +23 -14
- package/src/components/DropdownMenu/VTDropdownMenuContent.vue +46 -41
- package/src/components/DropdownMenu/VTDropdownMenuItem.vue +33 -12
- package/src/components/DropdownMenu/VTDropdownMenuTrigger.vue +70 -62
- package/src/components/Form/VTFormFeedback.vue +33 -22
- package/src/components/Form/VTInput.vue +52 -0
- package/src/components/Listbox/VTListbox.vue +22 -36
- package/src/components/Listbox/VTListboxContent.vue +38 -65
- package/src/components/Listbox/VTListboxItem.vue +12 -12
- package/src/components/Listbox/VTListboxSearch.vue +4 -8
- package/src/components/Listbox/VTListboxTrigger.vue +40 -18
- package/src/components/Popover/VTPopover.vue +23 -16
- package/src/components/Popover/VTPopoverContent.vue +52 -36
- package/src/components/Popover/VTPopoverTrigger.vue +16 -8
- package/src/components/Utils/FloatingUi.vue +56 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { computePosition, flip, shift, offset, size } from '@floating-ui/dom';
|
|
2
|
+
|
|
3
|
+
export const floatingUiMixin = {
|
|
4
|
+
props: {
|
|
5
|
+
placement: {
|
|
6
|
+
type: String,
|
|
7
|
+
default: 'bottom-start'
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
|
|
11
|
+
data() {
|
|
12
|
+
return {
|
|
13
|
+
component: null,
|
|
14
|
+
componentTrigger: null,
|
|
15
|
+
componentContent: null,
|
|
16
|
+
active: false,
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
watch: {
|
|
21
|
+
active(newVal) {
|
|
22
|
+
if (newVal) this.$nextTick(() => this.positionContentToTrigger());
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
mounted() {
|
|
27
|
+
this.component = {
|
|
28
|
+
setActive: this.setActive,
|
|
29
|
+
clearActive: this.clearActive,
|
|
30
|
+
};
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
methods: {
|
|
34
|
+
setActive() {
|
|
35
|
+
this.active = true;
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
clearActive() {
|
|
39
|
+
this.active = null;
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
positionContentToTrigger() {
|
|
43
|
+
const trigger = document.getElementById(this.componentTrigger.id);
|
|
44
|
+
const content = document.getElementById(this.componentContent.id);
|
|
45
|
+
|
|
46
|
+
computePosition(trigger, content, {
|
|
47
|
+
placement: this.placement,
|
|
48
|
+
middleware: [
|
|
49
|
+
offset(6),
|
|
50
|
+
flip(),
|
|
51
|
+
shift({ padding: 5 }),
|
|
52
|
+
size({
|
|
53
|
+
apply({ rects }) {
|
|
54
|
+
Object.assign(content.style, {
|
|
55
|
+
width: `${rects.reference.width}px`,
|
|
56
|
+
});
|
|
57
|
+
},
|
|
58
|
+
}),
|
|
59
|
+
],
|
|
60
|
+
}).then(({ x, y }) => {
|
|
61
|
+
Object.assign(content.style, {
|
|
62
|
+
left: `${x}px`,
|
|
63
|
+
top: `${y}px`,
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
},
|
|
67
|
+
}
|
|
68
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@veritree/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.1-0",
|
|
4
4
|
"description": "veritree ui library",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"access": "public"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
+
"@floating-ui/dom": "^1.0.4",
|
|
14
15
|
"@linusborg/vue-simple-portal": "^0.1.5",
|
|
15
16
|
"@veritree/icons": "^0.19.0"
|
|
16
17
|
},
|
|
@@ -5,26 +5,31 @@
|
|
|
5
5
|
</template>
|
|
6
6
|
|
|
7
7
|
<script>
|
|
8
|
-
import {
|
|
8
|
+
import { floatingUiMixin } from '../../../mixins/floating-ui';
|
|
9
|
+
import { genId } from '../../utils/ids';
|
|
9
10
|
|
|
10
11
|
export default {
|
|
11
|
-
name:
|
|
12
|
+
name: 'VTDropdownMenu',
|
|
13
|
+
|
|
14
|
+
mixins: [floatingUiMixin],
|
|
12
15
|
|
|
13
16
|
provide() {
|
|
14
17
|
return {
|
|
15
18
|
api: () => {
|
|
16
|
-
const { dark: isDark, headless: isHeadless
|
|
17
|
-
const { id, trigger, content, items } = this;
|
|
19
|
+
const { dark: isDark, headless: isHeadless } = this;
|
|
18
20
|
|
|
19
21
|
const registerTrigger = (trigger) => {
|
|
20
|
-
|
|
22
|
+
if (!trigger) return;
|
|
23
|
+
this.componentTrigger = trigger;
|
|
21
24
|
};
|
|
22
25
|
|
|
23
26
|
const registerContent = (content) => {
|
|
24
|
-
|
|
27
|
+
if (!content) return;
|
|
28
|
+
this.componentContent = content;
|
|
25
29
|
};
|
|
26
30
|
|
|
27
31
|
const registerItem = (item) => {
|
|
32
|
+
if (!item) return;
|
|
28
33
|
this.items.push(item);
|
|
29
34
|
};
|
|
30
35
|
|
|
@@ -33,13 +38,13 @@ export default {
|
|
|
33
38
|
};
|
|
34
39
|
|
|
35
40
|
return {
|
|
36
|
-
id,
|
|
41
|
+
id: this.componentId,
|
|
37
42
|
isDark,
|
|
38
43
|
isHeadless,
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
items,
|
|
44
|
+
component: this.component,
|
|
45
|
+
componentTrigger: this.componentTrigger,
|
|
46
|
+
componentContent: this.componentContent,
|
|
47
|
+
items: this.items,
|
|
43
48
|
registerTrigger,
|
|
44
49
|
registerContent,
|
|
45
50
|
registerItem,
|
|
@@ -66,11 +71,15 @@ export default {
|
|
|
66
71
|
|
|
67
72
|
data() {
|
|
68
73
|
return {
|
|
69
|
-
|
|
70
|
-
trigger: null,
|
|
71
|
-
content: null,
|
|
74
|
+
componentId: genId(),
|
|
72
75
|
items: [],
|
|
73
76
|
};
|
|
74
77
|
},
|
|
78
|
+
|
|
79
|
+
computed: {
|
|
80
|
+
id() {
|
|
81
|
+
return `dropdown-menu-${this.componentId}`;
|
|
82
|
+
},
|
|
83
|
+
},
|
|
75
84
|
};
|
|
76
85
|
</script>
|
|
@@ -1,47 +1,37 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
leave-class="translate-y-0 opacity-100"
|
|
8
|
-
leave-to-class="translate-y-[15px] opacity-0"
|
|
9
|
-
@after-leave="hide"
|
|
2
|
+
<FloatingUi
|
|
3
|
+
:visible="visible"
|
|
4
|
+
:id="id"
|
|
5
|
+
:headless="headless"
|
|
6
|
+
:class="{ 'dropdown-menu-content': headless }"
|
|
10
7
|
>
|
|
11
|
-
<
|
|
12
|
-
|
|
13
|
-
:id="id"
|
|
14
|
-
:class="{
|
|
15
|
-
MenuList: headless,
|
|
16
|
-
'absolute top-full mt-3 grid min-w-[222px] overflow-hidden rounded border border-solid py-2 px-3 transition-all':
|
|
17
|
-
!headless,
|
|
18
|
-
'border-gray-100 bg-white shadow-300': !dark,
|
|
19
|
-
'bg-forest-default border-gray-700 shadow-gray-700': dark,
|
|
20
|
-
'left-0': !right,
|
|
21
|
-
'right-0': right,
|
|
22
|
-
}"
|
|
23
|
-
>
|
|
24
|
-
<slot></slot>
|
|
25
|
-
</div>
|
|
26
|
-
</transition>
|
|
8
|
+
<slot></slot>
|
|
9
|
+
</FloatingUi>
|
|
27
10
|
</template>
|
|
28
11
|
|
|
29
12
|
<script>
|
|
30
|
-
import
|
|
13
|
+
import FloatingUi from '../Utils/FloatingUi.vue';
|
|
31
14
|
|
|
32
15
|
export default {
|
|
33
16
|
name: 'VTDropdownMenuContent',
|
|
34
17
|
|
|
18
|
+
components: {
|
|
19
|
+
FloatingUi,
|
|
20
|
+
},
|
|
21
|
+
|
|
35
22
|
inject: ['api'],
|
|
36
23
|
|
|
37
24
|
data() {
|
|
38
25
|
return {
|
|
39
|
-
id: `menucontent-${genId()}`,
|
|
40
26
|
visible: false,
|
|
41
27
|
};
|
|
42
28
|
},
|
|
43
29
|
|
|
44
30
|
computed: {
|
|
31
|
+
id() {
|
|
32
|
+
return `dropdown-menu-content-${this.api().id}`;
|
|
33
|
+
},
|
|
34
|
+
|
|
45
35
|
dark() {
|
|
46
36
|
return this.api().isDark;
|
|
47
37
|
},
|
|
@@ -50,44 +40,59 @@ export default {
|
|
|
50
40
|
return this.api().isHeadless;
|
|
51
41
|
},
|
|
52
42
|
|
|
53
|
-
|
|
54
|
-
return this.api().
|
|
43
|
+
component() {
|
|
44
|
+
return this.api().component;
|
|
55
45
|
},
|
|
56
46
|
|
|
57
|
-
|
|
58
|
-
return this.api().
|
|
47
|
+
componentTrigger() {
|
|
48
|
+
return this.api().componentTrigger;
|
|
59
49
|
},
|
|
60
50
|
},
|
|
61
51
|
|
|
62
52
|
mounted() {
|
|
63
53
|
this.api().registerContent(this);
|
|
64
54
|
|
|
65
|
-
|
|
66
|
-
if (this.trigger) this.trigger.toggleHasPopup();
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
// TODO: Create a directive or mixin for this
|
|
55
|
+
// T-218 Create a directive or mixin for this
|
|
70
56
|
document.addEventListener('click', (e) => {
|
|
57
|
+
if (!e) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
71
61
|
e.stopPropagation();
|
|
72
|
-
|
|
62
|
+
|
|
63
|
+
if (this.visible && !this.$el.contains(e.target)) {
|
|
64
|
+
this.componentTrigger.onClick();
|
|
65
|
+
}
|
|
73
66
|
});
|
|
74
67
|
},
|
|
75
68
|
|
|
76
69
|
destroyed() {
|
|
77
|
-
|
|
78
|
-
document.removeEventListener('click', this.trigger.onClick());
|
|
70
|
+
document.removeEventListener('click', this.componentTrigger.onClick);
|
|
79
71
|
},
|
|
80
72
|
|
|
81
73
|
methods: {
|
|
82
74
|
show() {
|
|
75
|
+
if (this.visible) return;
|
|
76
|
+
|
|
83
77
|
this.visible = true;
|
|
84
|
-
|
|
78
|
+
|
|
79
|
+
this.$nextTick(() => {
|
|
80
|
+
this.component.setActive();
|
|
81
|
+
this.$emit('shown');
|
|
82
|
+
});
|
|
85
83
|
},
|
|
86
84
|
|
|
87
85
|
hide() {
|
|
86
|
+
if (!this.visible) return;
|
|
87
|
+
|
|
88
88
|
this.visible = false;
|
|
89
|
-
|
|
90
|
-
this
|
|
89
|
+
|
|
90
|
+
this.$nextTick(() => {
|
|
91
|
+
this.componentTrigger.focus();
|
|
92
|
+
this.componentTrigger.hideExpanded();
|
|
93
|
+
this.component.clearActive();
|
|
94
|
+
this.$emit('hidden');
|
|
95
|
+
});
|
|
91
96
|
},
|
|
92
97
|
},
|
|
93
98
|
};
|
|
@@ -5,11 +5,12 @@
|
|
|
5
5
|
:to="to"
|
|
6
6
|
:class="{
|
|
7
7
|
MenuItem: headless,
|
|
8
|
-
'-mx-3 flex min-w-max items-center gap-
|
|
8
|
+
'-mx-3 flex min-w-max items-center gap-2 px-3 py-2 text-inherit no-underline':
|
|
9
9
|
!headless,
|
|
10
|
-
'hover:bg-secondary-200/10': !dark,
|
|
11
|
-
'text-white
|
|
12
|
-
'pointer-events-none opacity-75': disabled,
|
|
10
|
+
'hover:bg-secondary-200/10': !dark && !headless,
|
|
11
|
+
'text-white': dark && !headless,
|
|
12
|
+
'pointer-events-none opacity-75': disabled && !headless,
|
|
13
|
+
'bg-secondary-200/10': selected && !headless,
|
|
13
14
|
}"
|
|
14
15
|
:tabindex="tabIndex"
|
|
15
16
|
:aria-disabled="disabled"
|
|
@@ -52,13 +53,17 @@ export default {
|
|
|
52
53
|
|
|
53
54
|
data() {
|
|
54
55
|
return {
|
|
55
|
-
id: `menuitem-${genId()}`,
|
|
56
56
|
index: null,
|
|
57
|
+
selected: false,
|
|
57
58
|
tabIndex: 0,
|
|
58
59
|
};
|
|
59
60
|
},
|
|
60
61
|
|
|
61
62
|
computed: {
|
|
63
|
+
id() {
|
|
64
|
+
return `dropdown-menu-item-${this.api().id}-${genId()}`;
|
|
65
|
+
},
|
|
66
|
+
|
|
62
67
|
dark() {
|
|
63
68
|
return this.api().isDark;
|
|
64
69
|
},
|
|
@@ -79,19 +84,20 @@ export default {
|
|
|
79
84
|
return this.$el;
|
|
80
85
|
},
|
|
81
86
|
|
|
82
|
-
|
|
83
|
-
return this.api().
|
|
87
|
+
componentTrigger() {
|
|
88
|
+
return this.api().componentTrigger;
|
|
84
89
|
},
|
|
85
90
|
|
|
86
|
-
|
|
87
|
-
return this.api().
|
|
91
|
+
componentContent() {
|
|
92
|
+
return this.api().componentContent;
|
|
88
93
|
},
|
|
89
94
|
},
|
|
90
95
|
|
|
91
96
|
mounted() {
|
|
92
97
|
const item = {
|
|
98
|
+
select: this.select,
|
|
99
|
+
unselect: this.unselect,
|
|
93
100
|
focus: this.focus,
|
|
94
|
-
el: this.el,
|
|
95
101
|
};
|
|
96
102
|
|
|
97
103
|
this.api().registerItem(item);
|
|
@@ -100,10 +106,19 @@ export default {
|
|
|
100
106
|
},
|
|
101
107
|
|
|
102
108
|
methods: {
|
|
109
|
+
select() {
|
|
110
|
+
this.selected = true;
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
unselect() {
|
|
114
|
+
this.selected = false;
|
|
115
|
+
},
|
|
116
|
+
|
|
103
117
|
focus() {
|
|
104
118
|
if (!this.el) return;
|
|
105
119
|
|
|
106
120
|
this.tabIndex = -1;
|
|
121
|
+
this.selected = true;
|
|
107
122
|
this.el.focus();
|
|
108
123
|
},
|
|
109
124
|
|
|
@@ -145,6 +160,12 @@ export default {
|
|
|
145
160
|
*/
|
|
146
161
|
setFocusToItem(goToIndex) {
|
|
147
162
|
this.tabIndex = 0;
|
|
163
|
+
this.selected = false;
|
|
164
|
+
|
|
165
|
+
// set all selected to false
|
|
166
|
+
this.items.forEach((item) => item.unselect());
|
|
167
|
+
|
|
168
|
+
// focus item
|
|
148
169
|
this.items[goToIndex].focus();
|
|
149
170
|
},
|
|
150
171
|
|
|
@@ -152,8 +173,8 @@ export default {
|
|
|
152
173
|
* Hides content/menu and focus on trigger
|
|
153
174
|
*/
|
|
154
175
|
leaveMenu() {
|
|
155
|
-
if (this.
|
|
156
|
-
if (this.
|
|
176
|
+
if (this.componentContent) this.componentContent.hide();
|
|
177
|
+
if (this.componentTrigger) this.componentTrigger.focus();
|
|
157
178
|
},
|
|
158
179
|
|
|
159
180
|
onKeyEsc() {
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div
|
|
3
|
+
:id="id"
|
|
3
4
|
:aria-haspopup="hasPopup"
|
|
4
5
|
:aria-expanded="expanded"
|
|
5
6
|
:aria-controls="controls"
|
|
6
7
|
@keydown.down.prevent="onKeyArrowDown"
|
|
7
8
|
@keydown.up.prevent="onKeyArrowUp"
|
|
8
|
-
@keydown.esc.
|
|
9
|
+
@keydown.esc.stop="onKeyesc"
|
|
9
10
|
>
|
|
10
11
|
<slot></slot>
|
|
11
12
|
</div>
|
|
@@ -22,21 +23,17 @@ export default {
|
|
|
22
23
|
expanded: false,
|
|
23
24
|
hasPopup: false,
|
|
24
25
|
controls: null,
|
|
26
|
+
trigger: null,
|
|
25
27
|
};
|
|
26
28
|
},
|
|
27
29
|
|
|
28
30
|
computed: {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
return this.$slots.default[0].elm;
|
|
31
|
+
id() {
|
|
32
|
+
return `dropdown-menu-trigger-${this.api().id}`;
|
|
32
33
|
},
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
return this
|
|
36
|
-
},
|
|
37
|
-
|
|
38
|
-
content() {
|
|
39
|
-
return this.api().content;
|
|
35
|
+
componentContent() {
|
|
36
|
+
return this.api().componentContent;
|
|
40
37
|
},
|
|
41
38
|
|
|
42
39
|
items() {
|
|
@@ -53,15 +50,30 @@ export default {
|
|
|
53
50
|
},
|
|
54
51
|
|
|
55
52
|
mounted() {
|
|
56
|
-
|
|
57
|
-
|
|
53
|
+
const trigger = {
|
|
54
|
+
toggleExpanded: this.toggleExpanded,
|
|
55
|
+
hideExpanded: this.hideExpanded,
|
|
56
|
+
el: this.$el,
|
|
57
|
+
focus: this.focus,
|
|
58
|
+
id: this.id,
|
|
59
|
+
onClick: this.onClick,
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
this.api().registerTrigger(trigger);
|
|
63
|
+
|
|
64
|
+
this.setTrigger();
|
|
65
|
+
this.addTriggerEvents();
|
|
58
66
|
},
|
|
59
67
|
|
|
60
68
|
destroyed() {
|
|
61
|
-
this.
|
|
69
|
+
this.trigger.removeEventListener('click', this.onClick);
|
|
62
70
|
},
|
|
63
71
|
|
|
64
72
|
methods: {
|
|
73
|
+
setTrigger() {
|
|
74
|
+
this.trigger = this.$el.querySelector(':first-child');
|
|
75
|
+
},
|
|
76
|
+
|
|
65
77
|
/**
|
|
66
78
|
* Add event listener to slot element
|
|
67
79
|
*
|
|
@@ -72,16 +84,9 @@ export default {
|
|
|
72
84
|
* Slot must have only one child element. It avoids
|
|
73
85
|
* errors related to adding the event listener.
|
|
74
86
|
*/
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if (this.slotLength > 1) {
|
|
79
|
-
throw new Error('VTPopoverButton only accepts one item in its slot');
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
this.slotElm.addEventListener('click', (e) => {
|
|
83
|
-
e.stopImmediatePropagation();
|
|
84
|
-
this.onClick();
|
|
87
|
+
addTriggerEvents() {
|
|
88
|
+
this.trigger.addEventListener('click', (e) => {
|
|
89
|
+
this.onClick(e);
|
|
85
90
|
});
|
|
86
91
|
},
|
|
87
92
|
|
|
@@ -89,27 +94,21 @@ export default {
|
|
|
89
94
|
* Shows content/menu if not already visible
|
|
90
95
|
*/
|
|
91
96
|
showContent() {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
this.content.show();
|
|
95
|
-
}
|
|
97
|
+
this.expanded = true;
|
|
98
|
+
this.componentContent.show();
|
|
96
99
|
},
|
|
97
100
|
|
|
98
101
|
/**
|
|
99
102
|
* Focus slot element if it exists and toggle expanded
|
|
100
103
|
*/
|
|
101
104
|
focus() {
|
|
102
|
-
if (
|
|
103
|
-
|
|
104
|
-
this.slotElm.focus();
|
|
105
|
-
this.toggleExpanded();
|
|
105
|
+
if (this.trigger) this.trigger.focus();
|
|
106
106
|
},
|
|
107
107
|
|
|
108
|
-
|
|
109
|
-
* Toggles aria expanded attribute/state
|
|
110
|
-
*/
|
|
108
|
+
//
|
|
111
109
|
toggleExpanded() {
|
|
112
|
-
|
|
110
|
+
if (!this.expanded) return;
|
|
111
|
+
this.expanded = false;
|
|
113
112
|
},
|
|
114
113
|
|
|
115
114
|
/**
|
|
@@ -123,7 +122,7 @@ export default {
|
|
|
123
122
|
* Toggles aria popup/controls attribute/state
|
|
124
123
|
*/
|
|
125
124
|
toggleHasPopup() {
|
|
126
|
-
if (!this.
|
|
125
|
+
if (!this.componentContent) return;
|
|
127
126
|
|
|
128
127
|
this.hasPopup = !this.hasPopup;
|
|
129
128
|
|
|
@@ -132,37 +131,41 @@ export default {
|
|
|
132
131
|
return;
|
|
133
132
|
}
|
|
134
133
|
|
|
135
|
-
this.controls = this.
|
|
134
|
+
this.controls = this.componentContent.id;
|
|
136
135
|
},
|
|
137
136
|
|
|
138
137
|
/**
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
* 1. Toggle aria expanded attribute/state
|
|
142
|
-
* 2. Open the menu if it's closed
|
|
143
|
-
* 3. Close the menu if it's open
|
|
138
|
+
* 1. Set aria expanded attribute/state to false
|
|
139
|
+
* 2. Close the menu
|
|
144
140
|
*/
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
this.toggleExpanded();
|
|
141
|
+
hide() {
|
|
142
|
+
this.hideExpanded();
|
|
149
143
|
|
|
150
144
|
this.$nextTick(() => {
|
|
151
|
-
|
|
152
|
-
else this.content.hide();
|
|
145
|
+
this.componentContent.hide();
|
|
153
146
|
});
|
|
154
147
|
},
|
|
155
148
|
|
|
156
149
|
/**
|
|
157
|
-
*
|
|
158
|
-
*
|
|
150
|
+
* On click, do the following:
|
|
151
|
+
*
|
|
152
|
+
* 1. Toggle aria expanded attribute/state
|
|
153
|
+
* 2. Open the menu if it's closed
|
|
154
|
+
* 3. Close the menu if it's open
|
|
159
155
|
*/
|
|
160
|
-
|
|
161
|
-
this.
|
|
156
|
+
onClick(e) {
|
|
157
|
+
if (!this.componentContent) return;
|
|
162
158
|
|
|
163
|
-
this
|
|
164
|
-
this.
|
|
165
|
-
|
|
159
|
+
if (this.expanded) {
|
|
160
|
+
this.componentContent.hide();
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// delay stop propagation to close other visible
|
|
165
|
+
// dropdowns and delay click event to control
|
|
166
|
+
// this dropdown visibility
|
|
167
|
+
setTimeout(() => e.stopImmediatePropagation(), 50);
|
|
168
|
+
setTimeout(() => this.showContent(), 100);
|
|
166
169
|
},
|
|
167
170
|
|
|
168
171
|
/**
|
|
@@ -172,12 +175,15 @@ export default {
|
|
|
172
175
|
* 2. if the menu is expanded, focus the first menu item
|
|
173
176
|
*/
|
|
174
177
|
onKeyArrowDown() {
|
|
175
|
-
if (!this.
|
|
178
|
+
if (!this.componentContent) return;
|
|
176
179
|
|
|
177
180
|
this.showContent();
|
|
178
181
|
|
|
182
|
+
// settimeout here is delaying the focusing the element
|
|
183
|
+
// since it is not rendered yet. All items will only
|
|
184
|
+
// be available when the content is fully visible.
|
|
179
185
|
this.$nextTick(() => {
|
|
180
|
-
this.firstMenuItem.focus();
|
|
186
|
+
setTimeout(() => this.firstMenuItem.focus(), 150);
|
|
181
187
|
});
|
|
182
188
|
},
|
|
183
189
|
|
|
@@ -188,21 +194,23 @@ export default {
|
|
|
188
194
|
* 2. if the menu is expanded, focus the last menu item
|
|
189
195
|
*/
|
|
190
196
|
onKeyArrowUp() {
|
|
191
|
-
if (!this.
|
|
197
|
+
if (!this.componentContent) return;
|
|
192
198
|
|
|
193
199
|
this.showContent();
|
|
194
200
|
|
|
201
|
+
// settimeout here is delaying the focusing the element
|
|
202
|
+
// since it is not rendered yet. All items will only
|
|
203
|
+
// be available when the content is fully visible.
|
|
195
204
|
this.$nextTick(() => {
|
|
196
|
-
this.lastMenuItem.focus();
|
|
205
|
+
setTimeout(() => this.lastMenuItem.focus(), 150);
|
|
197
206
|
});
|
|
198
207
|
},
|
|
199
208
|
|
|
200
209
|
onKeyesc() {
|
|
201
|
-
if (!this.
|
|
210
|
+
if (!this.componentContent) return;
|
|
202
211
|
|
|
203
212
|
if (this.expanded) {
|
|
204
|
-
this.
|
|
205
|
-
this.content.hide();
|
|
213
|
+
this.componentContent.hide();
|
|
206
214
|
}
|
|
207
215
|
},
|
|
208
216
|
},
|