mirror of
https://github.com/directus/directus.git
synced 2026-04-25 03:00:53 -04:00
v-menu pointer event tweaks (#16512)
* v-menu pointer event tweaks * apply tweak to .v-menu click & closeOnContentClick
This commit is contained in:
9
app/src/components/__snapshots__/v-menu.test.ts.snap
Normal file
9
app/src/components/__snapshots__/v-menu.test.ts.snap
Normal file
@@ -0,0 +1,9 @@
|
||||
// Vitest Snapshot v1
|
||||
|
||||
exports[`Mount component 1`] = `
|
||||
"<div class=\\"v-menu\\" data-v-41b8fe03=\\"\\">
|
||||
<div class=\\"v-menu-activator\\" data-v-41b8fe03=\\"\\"></div>
|
||||
<!--teleport start-->
|
||||
<!--teleport end-->
|
||||
</div>"
|
||||
`;
|
||||
@@ -1,36 +0,0 @@
|
||||
import { test, expect, beforeEach } from 'vitest';
|
||||
import { mount } from '@vue/test-utils';
|
||||
|
||||
import VMenu from './v-menu.vue';
|
||||
import TransitionBounce from './transition/bounce.vue';
|
||||
import { GlobalMountOptions } from '@vue/test-utils/dist/types';
|
||||
import { directive } from '../../../../app/src/directives/click-outside';
|
||||
|
||||
beforeEach(() => {
|
||||
// create teleport target
|
||||
const el = document.createElement('div');
|
||||
el.id = 'menu-outlet';
|
||||
document.body.appendChild(el);
|
||||
});
|
||||
|
||||
const global: GlobalMountOptions = {
|
||||
directives: {
|
||||
'click-outside': directive as any,
|
||||
},
|
||||
components: {
|
||||
TransitionBounce,
|
||||
},
|
||||
};
|
||||
|
||||
test('Mount component', () => {
|
||||
expect(VMenu).toBeTruthy();
|
||||
|
||||
const wrapper = mount(VMenu, {
|
||||
slots: {
|
||||
default: 'Slot Content',
|
||||
},
|
||||
global,
|
||||
});
|
||||
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
});
|
||||
124
app/src/components/v-menu.test.ts
Normal file
124
app/src/components/v-menu.test.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
import { mount } from '@vue/test-utils';
|
||||
import { GlobalMountOptions } from '@vue/test-utils/dist/types';
|
||||
import { beforeEach, expect, test, vi } from 'vitest';
|
||||
|
||||
import { directive } from '@/directives/click-outside';
|
||||
import TransitionBounce from './transition/bounce.vue';
|
||||
import VMenu from './v-menu.vue';
|
||||
|
||||
beforeEach(() => {
|
||||
// create teleport target
|
||||
const el = document.createElement('div');
|
||||
el.id = 'menu-outlet';
|
||||
document.body.appendChild(el);
|
||||
|
||||
// mocking this as it seems like there's observer undefined error in happy-dom
|
||||
// but it is not crucial for the current test cases at the moment
|
||||
vi.spyOn(MutationObserver.prototype, 'disconnect').mockResolvedValue();
|
||||
});
|
||||
|
||||
const global: GlobalMountOptions = {
|
||||
directives: {
|
||||
'click-outside': directive as any,
|
||||
},
|
||||
components: {
|
||||
TransitionBounce,
|
||||
},
|
||||
};
|
||||
|
||||
test('Mount component', () => {
|
||||
expect(VMenu).toBeTruthy();
|
||||
|
||||
const wrapper = mount(VMenu, {
|
||||
slots: {
|
||||
default: 'Slot Content',
|
||||
},
|
||||
global,
|
||||
});
|
||||
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should not have click event listener when trigger is not "click"', () => {
|
||||
const wrapper = mount(VMenu, {
|
||||
global,
|
||||
});
|
||||
|
||||
const vMenuListeners = (wrapper.find('.v-menu').element as any)._vei;
|
||||
expect(vMenuListeners).toBeUndefined();
|
||||
});
|
||||
|
||||
test('should have click event listener when trigger is "click"', () => {
|
||||
const wrapper = mount(VMenu, {
|
||||
props: {
|
||||
trigger: 'click',
|
||||
},
|
||||
global,
|
||||
});
|
||||
|
||||
const vMenuListeners = (wrapper.find('.v-menu').element as any)._vei;
|
||||
expect(vMenuListeners).toHaveProperty('onClick');
|
||||
});
|
||||
|
||||
test('should not have click event listener when closeOnContentClick prop is false', () => {
|
||||
const wrapper = mount(VMenu, {
|
||||
props: {
|
||||
modelValue: true, // make it open in the beginning to ensure '.v-menu-content' is in the dom
|
||||
closeOnContentClick: false,
|
||||
},
|
||||
global,
|
||||
});
|
||||
|
||||
const vMenuContentListeners = (wrapper.getComponent(TransitionBounce).find('.v-menu-content').element as any)._vei;
|
||||
expect(vMenuContentListeners).toBeUndefined();
|
||||
});
|
||||
|
||||
test('should have click event listener when closeOnContentClick prop is true', () => {
|
||||
const wrapper = mount(VMenu, {
|
||||
props: {
|
||||
modelValue: true, // make it open in the beginning to ensure '.v-menu-content' is in the dom
|
||||
closeOnContentClick: true,
|
||||
},
|
||||
global,
|
||||
});
|
||||
|
||||
const vMenuContentListeners = (wrapper.getComponent(TransitionBounce).find('.v-menu-content').element as any)._vei;
|
||||
expect(vMenuContentListeners).toHaveProperty('onClick');
|
||||
});
|
||||
|
||||
test('should not have pointerenter and pointerleave event listener when trigger is not "hover"', () => {
|
||||
const wrapper = mount(VMenu, {
|
||||
props: {
|
||||
modelValue: true, // make it open in the beginning to ensure '.v-menu-content' is in the dom
|
||||
},
|
||||
global,
|
||||
});
|
||||
|
||||
const activatorListeners = (wrapper.find({ ref: 'activator' }).element as any)._vei;
|
||||
expect(activatorListeners).toBeUndefined();
|
||||
expect(activatorListeners).toBeUndefined();
|
||||
|
||||
// we need to use getComponent because it's teleported
|
||||
const vMenuContentListeners = (wrapper.getComponent(TransitionBounce).find('.v-menu-content').element as any)._vei;
|
||||
expect(vMenuContentListeners).not.toHaveProperty('onPointerenter');
|
||||
expect(vMenuContentListeners).not.toHaveProperty('onPointerleave');
|
||||
});
|
||||
|
||||
test('should have pointerenter and pointerleave event listener when trigger is "hover"', () => {
|
||||
const wrapper = mount(VMenu, {
|
||||
props: {
|
||||
modelValue: true, // make it open in the beginning to ensure '.v-menu-content' is in the dom
|
||||
trigger: 'hover',
|
||||
},
|
||||
global,
|
||||
});
|
||||
|
||||
const activatorListeners = (wrapper.find({ ref: 'activator' }).element as any)._vei;
|
||||
expect(activatorListeners).toHaveProperty('onPointerenter');
|
||||
expect(activatorListeners).toHaveProperty('onPointerleave');
|
||||
|
||||
// we need to use getComponent because it's teleported
|
||||
const vMenuContentListeners = (wrapper.getComponent(TransitionBounce).find('.v-menu-content').element as any)._vei;
|
||||
expect(vMenuContentListeners).toHaveProperty('onPointerenter');
|
||||
expect(vMenuContentListeners).toHaveProperty('onPointerleave');
|
||||
});
|
||||
@@ -1,11 +1,10 @@
|
||||
<template>
|
||||
<div class="v-menu" @click="onClick">
|
||||
<div ref="v-menu" class="v-menu" v-on="trigger === 'click' ? { click: onClick } : {}">
|
||||
<div
|
||||
ref="activator"
|
||||
class="v-menu-activator"
|
||||
:class="{ attached }"
|
||||
@pointerenter.stop="onPointerEnter"
|
||||
@pointerleave.stop="onPointerLeave"
|
||||
v-on="trigger === 'hover' ? { pointerenter: onPointerEnter, pointerleave: onPointerLeave } : {}"
|
||||
>
|
||||
<slot
|
||||
name="activator"
|
||||
@@ -39,9 +38,10 @@
|
||||
<div
|
||||
class="v-menu-content"
|
||||
:class="{ 'full-height': fullHeight, seamless }"
|
||||
@click.stop="onContentClick"
|
||||
@pointerenter.stop="onPointerEnter"
|
||||
@pointerleave.stop="onPointerLeave"
|
||||
v-on="{
|
||||
...(closeOnContentClick ? { click: onContentClick } : {}),
|
||||
...(trigger === 'hover' ? { pointerenter: onPointerEnter, pointerleave: onPointerLeave } : {}),
|
||||
}"
|
||||
>
|
||||
<slot
|
||||
v-bind="{
|
||||
@@ -240,7 +240,8 @@ function onClickOutsideMiddleware(e: Event) {
|
||||
}
|
||||
|
||||
function onContentClick(e: Event) {
|
||||
if (props.closeOnContentClick === true && e.target !== e.currentTarget) {
|
||||
e.stopPropagation();
|
||||
if (e.target !== e.currentTarget) {
|
||||
deactivate();
|
||||
}
|
||||
}
|
||||
@@ -262,18 +263,16 @@ function useEvents() {
|
||||
return { onClick, onPointerLeave, onPointerEnter };
|
||||
|
||||
function onClick() {
|
||||
if (props.trigger !== 'click') return;
|
||||
|
||||
toggle();
|
||||
}
|
||||
|
||||
function onPointerEnter() {
|
||||
if (props.trigger !== 'hover') return;
|
||||
function onPointerEnter(event: Event) {
|
||||
event.stopPropagation();
|
||||
isHovered.value = true;
|
||||
}
|
||||
|
||||
function onPointerLeave() {
|
||||
if (props.trigger !== 'hover') return;
|
||||
function onPointerLeave(event: Event) {
|
||||
event.stopPropagation();
|
||||
isHovered.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user