Skip to content

Commit

Permalink
fix(Menu): 水平模式,鼠标移除但未隐藏 (#851)
Browse files Browse the repository at this point in the history
  • Loading branch information
ocean-gao committed Jun 25, 2024
1 parent fc87810 commit 57e5520
Show file tree
Hide file tree
Showing 13 changed files with 223 additions and 173 deletions.
4 changes: 3 additions & 1 deletion components/menu/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,9 @@ export type RootMenuInjection = {
accordion: ComputedRef<boolean>;
expandTrigger: ComputedRef<TRIGGER>;
updateExpandedKeys: (val: string | number | (string | number)[]) => void;
handleSubMenu: (subMenu: MenuItemType, indexPath: Ref<MenuNode[]>) => void;
handleSubMenuExpand: (subMenu: MenuItemType, indexPath: Ref<MenuNode[]>) => void;
currentPopperShowSubMenus: Ref<(string | number)[]>;
updatePopperShowSubMenu: (val: string | number, state: string) => void;
};

export const ROOT_MENU_KEY: InjectionKey<RootMenuInjection> = Symbol('ROOT_MENU_KEY');
45 changes: 34 additions & 11 deletions components/menu/menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import {
defineComponent,
onMounted,
provide,
ref,
watch,
} from 'vue';
import { isFunction } from 'lodash-es';
import getPrefixCls from '../_util/getPrefixCls';
Expand Down Expand Up @@ -93,22 +95,16 @@ export default defineComponent({
return props.mode === 'horizontal' ? true : props.accordion;
});

// 展开的方式
// 展开的方式,垂直模式仅支持 click, 水平模式仅支持 hover
const expandTrigger = computed<TRIGGER>(() => {
// 垂直模式默认点击,且仅支持点击展开
if (props.mode === 'vertical') {
return 'click';
}
if (props.expandTrigger) {
return props.expandTrigger;
}
// 如果没有设置expandTrigger,水平模式默认hover
if (props.mode === 'horizontal') {
return 'hover';
} else {
return 'click';
}
});

const handleSubMenu = (
const handleSubMenuExpand = (
subMenu: MenuItemType,
indexPath: Ref<MenuNode[]>,
) => {
Expand All @@ -124,6 +120,31 @@ export default defineComponent({
updateExpandedKeys(subMenu.value || subMenu.uid);
};

const currentPopperShowSubMenus = ref<(string | number)[]>([]);
const updatePopperShowSubMenu = (value: string | number, state: string) => {
const current = currentPopperShowSubMenus.value;
const index = current.indexOf(value);

if (state === 'show') {
if (index < 0) {
current.push(value);
}
} else {
if (index > -1) {
current.splice(index, 1);
}
}
currentPopperShowSubMenus.value = current;
};
watch(currentPopperShowSubMenus, () => {
// 若鼠标滑出所有的子菜单区域,则隐藏所有子菜单
if (currentPopperShowSubMenus.value.length === 0) {
updateExpandedKeys([]);
}
}, {
deep: true,
});

provide(ROOT_MENU_KEY, {
props,
currentValue,
Expand All @@ -133,7 +154,9 @@ export default defineComponent({
accordion,
expandTrigger,
updateExpandedKeys,
handleSubMenu,
handleSubMenuExpand,
currentPopperShowSubMenus,
updatePopperShowSubMenu,
});

const classList = computed(() =>
Expand Down
5 changes: 4 additions & 1 deletion components/menu/menuItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,10 @@ export default defineComponent({
return null;
};
return () => (
<div class={classList.value} onClick={handleClick}>
<div
class={classList.value}
onClick={handleClick}
>
<div class={`${prefixCls}-wrapper`} style={paddingStyle.value}>
{renderIcon()}
{!onlyIcon.value ? renderTitle() : null}
Expand Down
98 changes: 65 additions & 33 deletions components/menu/subMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
computed,
defineComponent,
getCurrentInstance,
nextTick,
onBeforeUnmount,
onMounted,
provide,
Expand Down Expand Up @@ -115,29 +116,57 @@ export default defineComponent({
.join(' '),
);

const handleTrigger = () => {
const handleTriggerClick = () => {
isOpened.value = !isOpened.value;
rootMenu.handleSubMenu(subMenu as unknown as MenuItemType, indexPath);
rootMenu.handleSubMenuExpand(subMenu as unknown as MenuItemType, indexPath);
};

const handleEnter = () => {
// 如果是hover 且 只能展开一项的场景,进入第一层的时候要清空
const handlePopperTriggerShow = async () => {
// 记录当前已打开的子菜单 popper
rootMenu.updatePopperShowSubMenu(props.value || instance.uid, 'show');

// 只能展开一项的场景,进入第一层的时候要清空
if (rootMenu.accordion.value && isFirstLevel.value) {
isOpened.value = !isOpened.value;
rootMenu.updateExpandedKeys([]);
}
rootMenu.handleSubMenu(subMenu as unknown as MenuItemType, indexPath);

// 若当前子菜单已关闭,则打开
await nextTick();
if (!rootMenu.currentExpandedKeys.value.includes(props.value || instance.uid)) {
isOpened.value = !isOpened.value;
rootMenu.handleSubMenuExpand(subMenu as unknown as MenuItemType, indexPath);
}
};
const handlePopperTriggerHide = async () => {
rootMenu.updatePopperShowSubMenu(props.value || instance.uid, 'hide');

/**
* TODO: 同一层级只能展开一个子菜单
*/
};

const handlePopperTrigger = (state: string) => {
if (state === 'show') {
handlePopperTriggerShow();
} else {
handlePopperTriggerHide();
}
};

watch(
rootMenu.currentExpandedKeys,
[
rootMenu.currentExpandedKeys,
rootMenu.currentPopperShowSubMenus,
],
() => {
// 要通过监听currentExpandedKeys,自动打开或者关闭子菜单
const isHas = rootMenu.currentExpandedKeys.value.includes(
const currentIsExpanded = rootMenu.currentExpandedKeys.value.includes(
props.value || instance.uid,
);
if (!isHas && isOpened.value) {
if (isOpened.value && !currentIsExpanded) {
isOpened.value = false;
} else if (isHas && !isOpened.value) {
} else if (!isOpened.value && currentIsExpanded) {
isOpened.value = true;
}
},
Expand Down Expand Up @@ -182,34 +211,36 @@ export default defineComponent({
);
};

// 两种展开的方式 hover&click
const renderWrapper = (expandTrigger: TRIGGER) => {
const wrapperContent = (
const wrapperContent = () => {
return (
<>
{renderIcon()}
{!onlyIcon.value ? renderTitle() : null}
{!onlyIcon.value ? renderArrow() : null}
</>
);
return expandTrigger === 'click'
? (
<div
class={`${prefixCls}-wrapper`}
style={paddingStyle.value}
onClick={handleTrigger}
>
{wrapperContent}
</div>
)
: (
<div
class={`${prefixCls}-wrapper`}
style={paddingStyle.value}
onMouseenter={handleEnter}
>
{wrapperContent}
</div>
);
};
const renderWrapperClick = () => {
return (
<div
class={`${prefixCls}-wrapper`}
style={paddingStyle.value}
onClick={handleTriggerClick}
>
{wrapperContent()}
</div>
);
};

const renderWrapperPopper = () => {
return (
<div
class={`${prefixCls}-wrapper`}
style={paddingStyle.value}
>
{wrapperContent()}
</div>
);
};

const renderDefault = () => slots.default?.();
Expand All @@ -233,14 +264,15 @@ export default defineComponent({
offset={1}
v-slots={{
default: renderDefault,
trigger: () => renderWrapper(rootMenu.expandTrigger.value),
trigger: () => renderWrapperPopper(),
}}
onTrigger={handlePopperTrigger}
/>
);
}
return (
<>
{renderWrapper(rootMenu.expandTrigger.value)}
{renderWrapperClick()}
<FadeInExpandTransition>
<div
v-show={isOpened.value}
Expand Down
1 change: 1 addition & 0 deletions components/popper/const.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const STATE_TRIGGER_EVENT = 'trigger';
1 change: 1 addition & 0 deletions components/popper/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { VModelEvent } from '../_util/interface';

export interface PopperEmits {
(e: VModelEvent, val: boolean): void;
(e: 'trigger', value: string): void;
}

export interface VirtualRect {
Expand Down
5 changes: 4 additions & 1 deletion components/popper/popper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,14 @@ import usePopper from './usePopper';
import useScroll from './useScroll';

import { popperProps } from './props';
import { STATE_TRIGGER_EVENT } from './const';

const prefixCls = getPrefixCls('popper');

export default defineComponent({
name: 'FPopper',
props: popperProps,
emits: [UPDATE_MODEL_EVENT],
emits: [UPDATE_MODEL_EVENT, STATE_TRIGGER_EVENT],
setup(props, { slots, emit }) {
useTheme();
if (!slots.trigger) {
Expand Down Expand Up @@ -84,6 +85,7 @@ export default defineComponent({
useClickOutSide(
[triggerRef, popperRef],
() => {
emit(STATE_TRIGGER_EVENT, 'hide');
updateVisible(false);
},
disabledWatch,
Expand All @@ -99,6 +101,7 @@ export default defineComponent({
updateVisible,
props,
updateVirtualRect,
emit,
);
const popperClass = computed(() =>
[prefixCls, props.popperClass].filter(Boolean),
Expand Down
55 changes: 35 additions & 20 deletions components/popper/useTrigger.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { type Ref, ref, watch } from 'vue';
import { isBoolean, isFunction } from 'lodash-es';
import type { PopperProps } from './props';
import type { VirtualRect } from './interface';
import type { PopperEmits, VirtualRect } from './interface';
import { STATE_TRIGGER_EVENT } from './const';

const triggerEventsMap = {
click: ['onClick'],
Expand All @@ -17,6 +18,7 @@ export default function useTrigger(
updateVisible: (val: boolean) => void,
props: PopperProps,
updateVirtualRect: (val: VirtualRect | null) => void,
emit: PopperEmits,
) {
let triggerFocused = false;
let showTimer: ReturnType<typeof setTimeout>;
Expand All @@ -29,39 +31,49 @@ export default function useTrigger(
}

const hide = () => {
if (props.onlyShowTrigger) {
return;
}
if (isBoolean(props.disabled) && props.disabled) {
return;
}
if (isFunction(props.disabled) && props.disabled()) {
return;
}
const setHide = () => {
emit(STATE_TRIGGER_EVENT, 'hide');
if (props.onlyShowTrigger) {
return;
}
if (isBoolean(props.disabled) && props.disabled) {
return;
}
if (isFunction(props.disabled) && props.disabled()) {
return;
}
updateVisible(false);
};

clearTimers();
if (props.hideAfter) {
hideTimer = setTimeout(() => {
updateVisible(false);
setHide();
}, props.hideAfter);
} else {
updateVisible(false);
setHide();
}
};

const show = () => {
if (isBoolean(props.disabled) && props.disabled) {
return;
}
if (isFunction(props.disabled) && props.disabled()) {
return;
}
const setShow = () => {
emit(STATE_TRIGGER_EVENT, 'show');
if (isBoolean(props.disabled) && props.disabled) {
return;
}
if (isFunction(props.disabled) && props.disabled()) {
return;
}
updateVisible(true);
};

clearTimers();
if (props.showAfter) {
showTimer = setTimeout(() => {
updateVisible(true);
setShow();
}, props.showAfter);
} else {
updateVisible(true);
setShow();
}
};

Expand Down Expand Up @@ -150,6 +162,9 @@ export default function useTrigger(
if (props.trigger !== 'click') {
clearTimeout(hideTimer);
}
if (props.trigger === 'hover') {
show();
}
}

function onPopperMouseLeave() {
Expand Down
Loading

0 comments on commit 57e5520

Please sign in to comment.