refactor: menu
parent
f0baa6118b
commit
8dd74a9977
|
@ -7,8 +7,11 @@ import {
|
|||
PropType,
|
||||
inject,
|
||||
watchEffect,
|
||||
watch,
|
||||
reactive,
|
||||
} from 'vue';
|
||||
import useProvideMenu, { useProvideFirstLevel } from './hooks/useMenuContext';
|
||||
import shallowEqual from '../../_util/shallowequal';
|
||||
import useProvideMenu, { StoreMenuInfo, useProvideFirstLevel } from './hooks/useMenuContext';
|
||||
import useConfigInject from '../../_util/hooks/useConfigInject';
|
||||
import { MenuTheme, MenuMode, BuiltinPlacements, TriggerSubMenuAction } from './interface';
|
||||
import devWarning from 'ant-design-vue/es/vc-util/devWarning';
|
||||
|
@ -19,6 +22,7 @@ export const menuProps = {
|
|||
disabled: Boolean,
|
||||
inlineCollapsed: Boolean,
|
||||
overflowDisabled: Boolean,
|
||||
openKeys: Array,
|
||||
|
||||
theme: { type: String as PropType<MenuTheme>, default: 'light' },
|
||||
mode: { type: String as PropType<MenuMode>, default: 'vertical' },
|
||||
|
@ -42,7 +46,7 @@ export default defineComponent({
|
|||
emits: ['update:openKeys', 'openChange'],
|
||||
setup(props, { slots, emit }) {
|
||||
const { prefixCls, direction } = useConfigInject('menu', props);
|
||||
|
||||
const store = reactive<Record<string, StoreMenuInfo>>({});
|
||||
const siderCollapsed = inject(
|
||||
'layoutSiderCollapsed',
|
||||
computed(() => undefined),
|
||||
|
@ -70,8 +74,19 @@ export default defineComponent({
|
|||
});
|
||||
|
||||
const activeKeys = ref([]);
|
||||
const openKeys = ref([]);
|
||||
const selectedKeys = ref([]);
|
||||
|
||||
const mergedOpenKeys = ref([]);
|
||||
|
||||
watch(
|
||||
() => props.openKeys,
|
||||
(openKeys = mergedOpenKeys.value) => {
|
||||
console.log('mergedOpenKeys', openKeys);
|
||||
mergedOpenKeys.value = openKeys;
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
const changeActiveKeys = (keys: Key[]) => {
|
||||
activeKeys.value = keys;
|
||||
};
|
||||
|
@ -107,15 +122,46 @@ export default defineComponent({
|
|||
|
||||
useProvideFirstLevel(true);
|
||||
|
||||
const onOpenChange = (key: Key, open: boolean) => {
|
||||
// emit('update:openKeys', openKeys);
|
||||
emit('openChange', open);
|
||||
const getChildrenKeys = (eventKeys: string[]): Key[] => {
|
||||
const keys = [];
|
||||
eventKeys.forEach(eventKey => {
|
||||
const { key, childrenEventKeys } = store[eventKey] as any;
|
||||
keys.push(key, ...getChildrenKeys(childrenEventKeys.value));
|
||||
});
|
||||
return keys;
|
||||
};
|
||||
|
||||
const onInternalOpenChange = (eventKey: Key, open: boolean) => {
|
||||
const { key, childrenEventKeys } = store[eventKey] as any;
|
||||
let newOpenKeys = mergedOpenKeys.value.filter(k => k !== key);
|
||||
|
||||
if (open) {
|
||||
newOpenKeys.push(key);
|
||||
} else if (mergedMode.value !== 'inline') {
|
||||
// We need find all related popup to close
|
||||
const subPathKeys = getChildrenKeys(childrenEventKeys.value);
|
||||
newOpenKeys = newOpenKeys.filter(k => !subPathKeys.includes(k));
|
||||
}
|
||||
|
||||
if (!shallowEqual(mergedOpenKeys, newOpenKeys)) {
|
||||
mergedOpenKeys.value = newOpenKeys;
|
||||
emit('update:openKeys', newOpenKeys);
|
||||
emit('openChange', key, open);
|
||||
}
|
||||
};
|
||||
|
||||
const registerMenuInfo = (key: string, info: StoreMenuInfo) => {
|
||||
store[key] = info as any;
|
||||
};
|
||||
const unRegisterMenuInfo = (key: string) => {
|
||||
delete store[key];
|
||||
};
|
||||
|
||||
useProvideMenu({
|
||||
store,
|
||||
prefixCls,
|
||||
activeKeys,
|
||||
openKeys,
|
||||
openKeys: mergedOpenKeys,
|
||||
selectedKeys,
|
||||
changeActiveKeys,
|
||||
disabled,
|
||||
|
@ -132,7 +178,9 @@ export default defineComponent({
|
|||
siderCollapsed,
|
||||
defaultMotions,
|
||||
overflowDisabled: computed(() => props.overflowDisabled),
|
||||
onOpenChange,
|
||||
onOpenChange: onInternalOpenChange,
|
||||
registerMenuInfo,
|
||||
unRegisterMenuInfo,
|
||||
});
|
||||
return () => {
|
||||
return <ul class={className.value}>{slots.default?.()}</ul>;
|
||||
|
|
|
@ -23,9 +23,9 @@ export default defineComponent({
|
|||
setup(props, { slots, emit, attrs }) {
|
||||
const instance = getCurrentInstance();
|
||||
const key = instance.vnode.key;
|
||||
const uniKey = `menu_item_${++indexGuid}`;
|
||||
const parentKeys = useInjectKeyPath();
|
||||
console.log(parentKeys.value);
|
||||
const eventKey = `menu_item_${++indexGuid}_$$_${key}`;
|
||||
const { parentEventKeys } = useInjectKeyPath();
|
||||
console.log(parentEventKeys.value);
|
||||
const {
|
||||
prefixCls,
|
||||
activeKeys,
|
||||
|
@ -58,7 +58,7 @@ export default defineComponent({
|
|||
});
|
||||
const onMouseEnter = (event: MouseEvent) => {
|
||||
if (!mergedDisabled.value) {
|
||||
changeActiveKeys([...parentKeys.value, key]);
|
||||
changeActiveKeys([...parentEventKeys.value, key]);
|
||||
emit('mouseenter', event);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,5 +1,13 @@
|
|||
import PropTypes from '../../_util/vue-types';
|
||||
import { computed, defineComponent, getCurrentInstance, ref, watch, PropType } from 'vue';
|
||||
import {
|
||||
computed,
|
||||
defineComponent,
|
||||
getCurrentInstance,
|
||||
ref,
|
||||
watch,
|
||||
PropType,
|
||||
onBeforeUnmount,
|
||||
} from 'vue';
|
||||
import useProvideKeyPath, { useInjectKeyPath } from './hooks/useKeyPath';
|
||||
import { useInjectMenu, useProvideFirstLevel, MenuContextProvider } from './hooks/useMenuContext';
|
||||
import { getPropsSlot, isValidElement } from 'ant-design-vue/es/_util/props-util';
|
||||
|
@ -9,6 +17,7 @@ import PopupTrigger from './PopupTrigger';
|
|||
import SubMenuList from './SubMenuList';
|
||||
import InlineSubMenuList from './InlineSubMenuList';
|
||||
|
||||
let indexGuid = 0;
|
||||
export default defineComponent({
|
||||
name: 'ASubMenu',
|
||||
props: {
|
||||
|
@ -24,11 +33,34 @@ export default defineComponent({
|
|||
emits: ['titleClick', 'titleMouseenter', 'titleMouseleave'],
|
||||
inheritAttrs: false,
|
||||
setup(props, { slots, attrs, emit }) {
|
||||
useProvideKeyPath();
|
||||
useProvideFirstLevel(false);
|
||||
|
||||
const instance = getCurrentInstance();
|
||||
const key = instance.vnode.key;
|
||||
const parentKeys = useInjectKeyPath();
|
||||
|
||||
const eventKey = `sub_menu_${++indexGuid}_$$_${key}`;
|
||||
const { parentEventKeys, parentInfo } = useInjectKeyPath();
|
||||
const keysPath = computed(() => [...parentEventKeys.value, eventKey]);
|
||||
|
||||
const childrenEventKeys = ref([]);
|
||||
const menuInfo = {
|
||||
eventKey,
|
||||
key,
|
||||
parentEventKeys,
|
||||
childrenEventKeys,
|
||||
};
|
||||
|
||||
parentInfo.childrenEventKeys?.value.push(eventKey);
|
||||
onBeforeUnmount(() => {
|
||||
if (parentInfo.childrenEventKeys) {
|
||||
parentInfo.childrenEventKeys.value = parentInfo.childrenEventKeys?.value.filter(
|
||||
k => k != eventKey,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
useProvideKeyPath(eventKey, menuInfo);
|
||||
|
||||
const {
|
||||
prefixCls,
|
||||
activeKeys,
|
||||
|
@ -40,8 +72,16 @@ export default defineComponent({
|
|||
openKeys,
|
||||
overflowDisabled,
|
||||
onOpenChange,
|
||||
registerMenuInfo,
|
||||
unRegisterMenuInfo,
|
||||
} = useInjectMenu();
|
||||
|
||||
registerMenuInfo(eventKey, menuInfo);
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
unRegisterMenuInfo(eventKey);
|
||||
});
|
||||
|
||||
const subMenuPrefixCls = computed(() => `${prefixCls.value}-submenu`);
|
||||
const mergedDisabled = computed(() => contextDisabled.value || props.disabled);
|
||||
const elementRef = ref();
|
||||
|
@ -71,20 +111,20 @@ export default defineComponent({
|
|||
// >>>> Title click
|
||||
const onInternalTitleClick = (e: Event) => {
|
||||
// Skip if disabled
|
||||
if (mergedDisabled) {
|
||||
if (mergedDisabled.value) {
|
||||
return;
|
||||
}
|
||||
emit('titleClick', e, key);
|
||||
|
||||
// Trigger open by click when mode is `inline`
|
||||
if (mode.value === 'inline') {
|
||||
onOpenChange(key, !originOpen);
|
||||
onOpenChange(eventKey, !originOpen.value);
|
||||
}
|
||||
};
|
||||
|
||||
const onMouseEnter = (event: MouseEvent) => {
|
||||
if (!mergedDisabled.value) {
|
||||
changeActiveKeys([...parentKeys.value, key]);
|
||||
changeActiveKeys(keysPath.value);
|
||||
emit('titleMouseenter', event);
|
||||
}
|
||||
};
|
||||
|
@ -96,12 +136,12 @@ export default defineComponent({
|
|||
};
|
||||
|
||||
// ========================== DirectionStyle ==========================
|
||||
const directionStyle = useDirectionStyle(computed(() => parentKeys.value.length));
|
||||
const directionStyle = useDirectionStyle(computed(() => keysPath.value.length));
|
||||
|
||||
// >>>>> Visible change
|
||||
const onPopupVisibleChange = (newVisible: boolean) => {
|
||||
if (mode.value !== 'inline') {
|
||||
onOpenChange(key, newVisible);
|
||||
onOpenChange(eventKey, newVisible);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -110,11 +150,11 @@ export default defineComponent({
|
|||
* We should manually trigger an active
|
||||
*/
|
||||
const onInternalFocus = () => {
|
||||
changeActiveKeys([...parentKeys.value, key]);
|
||||
changeActiveKeys(keysPath.value);
|
||||
};
|
||||
|
||||
// =============================== Render ===============================
|
||||
const popupId = key && `${key}-popup`;
|
||||
const popupId = eventKey && `${eventKey}-popup`;
|
||||
|
||||
const popupClassName = computed(() =>
|
||||
classNames(
|
||||
|
@ -152,7 +192,7 @@ export default defineComponent({
|
|||
|
||||
// Cache mode if it change to `inline` which do not have popup motion
|
||||
const triggerModeRef = computed(() => {
|
||||
return mode.value !== 'inline' && parentKeys.value.length > 1 ? 'vertical' : mode.value;
|
||||
return mode.value !== 'inline' && keysPath.value.length > 1 ? 'vertical' : mode.value;
|
||||
});
|
||||
|
||||
const renderMode = computed(() => (mode.value === 'horizontal' ? 'vertical' : mode.value));
|
||||
|
@ -240,7 +280,7 @@ export default defineComponent({
|
|||
|
||||
{/* Inline mode */}
|
||||
{!overflowDisabled.value && (
|
||||
<InlineSubMenuList id={popupId} open={open.value} keyPath={parentKeys.value}>
|
||||
<InlineSubMenuList id={popupId} open={open.value} keyPath={keysPath.value}>
|
||||
{slots.default?.()}
|
||||
</InlineSubMenuList>
|
||||
)}
|
||||
|
|
|
@ -1,20 +1,23 @@
|
|||
import { Key } from '../../../_util/type';
|
||||
import { computed, ComputedRef, getCurrentInstance, inject, InjectionKey, provide } from 'vue';
|
||||
import { computed, ComputedRef, inject, InjectionKey, provide } from 'vue';
|
||||
import { StoreMenuInfo } from './useMenuContext';
|
||||
|
||||
const KeyPathContext: InjectionKey<ComputedRef<Key[]>> = Symbol('KeyPathContext');
|
||||
const KeyPathContext: InjectionKey<{
|
||||
parentEventKeys: ComputedRef<Key[]>;
|
||||
parentInfo: StoreMenuInfo;
|
||||
}> = Symbol('KeyPathContext');
|
||||
|
||||
const useInjectKeyPath = () => {
|
||||
return inject(
|
||||
KeyPathContext,
|
||||
computed(() => []),
|
||||
);
|
||||
return inject(KeyPathContext, {
|
||||
parentEventKeys: computed(() => []),
|
||||
parentInfo: {} as StoreMenuInfo,
|
||||
});
|
||||
};
|
||||
|
||||
const useProvideKeyPath = () => {
|
||||
const parentKeys = useInjectKeyPath();
|
||||
const key = getCurrentInstance().vnode.key;
|
||||
const keys = computed(() => [...parentKeys.value, key]);
|
||||
provide(KeyPathContext, keys);
|
||||
const useProvideKeyPath = (eventKey: string, menuInfo: StoreMenuInfo) => {
|
||||
const { parentEventKeys } = useInjectKeyPath();
|
||||
const keys = computed(() => [...parentEventKeys.value, eventKey]);
|
||||
provide(KeyPathContext, { parentEventKeys: keys, parentInfo: menuInfo });
|
||||
return keys;
|
||||
};
|
||||
|
||||
|
|
|
@ -1,8 +1,18 @@
|
|||
import { Key } from '../../../_util/type';
|
||||
import { ComputedRef, defineComponent, inject, InjectionKey, provide, Ref } from 'vue';
|
||||
import { ComputedRef, defineComponent, inject, InjectionKey, provide, Ref, UnwrapRef } from 'vue';
|
||||
import { BuiltinPlacements, MenuMode, MenuTheme, TriggerSubMenuAction } from '../interface';
|
||||
|
||||
export interface StoreMenuInfo {
|
||||
eventKey: string;
|
||||
key: Key;
|
||||
parentEventKeys: ComputedRef<Key[]>;
|
||||
childrenEventKeys: Ref<Key[]>;
|
||||
isLeaf?: boolean;
|
||||
}
|
||||
export interface MenuContextProps {
|
||||
store: UnwrapRef<Record<string, StoreMenuInfo>>;
|
||||
registerMenuInfo: (key: string, info: StoreMenuInfo) => void;
|
||||
unRegisterMenuInfo: (key: string) => void;
|
||||
prefixCls: ComputedRef<string>;
|
||||
openKeys: Ref<Key[]>;
|
||||
selectedKeys: Ref<Key[]>;
|
||||
|
|
2
v2-doc
2
v2-doc
|
@ -1 +1 @@
|
|||
Subproject commit d197053285b81e77718621c0b5b94cb3b21831a2
|
||||
Subproject commit a7013ae87f69dcbcf547f4b023255b8a7a775557
|
Loading…
Reference in New Issue