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