Improve context menus
This commit is contained in:
parent
9147772cb2
commit
85907f54ba
@ -1,19 +1,34 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="isOpen" id="context-menu-container" @click="close" @contextmenu.prevent="close">
|
<div
|
||||||
<ul id="context-menu" ref="contextMenu" role="menu" :style="style">
|
v-if="isOpen"
|
||||||
<li
|
id="context-menu-container"
|
||||||
v-for="item of items"
|
@click="containerClick"
|
||||||
:key="item.name"
|
@contextmenu.prevent="containerClick"
|
||||||
:class="[
|
>
|
||||||
'context-menu-' + item.type,
|
<ul
|
||||||
item.class ? 'context-menu-' + item.class : null,
|
id="context-menu"
|
||||||
]"
|
ref="contextMenu"
|
||||||
tabindex="0"
|
role="menu"
|
||||||
role="menuitem"
|
:style="style"
|
||||||
@click="clickItem(item)"
|
tabindex="-1"
|
||||||
>
|
@mouseleave="activeItem = -1"
|
||||||
{{ item.label }}
|
@keydown.enter.prevent="clickActiveItem"
|
||||||
</li>
|
>
|
||||||
|
<template v-for="(item, id) of items">
|
||||||
|
<li
|
||||||
|
:key="item.name"
|
||||||
|
:class="[
|
||||||
|
'context-menu-' + item.type,
|
||||||
|
item.class ? 'context-menu-' + item.class : null,
|
||||||
|
{active: id === activeItem},
|
||||||
|
]"
|
||||||
|
role="menuitem"
|
||||||
|
@mouseover="hoverItem(id)"
|
||||||
|
@click="clickItem(item)"
|
||||||
|
>
|
||||||
|
{{ item.label }}
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -31,6 +46,7 @@ export default {
|
|||||||
isOpen: false,
|
isOpen: false,
|
||||||
previousActiveElement: null,
|
previousActiveElement: null,
|
||||||
items: [],
|
items: [],
|
||||||
|
activeItem: -1,
|
||||||
style: {
|
style: {
|
||||||
left: 0,
|
left: 0,
|
||||||
top: 0,
|
top: 0,
|
||||||
@ -39,57 +55,27 @@ export default {
|
|||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
Mousetrap.bind("esc", this.close);
|
Mousetrap.bind("esc", this.close);
|
||||||
|
Mousetrap.bind(["up", "down", "tab", "shift+tab"], this.navigateMenu);
|
||||||
const trap = Mousetrap(this.$refs.contextMenu);
|
|
||||||
|
|
||||||
trap.bind(["up", "down"], (e, key) => {
|
|
||||||
if (!this.isOpen) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const items = this.$refs.contextMenu.querySelectorAll(".context-menu-item");
|
|
||||||
let index = Array.from(items).findIndex((item) => item === document.activeElement);
|
|
||||||
|
|
||||||
if (key === "down") {
|
|
||||||
index = (index + 1) % items.length;
|
|
||||||
} else {
|
|
||||||
index = Math.max(index, 0) - 1;
|
|
||||||
|
|
||||||
if (index < 0) {
|
|
||||||
index = items.length + index;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
items[index].focus();
|
|
||||||
});
|
|
||||||
|
|
||||||
trap.bind("enter", () => {
|
|
||||||
if (!this.isOpen) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const item = this.$refs.contextMenu.querySelector(":focus");
|
|
||||||
item.click();
|
|
||||||
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
destroyed() {
|
destroyed() {
|
||||||
Mousetrap.unbind("esc", this.close);
|
Mousetrap.unbind("esc", this.close);
|
||||||
|
Mousetrap.unbind(["up", "down", "tab", "shift+tab"], this.navigateMenu);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
open(event, items) {
|
open(event, items) {
|
||||||
this.items = items;
|
event.preventDefault();
|
||||||
this.isOpen = true;
|
|
||||||
this.previousActiveElement = document.activeElement;
|
this.previousActiveElement = document.activeElement;
|
||||||
|
this.items = items;
|
||||||
|
this.activeItem = 0;
|
||||||
|
this.isOpen = true;
|
||||||
|
|
||||||
// Position the menu and set the focus on the first item after it's size has updated
|
// Position the menu and set the focus on the first item after it's size has updated
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
const pos = this.positionContextMenu(event);
|
const pos = this.positionContextMenu(event);
|
||||||
this.style.left = pos.left + "px";
|
this.style.left = pos.left + "px";
|
||||||
this.style.top = pos.top + "px";
|
this.style.top = pos.top + "px";
|
||||||
|
this.$refs.contextMenu.focus();
|
||||||
this.$refs.contextMenu.querySelector(".context-menu-item:first-child").focus();
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
close() {
|
close() {
|
||||||
@ -98,17 +84,59 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.isOpen = false;
|
this.isOpen = false;
|
||||||
|
this.items = [];
|
||||||
|
|
||||||
if (this.previousActiveElement) {
|
if (this.previousActiveElement) {
|
||||||
this.previousActiveElement.focus();
|
this.previousActiveElement.focus();
|
||||||
this.previousActiveElement = null;
|
this.previousActiveElement = null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
hoverItem(id) {
|
||||||
|
this.activeItem = id;
|
||||||
|
},
|
||||||
clickItem(item) {
|
clickItem(item) {
|
||||||
if (item.action) {
|
if (item.action) {
|
||||||
item.action();
|
item.action();
|
||||||
|
this.close();
|
||||||
} else if (item.link) {
|
} else if (item.link) {
|
||||||
this.$router.push(item.link);
|
this.$router.push(item.link);
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
clickActiveItem() {
|
||||||
|
if (this.items[this.activeItem]) {
|
||||||
|
this.clickItem(this.items[this.activeItem]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
navigateMenu(event, key) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const direction = key === "down" || key === "tab" ? 1 : -1;
|
||||||
|
|
||||||
|
let currentIndex = this.activeItem;
|
||||||
|
|
||||||
|
currentIndex += direction;
|
||||||
|
|
||||||
|
const nextItem = this.items[currentIndex];
|
||||||
|
|
||||||
|
// If the next item we would select is a divider, skip over it
|
||||||
|
if (nextItem && !nextItem.action && !nextItem.link) {
|
||||||
|
currentIndex += direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentIndex < 0) {
|
||||||
|
currentIndex += this.items.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentIndex > this.items.length - 1) {
|
||||||
|
currentIndex -= this.items.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.activeItem = currentIndex;
|
||||||
|
},
|
||||||
|
containerClick(event) {
|
||||||
|
if (event.currentTarget === event.target) {
|
||||||
|
this.close();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
positionContextMenu(event) {
|
positionContextMenu(event) {
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
:data-name="user.nick"
|
:data-name="user.nick"
|
||||||
role="button"
|
role="button"
|
||||||
v-on="onHover ? {mouseover: hover} : {}"
|
v-on="onHover ? {mouseover: hover} : {}"
|
||||||
|
@click.prevent="rightClick($event)"
|
||||||
@contextmenu.prevent="rightClick($event)"
|
@contextmenu.prevent="rightClick($event)"
|
||||||
>{{ user.mode }}{{ user.nick }}</span
|
>{{ user.mode }}{{ user.nick }}</span
|
||||||
>
|
>
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
:data-name="user.original.nick"
|
:data-name="user.original.nick"
|
||||||
role="button"
|
role="button"
|
||||||
@mouseover="hover"
|
@mouseover="hover"
|
||||||
|
@click.prevent="rightClick($event)"
|
||||||
@contextmenu.prevent="rightClick($event)"
|
@contextmenu.prevent="rightClick($event)"
|
||||||
v-html="user.original.mode + user.string"
|
v-html="user.original.mode + user.string"
|
||||||
/>
|
/>
|
||||||
|
@ -2088,6 +2088,7 @@ part/quit messages where we don't load previews (adds a blank line otherwise) */
|
|||||||
box-shadow: 0 3px 12px rgba(0, 0, 0, 0.15);
|
box-shadow: 0 3px 12px rgba(0, 0, 0, 0.15);
|
||||||
border: 1px solid rgba(0, 0, 0, 0.15);
|
border: 1px solid rgba(0, 0, 0, 0.15);
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
|
outline: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.context-menu-divider {
|
.context-menu-divider {
|
||||||
@ -2109,15 +2110,13 @@ part/quit messages where we don't load previews (adds a blank line otherwise) */
|
|||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.context-menu-item:focus,
|
.context-menu-item.active,
|
||||||
.textcomplete-item:focus,
|
.textcomplete-item:focus,
|
||||||
.context-menu-item:hover,
|
|
||||||
.textcomplete-item:hover,
|
.textcomplete-item:hover,
|
||||||
.textcomplete-menu .active,
|
.textcomplete-menu .active,
|
||||||
#chat .userlist .user.active {
|
#chat .userlist .user.active {
|
||||||
background-color: rgba(0, 0, 0, 0.1);
|
background-color: rgba(0, 0, 0, 0.1);
|
||||||
transition: none;
|
transition: none;
|
||||||
outline: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.context-menu-item::before,
|
.context-menu-item::before,
|
||||||
|
Loading…
Reference in New Issue
Block a user