mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2026-01-24 02:08:10 +00:00
201 lines
4.3 KiB
Vue
201 lines
4.3 KiB
Vue
<template>
|
||
<transition name="fade">
|
||
<div
|
||
v-show="visible"
|
||
class="magic-editor-content-menu"
|
||
ref="menu"
|
||
:style="menuStyle"
|
||
@mouseenter="mouseenterHandler()"
|
||
>
|
||
<slot name="title"></slot>
|
||
<div>
|
||
<ToolButton
|
||
v-for="(item, index) in menuData"
|
||
event-type="mouseup"
|
||
ref="buttons"
|
||
:class="{ active: active && item.id === active }"
|
||
:data="item"
|
||
:key="index"
|
||
@mouseup="clickHandler"
|
||
@mouseenter="showSubMenu(item, index)"
|
||
></ToolButton>
|
||
</div>
|
||
<teleport to="body">
|
||
<content-menu
|
||
v-if="subMenuData.length"
|
||
class="sub-menu"
|
||
ref="subMenu"
|
||
:active="active"
|
||
:menu-data="subMenuData"
|
||
:is-sub-menu="true"
|
||
@hide="hide"
|
||
></content-menu>
|
||
</teleport>
|
||
</div>
|
||
</transition>
|
||
</template>
|
||
|
||
<script lang="ts" setup>
|
||
import { computed, nextTick, onBeforeUnmount, onMounted, ref } from 'vue';
|
||
|
||
import { useZIndex } from '@tmagic/design';
|
||
|
||
import { MenuButton, MenuComponent } from '@editor/type';
|
||
|
||
import ToolButton from './ToolButton.vue';
|
||
|
||
defineOptions({
|
||
name: 'MEditorContentMenu',
|
||
});
|
||
|
||
const props = withDefaults(
|
||
defineProps<{
|
||
menuData?: (MenuButton | MenuComponent)[];
|
||
isSubMenu?: boolean;
|
||
active?: string | number;
|
||
autoHide?: boolean;
|
||
}>(),
|
||
{
|
||
menuData: () => [],
|
||
isSubMenu: false,
|
||
autoHide: true,
|
||
},
|
||
);
|
||
|
||
const emit = defineEmits<{
|
||
hide: [];
|
||
show: [];
|
||
mouseenter: [];
|
||
}>();
|
||
|
||
const menu = ref<HTMLDivElement>();
|
||
const buttons = ref<InstanceType<typeof ToolButton>[]>();
|
||
const subMenu = ref<any>();
|
||
const visible = ref(false);
|
||
const subMenuData = ref<(MenuButton | MenuComponent)[]>([]);
|
||
const zIndex = useZIndex();
|
||
const curZIndex = ref<number>(0);
|
||
|
||
const menuPosition = ref({
|
||
left: 0,
|
||
top: 0,
|
||
});
|
||
|
||
const menuStyle = computed(() => ({
|
||
top: `${menuPosition.value.top}px`,
|
||
left: `${menuPosition.value.left}px`,
|
||
zIndex: curZIndex.value,
|
||
}));
|
||
|
||
const contains = (el: HTMLElement) => menu.value?.contains(el) || subMenu.value?.contains(el);
|
||
|
||
const hide = () => {
|
||
if (!visible.value) return;
|
||
|
||
visible.value = false;
|
||
subMenu.value?.hide();
|
||
|
||
emit('hide');
|
||
};
|
||
|
||
const clickHandler = () => {
|
||
if (!props.autoHide) return;
|
||
|
||
hide();
|
||
};
|
||
|
||
const outsideClickHideHandler = (e: MouseEvent) => {
|
||
if (!props.autoHide) return;
|
||
|
||
const target = e.target as HTMLElement | undefined;
|
||
if (!visible.value || !target) {
|
||
return;
|
||
}
|
||
if (contains(target)) {
|
||
return;
|
||
}
|
||
hide();
|
||
};
|
||
|
||
const setPosition = (e: { clientY: number; clientX: number }) => {
|
||
const menuHeight = menu.value?.clientHeight || 0;
|
||
|
||
let top = e.clientY;
|
||
if (menuHeight + e.clientY > document.body.clientHeight) {
|
||
top = document.body.clientHeight - menuHeight;
|
||
}
|
||
|
||
menuPosition.value = {
|
||
top,
|
||
left: e.clientX,
|
||
};
|
||
};
|
||
|
||
const show = (e?: { clientY: number; clientX: number }) => {
|
||
// 加setTimeout是以为,如果菜单中的按钮监听的是mouseup,那么菜单显示出来时鼠标如果正好在菜单上就会马上触发按钮的mouseup
|
||
setTimeout(() => {
|
||
visible.value = true;
|
||
|
||
nextTick(() => {
|
||
e && setPosition(e);
|
||
|
||
curZIndex.value = zIndex.nextZIndex();
|
||
|
||
emit('show');
|
||
});
|
||
}, 300);
|
||
};
|
||
|
||
const showSubMenu = (item: MenuButton | MenuComponent, index: number) => {
|
||
const menuItem = item as MenuButton;
|
||
if (typeof item !== 'object') {
|
||
return;
|
||
}
|
||
|
||
subMenuData.value = menuItem.items || [];
|
||
setTimeout(() => {
|
||
if (!visible.value) {
|
||
return;
|
||
}
|
||
|
||
if (menu.value) {
|
||
// 将子菜单放置在按钮右侧,与按钮齐平
|
||
let y = menu.value.offsetTop;
|
||
if (buttons.value?.[index].$el) {
|
||
const rect = buttons.value?.[index].$el.getBoundingClientRect();
|
||
y = rect.top;
|
||
}
|
||
subMenu.value?.show({
|
||
clientX: menu.value.offsetLeft + menu.value.clientWidth,
|
||
clientY: y,
|
||
});
|
||
}
|
||
}, 0);
|
||
};
|
||
|
||
const mouseenterHandler = () => {
|
||
emit('mouseenter');
|
||
};
|
||
|
||
onMounted(() => {
|
||
if (props.isSubMenu) return;
|
||
|
||
globalThis.addEventListener('mousedown', outsideClickHideHandler, true);
|
||
});
|
||
|
||
onBeforeUnmount(() => {
|
||
if (props.isSubMenu) return;
|
||
|
||
globalThis.removeEventListener('mousedown', outsideClickHideHandler, true);
|
||
});
|
||
|
||
defineExpose({
|
||
menu,
|
||
menuPosition,
|
||
hide,
|
||
show,
|
||
contains,
|
||
setPosition,
|
||
});
|
||
</script>
|