refactor: menu
parent
994df6ff6f
commit
2ab77978f2
|
@ -1,9 +1,14 @@
|
||||||
import usePrefixCls from 'ant-design-vue/es/_util/hooks/usePrefixCls';
|
import { Key } from '../../_util/type';
|
||||||
import { defineComponent, ExtractPropTypes } from 'vue';
|
import { computed, defineComponent, ExtractPropTypes, ref, PropType } from 'vue';
|
||||||
import useProvideMenu from './hooks/useMenuContext';
|
import useProvideMenu from './hooks/useMenuContext';
|
||||||
|
import useConfigInject from '../../_util/hooks/useConfigInject';
|
||||||
|
import { MenuTheme, MenuMode } from './interface';
|
||||||
|
|
||||||
export const menuProps = {
|
export const menuProps = {
|
||||||
prefixCls: String,
|
prefixCls: String,
|
||||||
|
disabled: Boolean,
|
||||||
|
theme: { type: String as PropType<MenuTheme>, default: 'light' },
|
||||||
|
mode: { type: String as PropType<MenuMode>, default: 'vertical' },
|
||||||
};
|
};
|
||||||
|
|
||||||
export type MenuProps = Partial<ExtractPropTypes<typeof menuProps>>;
|
export type MenuProps = Partial<ExtractPropTypes<typeof menuProps>>;
|
||||||
|
@ -12,10 +17,30 @@ export default defineComponent({
|
||||||
name: 'AMenu',
|
name: 'AMenu',
|
||||||
props: menuProps,
|
props: menuProps,
|
||||||
setup(props, { slots }) {
|
setup(props, { slots }) {
|
||||||
const prefixCls = usePrefixCls('menu', props);
|
const { prefixCls, direction } = useConfigInject('menu', props);
|
||||||
useProvideMenu({ prefixCls });
|
const activeKeys = ref([]);
|
||||||
|
const openKeys = ref([]);
|
||||||
|
const selectedKeys = ref([]);
|
||||||
|
const changeActiveKeys = (keys: Key[]) => {
|
||||||
|
activeKeys.value = keys;
|
||||||
|
};
|
||||||
|
const disabled = computed(() => !!props.disabled);
|
||||||
|
useProvideMenu({ prefixCls, activeKeys, openKeys, selectedKeys, changeActiveKeys, disabled });
|
||||||
|
const isRtl = computed(() => direction.value === 'rtl');
|
||||||
|
const mergedMode = ref('vertical');
|
||||||
|
const mergedInlineCollapsed = ref(false);
|
||||||
|
const className = computed(() => {
|
||||||
|
return {
|
||||||
|
[`${prefixCls.value}`]: true,
|
||||||
|
[`${prefixCls.value}-root`]: true,
|
||||||
|
[`${prefixCls.value}-${mergedMode.value}`]: true,
|
||||||
|
[`${prefixCls.value}-inline-collapsed`]: mergedInlineCollapsed.value,
|
||||||
|
[`${prefixCls.value}-rtl`]: isRtl.value,
|
||||||
|
[`${prefixCls.value}-${props.theme}`]: true,
|
||||||
|
};
|
||||||
|
});
|
||||||
return () => {
|
return () => {
|
||||||
return <div>{slots.default?.()}</div>;
|
return <ul class={className.value}>{slots.default?.()}</ul>;
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,16 +1,76 @@
|
||||||
import { defineComponent, getCurrentInstance } from 'vue';
|
import { computed, defineComponent, getCurrentInstance, ref, watch } from 'vue';
|
||||||
|
import { useInjectKeyPath } from './hooks/useKeyPath';
|
||||||
|
import { useInjectMenu } from './hooks/useMenuContext';
|
||||||
|
|
||||||
let indexGuid = 0;
|
let indexGuid = 0;
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'AMenuItem',
|
name: 'AMenuItem',
|
||||||
setup(props, { slots }) {
|
props: {
|
||||||
|
role: String,
|
||||||
|
disabled: Boolean,
|
||||||
|
},
|
||||||
|
emits: ['mouseenter', 'mouseleave'],
|
||||||
|
setup(props, { slots, emit }) {
|
||||||
const instance = getCurrentInstance();
|
const instance = getCurrentInstance();
|
||||||
const key = instance.vnode.key;
|
const key = instance.vnode.key;
|
||||||
const uniKey = `menu_item_${++indexGuid}`;
|
const uniKey = `menu_item_${++indexGuid}`;
|
||||||
|
const parentKeys = useInjectKeyPath();
|
||||||
|
console.log(parentKeys.value);
|
||||||
|
const { prefixCls, activeKeys, disabled, changeActiveKeys } = useInjectMenu();
|
||||||
|
const isActive = ref(false);
|
||||||
|
watch(
|
||||||
|
activeKeys,
|
||||||
|
() => {
|
||||||
|
isActive.value = !!activeKeys.value.find(val => val === key);
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
);
|
||||||
|
const mergedDisabled = computed(() => disabled.value || props.disabled);
|
||||||
|
const selected = computed(() => false);
|
||||||
|
const classNames = computed(() => {
|
||||||
|
const itemCls = `${prefixCls.value}-item`;
|
||||||
|
return {
|
||||||
|
[`${itemCls}`]: true,
|
||||||
|
[`${itemCls}-active`]: isActive.value,
|
||||||
|
[`${itemCls}-selected`]: selected.value,
|
||||||
|
[`${itemCls}-disabled`]: mergedDisabled.value,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const onMouseEnter = (event: MouseEvent) => {
|
||||||
|
if (!mergedDisabled.value) {
|
||||||
|
changeActiveKeys([...parentKeys.value, key]);
|
||||||
|
emit('mouseenter', event);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const onMouseLeave = (event: MouseEvent) => {
|
||||||
|
if (!mergedDisabled.value) {
|
||||||
|
changeActiveKeys([]);
|
||||||
|
emit('mouseleave', event);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
return <li>{slots.default?.()}</li>;
|
// ============================ Render ============================
|
||||||
|
const optionRoleProps = {};
|
||||||
|
|
||||||
|
if (props.role === 'option') {
|
||||||
|
optionRoleProps['aria-selected'] = selected.value;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<li
|
||||||
|
class={classNames.value}
|
||||||
|
role={props.role || 'menuitem'}
|
||||||
|
tabindex={props.disabled ? null : -1}
|
||||||
|
data-menu-id={key}
|
||||||
|
aria-disabled={props.disabled}
|
||||||
|
{...optionRoleProps}
|
||||||
|
onMouseenter={onMouseEnter}
|
||||||
|
onMouseleave={onMouseLeave}
|
||||||
|
>
|
||||||
|
{slots.default?.()}
|
||||||
|
</li>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
|
import useProvideKeyPath from './hooks/useKeyPath';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'ASubMenu',
|
name: 'ASubMenu',
|
||||||
setup(props, { slots }) {
|
setup(props, { slots }) {
|
||||||
|
useProvideKeyPath();
|
||||||
return () => {
|
return () => {
|
||||||
return <ul>{slots.default?.()}</ul>;
|
return <ul>{slots.default?.()}</ul>;
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { Key } from '../../../_util/type';
|
||||||
|
import { computed, ComputedRef, getCurrentInstance, inject, InjectionKey, provide } from 'vue';
|
||||||
|
|
||||||
|
const KeyPathContext: InjectionKey<ComputedRef<Key[]>> = Symbol('KeyPathContext');
|
||||||
|
|
||||||
|
const useInjectKeyPath = () => {
|
||||||
|
return inject(
|
||||||
|
KeyPathContext,
|
||||||
|
computed(() => []),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const useProvideKeyPath = () => {
|
||||||
|
const parentKeys = useInjectKeyPath();
|
||||||
|
const key = getCurrentInstance().vnode.key;
|
||||||
|
const keys = computed(() => [...parentKeys.value, key]);
|
||||||
|
provide(KeyPathContext, keys);
|
||||||
|
return keys;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { useProvideKeyPath, useInjectKeyPath, KeyPathContext };
|
||||||
|
|
||||||
|
export default useProvideKeyPath;
|
|
@ -1,4 +1,5 @@
|
||||||
import { computed, ComputedRef, inject, InjectionKey, provide } from 'vue';
|
import { Key } from '../../../_util/type';
|
||||||
|
import { ComputedRef, inject, InjectionKey, provide, Ref } from 'vue';
|
||||||
|
|
||||||
// import {
|
// import {
|
||||||
// BuiltinPlacements,
|
// BuiltinPlacements,
|
||||||
|
@ -10,19 +11,21 @@ import { computed, ComputedRef, inject, InjectionKey, provide } from 'vue';
|
||||||
|
|
||||||
export interface MenuContextProps {
|
export interface MenuContextProps {
|
||||||
prefixCls: ComputedRef<string>;
|
prefixCls: ComputedRef<string>;
|
||||||
// openKeys: string[];
|
openKeys: Ref<Key[]>;
|
||||||
|
selectedKeys: Ref<Key[]>;
|
||||||
// rtl?: boolean;
|
// rtl?: boolean;
|
||||||
|
|
||||||
// // Mode
|
// // Mode
|
||||||
// mode: MenuMode;
|
// mode: MenuMode;
|
||||||
|
|
||||||
// // Disabled
|
// // Disabled
|
||||||
// disabled?: boolean;
|
disabled?: ComputedRef<boolean>;
|
||||||
// // Used for overflow only. Prevent hidden node trigger open
|
// // Used for overflow only. Prevent hidden node trigger open
|
||||||
// overflowDisabled?: boolean;
|
// overflowDisabled?: boolean;
|
||||||
|
|
||||||
// // Active
|
// // Active
|
||||||
// activeKey: string;
|
activeKeys: Ref<Key[]>;
|
||||||
|
changeActiveKeys: (keys: Key[]) => void;
|
||||||
// onActive: (key: string) => void;
|
// onActive: (key: string) => void;
|
||||||
// onInactive: (key: string) => void;
|
// onInactive: (key: string) => void;
|
||||||
|
|
||||||
|
@ -60,9 +63,7 @@ const useProvideMenu = (props: MenuContextProps) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const useInjectMenu = () => {
|
const useInjectMenu = () => {
|
||||||
return inject(MenuContextKey, {
|
return inject(MenuContextKey);
|
||||||
prefixCls: computed(() => 'ant'),
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export { useProvideMenu, MenuContextKey, useInjectMenu };
|
export { useProvideMenu, MenuContextKey, useInjectMenu };
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
export type MenuTheme = 'light' | 'dark';
|
||||||
|
|
||||||
// ========================== Basic ==========================
|
// ========================== Basic ==========================
|
||||||
export type MenuMode = 'horizontal' | 'vertical' | 'inline';
|
export type MenuMode = 'horizontal' | 'vertical' | 'inline';
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
mode="inline"
|
mode="inline"
|
||||||
@click="handleClick"
|
@click="handleClick"
|
||||||
>
|
>
|
||||||
|
<a-menu-item key="0">Option 0</a-menu-item>
|
||||||
<a-sub-menu key="sub1" @titleClick="titleClick">
|
<a-sub-menu key="sub1" @titleClick="titleClick">
|
||||||
<template #title>
|
<template #title>
|
||||||
<span>
|
<span>
|
||||||
|
|
2
v2-doc
2
v2-doc
|
@ -1 +1 @@
|
||||||
Subproject commit a7013ae87f69dcbcf547f4b023255b8a7a775557
|
Subproject commit d197053285b81e77718621c0b5b94cb3b21831a2
|
Loading…
Reference in New Issue