refactor: menu

feat-new-menu
tanjinzhou 2021-05-18 18:56:33 +08:00
parent f0baa6118b
commit 8dd74a9977
6 changed files with 138 additions and 37 deletions

View File

@ -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>;

View File

@ -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);
}
};

View File

@ -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>
)}

View File

@ -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;
};

View File

@ -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

@ -1 +1 @@
Subproject commit d197053285b81e77718621c0b5b94cb3b21831a2
Subproject commit a7013ae87f69dcbcf547f4b023255b8a7a775557