mirror of
https://gitee.com/ant-design-vue/ant-design-vue.git
synced 2024-12-05 05:29:01 +08:00
refactor: menu
This commit is contained in:
parent
7b494fd445
commit
8179361803
@ -1,7 +1,7 @@
|
||||
import Menu from './src/Menu';
|
||||
import MenuItem from './src/MenuItem';
|
||||
import SubMenu from './src/SubMenu';
|
||||
import ItemGroup from './src/ItemGroup';
|
||||
import Menu, { MenuProps } from './src/Menu';
|
||||
import MenuItem, { MenuItemProps } from './src/MenuItem';
|
||||
import SubMenu, { SubMenuProps } from './src/SubMenu';
|
||||
import ItemGroup, { MenuItemGroupProps } from './src/ItemGroup';
|
||||
import Divider from './src/Divider';
|
||||
import { App } from 'vue';
|
||||
/* istanbul ignore next */
|
||||
@ -19,6 +19,18 @@ Menu.Divider = Divider;
|
||||
Menu.SubMenu = SubMenu;
|
||||
Menu.ItemGroup = ItemGroup;
|
||||
|
||||
export {
|
||||
SubMenu,
|
||||
MenuItem as Item,
|
||||
MenuItem,
|
||||
ItemGroup,
|
||||
Divider,
|
||||
MenuProps,
|
||||
SubMenuProps,
|
||||
MenuItemProps,
|
||||
MenuItemGroupProps,
|
||||
};
|
||||
|
||||
export default Menu as typeof Menu &
|
||||
Plugin & {
|
||||
readonly Item: typeof MenuItem;
|
||||
|
@ -1,13 +1,17 @@
|
||||
import { getPropsSlot } from '../../_util/props-util';
|
||||
import { computed, defineComponent } from 'vue';
|
||||
import { computed, defineComponent, ExtractPropTypes } from 'vue';
|
||||
import PropTypes from '../../_util/vue-types';
|
||||
import { useInjectMenu } from './hooks/useMenuContext';
|
||||
|
||||
const menuItemGroupProps = {
|
||||
title: PropTypes.VNodeChild,
|
||||
};
|
||||
|
||||
export type MenuItemGroupProps = Partial<ExtractPropTypes<typeof menuItemGroupProps>>;
|
||||
|
||||
export default defineComponent({
|
||||
name: 'AMenuItemGroup',
|
||||
props: {
|
||||
title: PropTypes.VNodeChild,
|
||||
},
|
||||
props: menuItemGroupProps,
|
||||
inheritAttrs: false,
|
||||
slots: ['title'],
|
||||
setup(props, { slots, attrs }) {
|
||||
|
@ -10,8 +10,8 @@ import {
|
||||
watch,
|
||||
reactive,
|
||||
onMounted,
|
||||
toRaw,
|
||||
unref,
|
||||
UnwrapRef,
|
||||
} from 'vue';
|
||||
import shallowEqual from '../../_util/shallowequal';
|
||||
import useProvideMenu, { StoreMenuInfo, useProvideFirstLevel } from './hooks/useMenuContext';
|
||||
@ -35,6 +35,7 @@ export const menuProps = {
|
||||
overflowDisabled: Boolean,
|
||||
openKeys: Array,
|
||||
selectedKeys: Array,
|
||||
activeKey: String, // 内部组件使用
|
||||
selectable: { type: Boolean, default: true },
|
||||
multiple: { type: Boolean, default: false },
|
||||
|
||||
@ -59,7 +60,15 @@ export type MenuProps = Partial<ExtractPropTypes<typeof menuProps>>;
|
||||
export default defineComponent({
|
||||
name: 'AMenu',
|
||||
props: menuProps,
|
||||
emits: ['update:openKeys', 'openChange', 'select', 'deselect', 'update:selectedKeys', 'click'],
|
||||
emits: [
|
||||
'update:openKeys',
|
||||
'openChange',
|
||||
'select',
|
||||
'deselect',
|
||||
'update:selectedKeys',
|
||||
'click',
|
||||
'update:activeKey',
|
||||
],
|
||||
setup(props, { slots, emit }) {
|
||||
const { prefixCls, direction } = useConfigInject('menu', props);
|
||||
const store = reactive<Record<string, StoreMenuInfo>>({});
|
||||
@ -94,6 +103,34 @@ export default defineComponent({
|
||||
|
||||
const activeKeys = ref([]);
|
||||
const mergedSelectedKeys = ref([]);
|
||||
const keyMapStore = ref({});
|
||||
watch(
|
||||
store,
|
||||
() => {
|
||||
const newKeyMapStore = {};
|
||||
for (let [_eventKey, menuInfo] of Object.entries(store)) {
|
||||
newKeyMapStore[menuInfo.key] = menuInfo;
|
||||
}
|
||||
keyMapStore.value = newKeyMapStore;
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
watchEffect(() => {
|
||||
if ('activeKey' in props) {
|
||||
let keys = [];
|
||||
const menuInfo = props.activeKey
|
||||
? (keyMapStore.value[props.activeKey] as UnwrapRef<StoreMenuInfo>)
|
||||
: undefined;
|
||||
if (menuInfo && props.activeKey !== undefined) {
|
||||
keys = [...menuInfo.parentKeys, props.activeKey];
|
||||
} else {
|
||||
keys = [];
|
||||
}
|
||||
if (!shallowEqual(activeKeys.value, keys)) {
|
||||
activeKeys.value = keys;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.selectedKeys,
|
||||
@ -106,11 +143,12 @@ export default defineComponent({
|
||||
const selectedSubMenuEventKeys = ref([]);
|
||||
|
||||
watch(
|
||||
[store, mergedSelectedKeys],
|
||||
[keyMapStore, mergedSelectedKeys],
|
||||
() => {
|
||||
let subMenuParentEventKeys = [];
|
||||
(Object.values(toRaw(store)) as any).forEach((menuInfo: StoreMenuInfo) => {
|
||||
if (mergedSelectedKeys.value.includes(menuInfo.key)) {
|
||||
mergedSelectedKeys.value.forEach(key => {
|
||||
const menuInfo = keyMapStore.value[key];
|
||||
if (menuInfo) {
|
||||
subMenuParentEventKeys.push(...unref(menuInfo.parentEventKeys));
|
||||
}
|
||||
});
|
||||
@ -172,7 +210,11 @@ export default defineComponent({
|
||||
);
|
||||
|
||||
const changeActiveKeys = (keys: Key[]) => {
|
||||
activeKeys.value = keys;
|
||||
if ('activeKey' in props) {
|
||||
emit('update:activeKey', keys[keys.length - 1]);
|
||||
} else {
|
||||
activeKeys.value = keys;
|
||||
}
|
||||
};
|
||||
const disabled = computed(() => !!props.disabled);
|
||||
const isRtl = computed(() => direction.value === 'rtl');
|
||||
|
@ -1,24 +1,36 @@
|
||||
import { flattenChildren, getPropsSlot, isValidElement } from '../../_util/props-util';
|
||||
import PropTypes from '../../_util/vue-types';
|
||||
import { computed, defineComponent, getCurrentInstance, onBeforeUnmount, ref, watch } from 'vue';
|
||||
import {
|
||||
computed,
|
||||
defineComponent,
|
||||
ExtractPropTypes,
|
||||
getCurrentInstance,
|
||||
onBeforeUnmount,
|
||||
ref,
|
||||
watch,
|
||||
} from 'vue';
|
||||
import { useInjectKeyPath } from './hooks/useKeyPath';
|
||||
import { useInjectFirstLevel, useInjectMenu } from './hooks/useMenuContext';
|
||||
import { cloneElement } from '../../_util/vnode';
|
||||
import Tooltip from '../../tooltip';
|
||||
import { MenuInfo } from './interface';
|
||||
import KeyCode from 'ant-design-vue/es/_util/KeyCode';
|
||||
import useDirectionStyle from './hooks/useDirectionStyle';
|
||||
|
||||
let indexGuid = 0;
|
||||
const menuItemProps = {
|
||||
role: String,
|
||||
disabled: Boolean,
|
||||
danger: Boolean,
|
||||
title: { type: [String, Boolean], default: undefined },
|
||||
icon: PropTypes.VNodeChild,
|
||||
};
|
||||
|
||||
export type MenuItemProps = Partial<ExtractPropTypes<typeof menuItemProps>>;
|
||||
|
||||
export default defineComponent({
|
||||
name: 'AMenuItem',
|
||||
props: {
|
||||
role: String,
|
||||
disabled: Boolean,
|
||||
danger: Boolean,
|
||||
title: { type: [String, Boolean], default: undefined },
|
||||
icon: PropTypes.VNodeChild,
|
||||
},
|
||||
props: menuItemProps,
|
||||
emits: ['mouseenter', 'mouseleave', 'click', 'keydown', 'focus'],
|
||||
slots: ['icon'],
|
||||
inheritAttrs: false,
|
||||
@ -26,7 +38,7 @@ export default defineComponent({
|
||||
const instance = getCurrentInstance();
|
||||
const key = instance.vnode.key;
|
||||
const eventKey = `menu_item_${++indexGuid}_$$_${key}`;
|
||||
const { parentEventKeys } = useInjectKeyPath();
|
||||
const { parentEventKeys, parentKeys } = useInjectKeyPath();
|
||||
const {
|
||||
prefixCls,
|
||||
activeKeys,
|
||||
@ -37,21 +49,21 @@ export default defineComponent({
|
||||
siderCollapsed,
|
||||
onItemClick,
|
||||
selectedKeys,
|
||||
store,
|
||||
registerMenuInfo,
|
||||
unRegisterMenuInfo,
|
||||
} = useInjectMenu();
|
||||
const firstLevel = useInjectFirstLevel();
|
||||
const isActive = ref(false);
|
||||
const keyPath = computed(() => {
|
||||
return [...parentEventKeys.value.map(eK => store[eK].key), key];
|
||||
const keysPath = computed(() => {
|
||||
return [...parentKeys.value, key];
|
||||
});
|
||||
|
||||
const keysPath = computed(() => [...parentEventKeys.value, eventKey]);
|
||||
// const keysPath = computed(() => [...parentEventKeys.value, eventKey]);
|
||||
const menuInfo = {
|
||||
eventKey,
|
||||
key,
|
||||
parentEventKeys,
|
||||
parentKeys,
|
||||
isLeaf: true,
|
||||
};
|
||||
|
||||
@ -85,7 +97,7 @@ export default defineComponent({
|
||||
return {
|
||||
key: key,
|
||||
eventKey: eventKey,
|
||||
keyPath: keyPath.value,
|
||||
keyPath: keysPath.value,
|
||||
eventKeyPath: [...parentEventKeys.value, eventKey],
|
||||
domEvent: e,
|
||||
};
|
||||
@ -150,6 +162,9 @@ export default defineComponent({
|
||||
return <span class={`${prefixCls.value}-title-content`}>{children}</span>;
|
||||
};
|
||||
|
||||
// ========================== DirectionStyle ==========================
|
||||
const directionStyle = useDirectionStyle(computed(() => keysPath.value.length));
|
||||
|
||||
return () => {
|
||||
const { title } = props;
|
||||
const children = flattenChildren(slots.default?.());
|
||||
@ -187,6 +202,7 @@ export default defineComponent({
|
||||
>
|
||||
<li
|
||||
{...attrs}
|
||||
style={{ ...((attrs.style as any) || {}), ...directionStyle.value }}
|
||||
class={[
|
||||
classNames.value,
|
||||
{
|
||||
|
@ -7,6 +7,7 @@ import {
|
||||
watch,
|
||||
PropType,
|
||||
onBeforeUnmount,
|
||||
ExtractPropTypes,
|
||||
} from 'vue';
|
||||
import useProvideKeyPath, { useInjectKeyPath } from './hooks/useKeyPath';
|
||||
import { useInjectMenu, useProvideFirstLevel, MenuContextProvider } from './hooks/useMenuContext';
|
||||
@ -19,17 +20,22 @@ import InlineSubMenuList from './InlineSubMenuList';
|
||||
import Transition, { getTransitionProps } from '../../_util/transition';
|
||||
|
||||
let indexGuid = 0;
|
||||
|
||||
const subMenuProps = {
|
||||
icon: PropTypes.VNodeChild,
|
||||
title: PropTypes.VNodeChild,
|
||||
disabled: Boolean,
|
||||
level: Number,
|
||||
popupClassName: String,
|
||||
popupOffset: Array as PropType<number[]>,
|
||||
internalPopupClose: Boolean,
|
||||
};
|
||||
|
||||
export type SubMenuProps = Partial<ExtractPropTypes<typeof subMenuProps>>;
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ASubMenu',
|
||||
props: {
|
||||
icon: PropTypes.VNodeChild,
|
||||
title: PropTypes.VNodeChild,
|
||||
disabled: Boolean,
|
||||
level: Number,
|
||||
popupClassName: String,
|
||||
popupOffset: Array as PropType<number[]>,
|
||||
internalPopupClose: Boolean,
|
||||
},
|
||||
props: subMenuProps,
|
||||
slots: ['icon', 'title'],
|
||||
emits: ['titleClick', 'mouseenter', 'mouseleave'],
|
||||
inheritAttrs: false,
|
||||
@ -40,15 +46,16 @@ export default defineComponent({
|
||||
const key = instance.vnode.key;
|
||||
|
||||
const eventKey = `sub_menu_${++indexGuid}_$$_${key}`;
|
||||
const { parentEventKeys, parentInfo } = useInjectKeyPath();
|
||||
const keysPath = computed(() => [...parentEventKeys.value, eventKey]);
|
||||
|
||||
const { parentEventKeys, parentInfo, parentKeys } = useInjectKeyPath();
|
||||
const keysPath = computed(() => [...parentKeys.value, key]);
|
||||
const eventKeysPath = computed(() => [...parentEventKeys.value, eventKey]);
|
||||
const childrenEventKeys = ref([]);
|
||||
const menuInfo = {
|
||||
eventKey,
|
||||
key,
|
||||
parentEventKeys,
|
||||
childrenEventKeys,
|
||||
parentKeys,
|
||||
};
|
||||
parentInfo.childrenEventKeys?.value.push(eventKey);
|
||||
onBeforeUnmount(() => {
|
||||
@ -59,7 +66,7 @@ export default defineComponent({
|
||||
}
|
||||
});
|
||||
|
||||
useProvideKeyPath(eventKey, menuInfo);
|
||||
useProvideKeyPath(eventKey, key, menuInfo);
|
||||
|
||||
const {
|
||||
prefixCls,
|
||||
@ -141,7 +148,7 @@ export default defineComponent({
|
||||
};
|
||||
|
||||
// ========================== DirectionStyle ==========================
|
||||
const directionStyle = useDirectionStyle(computed(() => keysPath.value.length));
|
||||
const directionStyle = useDirectionStyle(computed(() => eventKeysPath.value.length));
|
||||
|
||||
// >>>>> Visible change
|
||||
const onPopupVisibleChange = (newVisible: boolean) => {
|
||||
@ -189,7 +196,7 @@ export default defineComponent({
|
||||
|
||||
// Cache mode if it change to `inline` which do not have popup motion
|
||||
const triggerModeRef = computed(() => {
|
||||
return mode.value !== 'inline' && keysPath.value.length > 1 ? 'vertical' : mode.value;
|
||||
return mode.value !== 'inline' && eventKeysPath.value.length > 1 ? 'vertical' : mode.value;
|
||||
});
|
||||
|
||||
const renderMode = computed(() => (mode.value === 'horizontal' ? 'vertical' : mode.value));
|
||||
|
@ -8,7 +8,7 @@ export default function useDirectionStyle(level: ComputedRef<number>): ComputedR
|
||||
mode.value !== 'inline'
|
||||
? null
|
||||
: rtl.value
|
||||
? { paddingRight: level.value * inlineIndent.value }
|
||||
: { paddingLeft: level.value * inlineIndent.value },
|
||||
? { paddingRight: `${level.value * inlineIndent.value}px` }
|
||||
: { paddingLeft: `${level.value * inlineIndent.value}px` },
|
||||
);
|
||||
}
|
||||
|
@ -3,21 +3,24 @@ import { computed, ComputedRef, inject, InjectionKey, provide } from 'vue';
|
||||
import { StoreMenuInfo } from './useMenuContext';
|
||||
|
||||
const KeyPathContext: InjectionKey<{
|
||||
parentEventKeys: ComputedRef<Key[]>;
|
||||
parentEventKeys: ComputedRef<string[]>;
|
||||
parentKeys: ComputedRef<Key[]>;
|
||||
parentInfo: StoreMenuInfo;
|
||||
}> = Symbol('KeyPathContext');
|
||||
|
||||
const useInjectKeyPath = () => {
|
||||
return inject(KeyPathContext, {
|
||||
parentEventKeys: computed(() => []),
|
||||
parentKeys: computed(() => []),
|
||||
parentInfo: {} as StoreMenuInfo,
|
||||
});
|
||||
};
|
||||
|
||||
const useProvideKeyPath = (eventKey: string, menuInfo: StoreMenuInfo) => {
|
||||
const { parentEventKeys } = useInjectKeyPath();
|
||||
const keys = computed(() => [...parentEventKeys.value, eventKey]);
|
||||
provide(KeyPathContext, { parentEventKeys: keys, parentInfo: menuInfo });
|
||||
const useProvideKeyPath = (eventKey: string, key: Key, menuInfo: StoreMenuInfo) => {
|
||||
const { parentEventKeys, parentKeys } = useInjectKeyPath();
|
||||
const eventKeys = computed(() => [...parentEventKeys.value, eventKey]);
|
||||
const keys = computed(() => [...parentKeys.value, key]);
|
||||
provide(KeyPathContext, { parentEventKeys: eventKeys, parentKeys: keys, parentInfo: menuInfo });
|
||||
return keys;
|
||||
};
|
||||
|
||||
|
@ -24,6 +24,7 @@ export interface StoreMenuInfo {
|
||||
parentEventKeys: ComputedRef<string[]>;
|
||||
childrenEventKeys?: Ref<string[]>;
|
||||
isLeaf?: boolean;
|
||||
parentKeys: ComputedRef<Key[]>;
|
||||
}
|
||||
export interface MenuContextProps {
|
||||
isRootMenu: boolean;
|
||||
|
@ -22,7 +22,7 @@ export interface MenuInfo {
|
||||
key: Key;
|
||||
eventKey: string;
|
||||
keyPath?: Key[];
|
||||
eventKeyPath: Key[];
|
||||
eventKeyPath: string[];
|
||||
domEvent: MouseEvent | KeyboardEvent;
|
||||
}
|
||||
|
||||
|
2
v2-doc
2
v2-doc
@ -1 +1 @@
|
||||
Subproject commit d197053285b81e77718621c0b5b94cb3b21831a2
|
||||
Subproject commit a7013ae87f69dcbcf547f4b023255b8a7a775557
|
Loading…
Reference in New Issue
Block a user