feat: support smooth auto-scroll to active menu item (#6102)

This commit is contained in:
Vben
2025-05-03 18:05:26 +08:00
committed by Netfan
parent c4a4ca5232
commit 4fd2fee782
9 changed files with 155 additions and 70 deletions

View File

@@ -31,6 +31,7 @@ import {
createSubMenuContext,
useMenuStyle,
} from '../hooks';
import { useMenuScroll } from '../hooks/use-menu-scroll';
import { flattedChildren } from '../utils';
import SubMenu from './sub-menu.vue';
@@ -44,6 +45,7 @@ const props = withDefaults(defineProps<Props>(), {
mode: 'vertical',
rounded: true,
theme: 'dark',
scrollToActive: false,
});
const emit = defineEmits<{
@@ -206,15 +208,19 @@ function handleResize() {
isFirstTimeRender = false;
}
function getActivePaths() {
const activeItem = activePath.value && items.value[activePath.value];
const enableScroll = computed(
() => props.scrollToActive && props.mode === 'vertical' && !props.collapse,
);
if (!activeItem || props.mode === 'horizontal' || props.collapse) {
return [];
}
const { scrollToActiveItem } = useMenuScroll(activePath, {
enable: enableScroll,
delay: 320,
});
return activeItem.parentPaths;
}
// 监听 activePath 变化,自动滚动到激活项
watch(activePath, () => {
scrollToActiveItem();
});
// 默认展开菜单
function initMenu() {
@@ -318,6 +324,16 @@ function removeSubMenu(subMenu: MenuItemRegistered) {
function removeMenuItem(item: MenuItemRegistered) {
Reflect.deleteProperty(items.value, item.path);
}
function getActivePaths() {
const activeItem = activePath.value && items.value[activePath.value];
if (!activeItem || props.mode === 'horizontal' || props.collapse) {
return [];
}
return activeItem.parentPaths;
}
</script>
<template>
<ul

View File

@@ -0,0 +1,46 @@
import type { Ref } from 'vue';
import { watch } from 'vue';
import { useDebounceFn } from '@vueuse/core';
interface UseMenuScrollOptions {
delay?: number;
enable?: boolean | Ref<boolean>;
}
export function useMenuScroll(
activePath: Ref<string | undefined>,
options: UseMenuScrollOptions = {},
) {
const { enable = true, delay = 320 } = options;
function scrollToActiveItem() {
const isEnabled = typeof enable === 'boolean' ? enable : enable.value;
if (!isEnabled) return;
const activeElement = document.querySelector(
`aside li[role=menuitem].is-active`,
);
if (activeElement) {
activeElement.scrollIntoView({
behavior: 'smooth',
block: 'center',
inline: 'center',
});
}
}
const debouncedScroll = useDebounceFn(scrollToActiveItem, delay);
watch(activePath, () => {
const isEnabled = typeof enable === 'boolean' ? enable : enable.value;
if (!isEnabled) return;
debouncedScroll();
});
return {
scrollToActiveItem,
};
}

View File

@@ -18,15 +18,9 @@ defineOptions({
const props = withDefaults(defineProps<Props>(), {
collapse: false,
// theme: 'dark',
});
const forward = useForwardProps(props);
// const emit = defineEmits<{
// 'update:openKeys': [key: Key[]];
// 'update:selectedKeys': [key: Key[]];
// }>();
</script>
<template>

View File

@@ -42,6 +42,12 @@ interface MenuProps {
*/
rounded?: boolean;
/**
* @zh_CN 是否自动滚动到激活的菜单项
* @default false
*/
scrollToActive?: boolean;
/**
* @zh_CN 菜单主题
* @default dark