refactor: menu
parent
2ab77978f2
commit
16f051d593
|
@ -1,4 +1,11 @@
|
|||
import { defineComponent, nextTick, Transition as T, TransitionGroup as TG } from 'vue';
|
||||
import {
|
||||
BaseTransitionProps,
|
||||
CSSProperties,
|
||||
defineComponent,
|
||||
nextTick,
|
||||
Transition as T,
|
||||
TransitionGroup as TG,
|
||||
} from 'vue';
|
||||
import { findDOMNode } from './props-util';
|
||||
|
||||
export const getTransitionProps = (transitionName: string, opt: object = {}) => {
|
||||
|
@ -80,6 +87,37 @@ if (process.env.NODE_ENV === 'test') {
|
|||
});
|
||||
}
|
||||
|
||||
export { Transition, TransitionGroup };
|
||||
export declare type MotionEvent = (TransitionEvent | AnimationEvent) & {
|
||||
deadline?: boolean;
|
||||
};
|
||||
|
||||
export declare type MotionEventHandler = (
|
||||
element: HTMLElement,
|
||||
done?: () => void,
|
||||
) => CSSProperties | void;
|
||||
|
||||
export declare type MotionEndEventHandler = (
|
||||
element: HTMLElement,
|
||||
done?: () => void,
|
||||
) => boolean | void;
|
||||
|
||||
// ================== Collapse Motion ==================
|
||||
const getCollapsedHeight: MotionEventHandler = () => ({ height: 0, opacity: 0 });
|
||||
const getRealHeight: MotionEventHandler = node => ({ height: node.scrollHeight, opacity: 1 });
|
||||
const getCurrentHeight: MotionEventHandler = node => ({ height: node.offsetHeight });
|
||||
// const skipOpacityTransition: MotionEndEventHandler = (_, event) =>
|
||||
// (event as TransitionEvent).propertyName === 'height';
|
||||
|
||||
const collapseMotion: BaseTransitionProps<HTMLElement> = {
|
||||
// motionName: 'ant-motion-collapse',
|
||||
appear: true,
|
||||
// onAppearStart: getCollapsedHeight,
|
||||
onBeforeEnter: getCollapsedHeight,
|
||||
onEnter: getRealHeight,
|
||||
onBeforeLeave: getCurrentHeight,
|
||||
onLeave: getCollapsedHeight,
|
||||
};
|
||||
|
||||
export { Transition, TransitionGroup, collapseMotion };
|
||||
|
||||
export default Transition;
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
import { computed, defineComponent, ref, watch } from '@vue/runtime-core';
|
||||
import { useInjectMenu, MenuContextProvider } from './hooks/useMenuContext';
|
||||
import { MenuMode } from './interface';
|
||||
import SubMenuList from './SubMenuList';
|
||||
export default defineComponent({
|
||||
name: 'InlineSubMenuList',
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
id: String,
|
||||
open: Boolean,
|
||||
keyPath: Array,
|
||||
},
|
||||
setup(props, { slots }) {
|
||||
const fixedMode: MenuMode = 'inline';
|
||||
const { prefixCls, forceSubMenuRender, motion, mode } = useInjectMenu();
|
||||
const sameModeRef = computed(() => mode.value === fixedMode);
|
||||
const destroy = ref(!sameModeRef.value);
|
||||
|
||||
// ================================= Effect =================================
|
||||
// Reset destroy state when mode change back
|
||||
watch(
|
||||
mode,
|
||||
() => {
|
||||
if (sameModeRef.value) {
|
||||
destroy.value = false;
|
||||
}
|
||||
},
|
||||
{ flush: 'post' },
|
||||
);
|
||||
let transitionProps = computed(() => {
|
||||
return { appear: props.keyPath.length > 1, css: false };
|
||||
});
|
||||
|
||||
return () => {
|
||||
if (destroy.value) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<MenuContextProvider
|
||||
props={{
|
||||
mode: fixedMode,
|
||||
locked: !sameModeRef.value,
|
||||
}}
|
||||
>
|
||||
<SubMenuList id={props.id}>{slots.default?.()}</SubMenuList>
|
||||
</MenuContextProvider>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
|
@ -8,13 +8,14 @@ export default defineComponent({
|
|||
props: {
|
||||
title: PropTypes.VNodeChild,
|
||||
},
|
||||
inheritAttrs: false,
|
||||
slots: ['title'],
|
||||
setup(props, { slots }) {
|
||||
setup(props, { slots, attrs }) {
|
||||
const { prefixCls } = useInjectMenu();
|
||||
const groupPrefixCls = computed(() => `${prefixCls.value}-item-group`);
|
||||
return () => {
|
||||
return (
|
||||
<li onClick={e => e.stopPropagation()} class={groupPrefixCls.value}>
|
||||
<li {...attrs} onClick={e => e.stopPropagation()} class={groupPrefixCls.value}>
|
||||
<div
|
||||
title={typeof props.title === 'string' ? props.title : undefined}
|
||||
class={`${groupPrefixCls.value}-title`}
|
||||
|
|
|
@ -1,14 +1,36 @@
|
|||
import { Key } from '../../_util/type';
|
||||
import { computed, defineComponent, ExtractPropTypes, ref, PropType } from 'vue';
|
||||
import useProvideMenu from './hooks/useMenuContext';
|
||||
import {
|
||||
computed,
|
||||
defineComponent,
|
||||
ExtractPropTypes,
|
||||
ref,
|
||||
PropType,
|
||||
inject,
|
||||
watchEffect,
|
||||
} from 'vue';
|
||||
import useProvideMenu, { useProvideFirstLevel } from './hooks/useMenuContext';
|
||||
import useConfigInject from '../../_util/hooks/useConfigInject';
|
||||
import { MenuTheme, MenuMode } from './interface';
|
||||
import { MenuTheme, MenuMode, BuiltinPlacements, TriggerSubMenuAction } from './interface';
|
||||
import devWarning from 'ant-design-vue/es/vc-util/devWarning';
|
||||
import { collapseMotion } from 'ant-design-vue/es/_util/transition';
|
||||
|
||||
export const menuProps = {
|
||||
prefixCls: String,
|
||||
disabled: Boolean,
|
||||
inlineCollapsed: Boolean,
|
||||
|
||||
theme: { type: String as PropType<MenuTheme>, default: 'light' },
|
||||
mode: { type: String as PropType<MenuMode>, default: 'vertical' },
|
||||
|
||||
inlineIndent: { type: Number, default: 24 },
|
||||
subMenuOpenDelay: { type: Number, default: 0.1 },
|
||||
subMenuCloseDelay: { type: Number, default: 0.1 },
|
||||
|
||||
builtinPlacements: { type: Object as PropType<BuiltinPlacements> },
|
||||
|
||||
triggerSubMenuAction: { type: String as PropType<TriggerSubMenuAction>, default: 'hover' },
|
||||
|
||||
getPopupContainer: Function as PropType<(node: HTMLElement) => HTMLElement>,
|
||||
};
|
||||
|
||||
export type MenuProps = Partial<ExtractPropTypes<typeof menuProps>>;
|
||||
|
@ -18,6 +40,33 @@ export default defineComponent({
|
|||
props: menuProps,
|
||||
setup(props, { slots }) {
|
||||
const { prefixCls, direction } = useConfigInject('menu', props);
|
||||
|
||||
const siderCollapsed = inject(
|
||||
'layoutSiderCollapsed',
|
||||
computed(() => undefined),
|
||||
);
|
||||
const inlineCollapsed = computed(() => {
|
||||
const { inlineCollapsed } = props;
|
||||
if (siderCollapsed.value !== undefined) {
|
||||
return siderCollapsed.value;
|
||||
}
|
||||
return inlineCollapsed;
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
devWarning(
|
||||
!('inlineCollapsed' in props && props.mode !== 'inline'),
|
||||
'Menu',
|
||||
'`inlineCollapsed` should only be used when `mode` is inline.',
|
||||
);
|
||||
|
||||
devWarning(
|
||||
!(siderCollapsed.value !== undefined && 'inlineCollapsed' in props),
|
||||
'Menu',
|
||||
'`inlineCollapsed` not control Menu under Sider. Should set `collapsed` on Sider instead.',
|
||||
);
|
||||
});
|
||||
|
||||
const activeKeys = ref([]);
|
||||
const openKeys = ref([]);
|
||||
const selectedKeys = ref([]);
|
||||
|
@ -25,10 +74,18 @@ export default defineComponent({
|
|||
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 mergedMode = ref<MenuMode>('vertical');
|
||||
const mergedInlineCollapsed = ref(false);
|
||||
watchEffect(() => {
|
||||
if (props.mode === 'inline' && inlineCollapsed.value) {
|
||||
mergedMode.value = 'vertical';
|
||||
mergedInlineCollapsed.value = inlineCollapsed.value;
|
||||
}
|
||||
mergedMode.value = props.mode;
|
||||
mergedInlineCollapsed.value = false;
|
||||
});
|
||||
|
||||
const className = computed(() => {
|
||||
return {
|
||||
[`${prefixCls.value}`]: true,
|
||||
|
@ -39,6 +96,35 @@ export default defineComponent({
|
|||
[`${prefixCls.value}-${props.theme}`]: true,
|
||||
};
|
||||
});
|
||||
|
||||
const defaultMotions = {
|
||||
horizontal: { motionName: `ant-slide-up` },
|
||||
inline: collapseMotion,
|
||||
other: { motionName: `ant-zoom-big` },
|
||||
};
|
||||
|
||||
useProvideFirstLevel(true);
|
||||
|
||||
useProvideMenu({
|
||||
prefixCls,
|
||||
activeKeys,
|
||||
openKeys,
|
||||
selectedKeys,
|
||||
changeActiveKeys,
|
||||
disabled,
|
||||
rtl: isRtl,
|
||||
mode: mergedMode,
|
||||
inlineIndent: computed(() => props.inlineIndent),
|
||||
subMenuCloseDelay: computed(() => props.subMenuCloseDelay),
|
||||
subMenuOpenDelay: computed(() => props.subMenuOpenDelay),
|
||||
builtinPlacements: computed(() => props.builtinPlacements),
|
||||
triggerSubMenuAction: computed(() => props.triggerSubMenuAction),
|
||||
getPopupContainer: computed(() => props.getPopupContainer),
|
||||
inlineCollapsed: mergedInlineCollapsed,
|
||||
antdMenuTheme: computed(() => props.theme),
|
||||
siderCollapsed,
|
||||
defaultMotions,
|
||||
});
|
||||
return () => {
|
||||
return <ul class={className.value}>{slots.default?.()}</ul>;
|
||||
};
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
import { flattenChildren, getPropsSlot, isValidElement } from '../../_util/props-util';
|
||||
import PropTypes from '../../_util/vue-types';
|
||||
import { computed, defineComponent, getCurrentInstance, ref, watch } from 'vue';
|
||||
import { useInjectKeyPath } from './hooks/useKeyPath';
|
||||
import { useInjectMenu } from './hooks/useMenuContext';
|
||||
import { useInjectFirstLevel, useInjectMenu } from './hooks/useMenuContext';
|
||||
import { cloneElement } from '../../_util/vnode';
|
||||
import Tooltip from '../../tooltip';
|
||||
|
||||
let indexGuid = 0;
|
||||
|
||||
|
@ -9,15 +13,29 @@ export default defineComponent({
|
|||
props: {
|
||||
role: String,
|
||||
disabled: Boolean,
|
||||
danger: Boolean,
|
||||
title: { type: [String, Boolean] },
|
||||
icon: PropTypes.VNodeChild,
|
||||
},
|
||||
emits: ['mouseenter', 'mouseleave'],
|
||||
setup(props, { slots, emit }) {
|
||||
slots: ['icon'],
|
||||
inheritAttrs: false,
|
||||
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 { prefixCls, activeKeys, disabled, changeActiveKeys } = useInjectMenu();
|
||||
const {
|
||||
prefixCls,
|
||||
activeKeys,
|
||||
disabled,
|
||||
changeActiveKeys,
|
||||
rtl,
|
||||
inlineCollapsed,
|
||||
siderCollapsed,
|
||||
} = useInjectMenu();
|
||||
const firstLevel = useInjectFirstLevel();
|
||||
const isActive = ref(false);
|
||||
watch(
|
||||
activeKeys,
|
||||
|
@ -32,6 +50,7 @@ export default defineComponent({
|
|||
const itemCls = `${prefixCls.value}-item`;
|
||||
return {
|
||||
[`${itemCls}`]: true,
|
||||
[`${itemCls}-danger`]: props.danger,
|
||||
[`${itemCls}-active`]: isActive.value,
|
||||
[`${itemCls}-selected`]: selected.value,
|
||||
[`${itemCls}-disabled`]: mergedDisabled.value,
|
||||
|
@ -50,26 +69,80 @@ export default defineComponent({
|
|||
}
|
||||
};
|
||||
|
||||
const renderItemChildren = (icon: any, children: any) => {
|
||||
// inline-collapsed.md demo 依赖 span 来隐藏文字,有 icon 属性,则内部包裹一个 span
|
||||
// ref: https://github.com/ant-design/ant-design/pull/23456
|
||||
if (!icon || (isValidElement(children) && children.type === 'span')) {
|
||||
if (children && inlineCollapsed.value && firstLevel && typeof children === 'string') {
|
||||
return (
|
||||
<div class={`${prefixCls.value}-inline-collapsed-noicon`}>{children.charAt(0)}</div>
|
||||
);
|
||||
}
|
||||
return children;
|
||||
}
|
||||
return <span class={`${prefixCls.value}-title-content`}>{children}</span>;
|
||||
};
|
||||
|
||||
return () => {
|
||||
const { title } = props;
|
||||
const children = flattenChildren(slots.default?.());
|
||||
const childrenLength = children.length;
|
||||
let tooltipTitle: any = title;
|
||||
if (typeof title === 'undefined') {
|
||||
tooltipTitle = firstLevel ? children : '';
|
||||
} else if (title === false) {
|
||||
tooltipTitle = '';
|
||||
}
|
||||
const tooltipProps: any = {
|
||||
title: tooltipTitle,
|
||||
};
|
||||
|
||||
if (!siderCollapsed.value && !inlineCollapsed.value) {
|
||||
tooltipProps.title = null;
|
||||
// Reset `visible` to fix control mode tooltip display not correct
|
||||
// ref: https://github.com/ant-design/ant-design/issues/16742
|
||||
tooltipProps.visible = false;
|
||||
}
|
||||
|
||||
// ============================ Render ============================
|
||||
const optionRoleProps = {};
|
||||
|
||||
if (props.role === 'option') {
|
||||
optionRoleProps['aria-selected'] = selected.value;
|
||||
}
|
||||
|
||||
const icon = getPropsSlot(slots, props, 'icon');
|
||||
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}
|
||||
<Tooltip
|
||||
{...tooltipProps}
|
||||
placement={rtl.value ? 'left' : 'right'}
|
||||
overlayClassName={`${prefixCls.value}-inline-collapsed-tooltip`}
|
||||
>
|
||||
{slots.default?.()}
|
||||
</li>
|
||||
<li
|
||||
{...attrs}
|
||||
class={[
|
||||
classNames.value,
|
||||
{
|
||||
[`${attrs.class}`]: !!attrs.class,
|
||||
[`${prefixCls.value}-item-only-child`]:
|
||||
(icon ? childrenLength + 1 : childrenLength) === 1,
|
||||
},
|
||||
]}
|
||||
role={props.role || 'menuitem'}
|
||||
tabindex={props.disabled ? null : -1}
|
||||
data-menu-id={key}
|
||||
aria-disabled={props.disabled}
|
||||
{...optionRoleProps}
|
||||
onMouseenter={onMouseEnter}
|
||||
onMouseleave={onMouseLeave}
|
||||
title={typeof title === 'string' ? title : undefined}
|
||||
>
|
||||
{cloneElement(icon, {
|
||||
class: `${prefixCls.value}-item-icon`,
|
||||
})}
|
||||
{renderItemChildren(icon, children)}
|
||||
</li>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
},
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
import Trigger from '../../vc-trigger';
|
||||
import { computed, defineComponent, onBeforeUnmount, PropType, ref, watch } from 'vue';
|
||||
import { MenuMode } from './interface';
|
||||
import { useInjectMenu } from './hooks/useMenuContext';
|
||||
import { placements, placementsRtl } from './placements';
|
||||
import raf from '../../_util/raf';
|
||||
import classNames from '../../_util/classNames';
|
||||
|
||||
const popupPlacementMap = {
|
||||
horizontal: 'bottomLeft',
|
||||
vertical: 'rightTop',
|
||||
'vertical-left': 'rightTop',
|
||||
'vertical-right': 'leftTop',
|
||||
};
|
||||
export default defineComponent({
|
||||
name: 'PopupTrigger',
|
||||
props: {
|
||||
prefixCls: String,
|
||||
mode: String as PropType<MenuMode>,
|
||||
visible: Boolean,
|
||||
// popup: React.ReactNode;
|
||||
popupClassName: String,
|
||||
popupOffset: Array as PropType<number[]>,
|
||||
disabled: Boolean,
|
||||
onVisibleChange: Function as PropType<(visible: boolean) => void>,
|
||||
},
|
||||
slots: ['popup'],
|
||||
emits: ['visibleChange'],
|
||||
inheritAttrs: false,
|
||||
setup(props, { slots, emit }) {
|
||||
const innerVisible = ref(false);
|
||||
const {
|
||||
getPopupContainer,
|
||||
rtl,
|
||||
subMenuOpenDelay,
|
||||
subMenuCloseDelay,
|
||||
builtinPlacements,
|
||||
triggerSubMenuAction,
|
||||
} = useInjectMenu();
|
||||
|
||||
const placement = computed(() =>
|
||||
rtl
|
||||
? { ...placementsRtl, ...builtinPlacements.value }
|
||||
: { ...placements, ...builtinPlacements.value },
|
||||
);
|
||||
|
||||
const popupPlacement = computed(() => popupPlacementMap[props.mode]);
|
||||
|
||||
const visibleRef = ref<number>();
|
||||
watch(
|
||||
() => props.visible,
|
||||
visible => {
|
||||
raf.cancel(visibleRef.value);
|
||||
visibleRef.value = raf(() => {
|
||||
innerVisible.value = visible;
|
||||
});
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
onBeforeUnmount(() => {
|
||||
raf.cancel(visibleRef.value);
|
||||
});
|
||||
|
||||
const onVisibleChange = (visible: boolean) => {
|
||||
emit('visibleChange', visible);
|
||||
};
|
||||
return () => {
|
||||
const { prefixCls, popupClassName, mode, popupOffset, disabled } = props;
|
||||
return (
|
||||
<Trigger
|
||||
prefixCls={prefixCls}
|
||||
popupClassName={classNames(
|
||||
`${prefixCls}-popup`,
|
||||
{
|
||||
[`${prefixCls}-rtl`]: rtl,
|
||||
},
|
||||
popupClassName,
|
||||
)}
|
||||
stretch={mode === 'horizontal' ? 'minWidth' : null}
|
||||
getPopupContainer={getPopupContainer.value}
|
||||
builtinPlacements={placement.value}
|
||||
popupPlacement={popupPlacement.value}
|
||||
popupVisible={innerVisible.value}
|
||||
popupAlign={popupOffset && { offset: popupOffset }}
|
||||
action={disabled ? [] : [triggerSubMenuAction.value]}
|
||||
mouseEnterDelay={subMenuOpenDelay.value}
|
||||
mouseLeaveDelay={subMenuCloseDelay.value}
|
||||
onPopupVisibleChange={onVisibleChange}
|
||||
// forceRender={forceSubMenuRender}
|
||||
// popupMotion={mergedMotion}
|
||||
v-slots={{ popup: slots.popup, default: slots.default }}
|
||||
></Trigger>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
|
@ -1,12 +1,74 @@
|
|||
import { defineComponent } from 'vue';
|
||||
import useProvideKeyPath from './hooks/useKeyPath';
|
||||
import PropTypes from '../../_util/vue-types';
|
||||
import { computed, defineComponent } from 'vue';
|
||||
import useProvideKeyPath, { useInjectKeyPath } from './hooks/useKeyPath';
|
||||
import { useInjectMenu, useProvideFirstLevel } from './hooks/useMenuContext';
|
||||
import { getPropsSlot, isValidElement } from 'ant-design-vue/es/_util/props-util';
|
||||
import classNames from 'ant-design-vue/es/_util/classNames';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ASubMenu',
|
||||
setup(props, { slots }) {
|
||||
props: {
|
||||
icon: PropTypes.VNodeChild,
|
||||
title: PropTypes.VNodeChild,
|
||||
disabled: Boolean,
|
||||
level: Number,
|
||||
popupClassName: String,
|
||||
popupOffset: [Number, Number],
|
||||
},
|
||||
slots: ['icon', 'title'],
|
||||
inheritAttrs: false,
|
||||
setup(props, { slots, attrs }) {
|
||||
useProvideKeyPath();
|
||||
useProvideFirstLevel(false);
|
||||
const keyPath = useInjectKeyPath();
|
||||
const {
|
||||
prefixCls,
|
||||
activeKeys,
|
||||
disabled,
|
||||
changeActiveKeys,
|
||||
rtl,
|
||||
mode,
|
||||
inlineCollapsed,
|
||||
antdMenuTheme,
|
||||
} = useInjectMenu();
|
||||
|
||||
const popupClassName = computed(() =>
|
||||
classNames(prefixCls, `${prefixCls.value}-${antdMenuTheme.value}`, props.popupClassName),
|
||||
);
|
||||
const renderTitle = (title: any, icon: any) => {
|
||||
if (!icon) {
|
||||
return inlineCollapsed.value && props.level === 1 && title && typeof title === 'string' ? (
|
||||
<div class={`${prefixCls.value}-inline-collapsed-noicon`}>{title.charAt(0)}</div>
|
||||
) : (
|
||||
title
|
||||
);
|
||||
}
|
||||
// inline-collapsed.md demo 依赖 span 来隐藏文字,有 icon 属性,则内部包裹一个 span
|
||||
// ref: https://github.com/ant-design/ant-design/pull/23456
|
||||
const titleIsSpan = isValidElement(title) && title.type === 'span';
|
||||
return (
|
||||
<>
|
||||
{icon}
|
||||
{titleIsSpan ? title : <span class={`${prefixCls.value}-title-content`}>{title}</span>}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const className = computed(() =>
|
||||
classNames(
|
||||
prefixCls.value,
|
||||
`${prefixCls.value}-sub`,
|
||||
`${prefixCls.value}-${mode.value === 'inline' ? 'inline' : 'vertical'}`,
|
||||
),
|
||||
);
|
||||
return () => {
|
||||
return <ul>{slots.default?.()}</ul>;
|
||||
const icon = getPropsSlot(slots, props, 'icon');
|
||||
const title = renderTitle(getPropsSlot(slots, props, 'title'), icon);
|
||||
return (
|
||||
<ul {...attrs} class={[className.value, attrs.class]} data-menu-list>
|
||||
{slots.default?.()}
|
||||
</ul>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
import classNames from '../../_util/classNames';
|
||||
import { FunctionalComponent, provide } from 'vue';
|
||||
import { useInjectMenu } from './hooks/useMenuContext';
|
||||
const InternalSubMenuList: FunctionalComponent<any> = (_props, { slots, attrs }) => {
|
||||
const { prefixCls, mode } = useInjectMenu();
|
||||
return (
|
||||
<ul
|
||||
{...attrs}
|
||||
class={classNames(
|
||||
prefixCls,
|
||||
`${prefixCls}-sub`,
|
||||
`${prefixCls}-${mode.value === 'inline' ? 'inline' : 'vertical'}`,
|
||||
)}
|
||||
data-menu-list
|
||||
>
|
||||
{slots.default?.()}
|
||||
</ul>
|
||||
);
|
||||
};
|
||||
|
||||
InternalSubMenuList.displayName = 'SubMenuList';
|
||||
|
||||
export default InternalSubMenuList;
|
|
@ -0,0 +1,14 @@
|
|||
import { computed, ComputedRef, CSSProperties } from 'vue';
|
||||
import { useInjectMenu } from './useMenuContext';
|
||||
|
||||
export default function useDirectionStyle(level: ComputedRef<number>): ComputedRef<CSSProperties> {
|
||||
const { mode, rtl, inlineIndent } = useInjectMenu();
|
||||
|
||||
return computed(() =>
|
||||
mode.value !== 'inline'
|
||||
? null
|
||||
: rtl.value
|
||||
? { paddingRight: level.value * inlineIndent.value }
|
||||
: { paddingLeft: level.value * inlineIndent.value },
|
||||
);
|
||||
}
|
|
@ -1,22 +1,22 @@
|
|||
import { Key } from '../../../_util/type';
|
||||
import { ComputedRef, inject, InjectionKey, provide, Ref } from 'vue';
|
||||
|
||||
// import {
|
||||
// BuiltinPlacements,
|
||||
// MenuClickEventHandler,
|
||||
// MenuMode,
|
||||
// RenderIconType,
|
||||
// TriggerSubMenuAction,
|
||||
// } from '../interface';
|
||||
import { ComputedRef, FunctionalComponent, inject, InjectionKey, provide, Ref } from 'vue';
|
||||
import { BuiltinPlacements, MenuMode, MenuTheme, TriggerSubMenuAction } from '../interface';
|
||||
|
||||
export interface MenuContextProps {
|
||||
prefixCls: ComputedRef<string>;
|
||||
openKeys: Ref<Key[]>;
|
||||
selectedKeys: Ref<Key[]>;
|
||||
// rtl?: boolean;
|
||||
rtl?: ComputedRef<boolean>;
|
||||
|
||||
locked?: Ref<boolean>;
|
||||
|
||||
inlineCollapsed: Ref<boolean>;
|
||||
antdMenuTheme?: ComputedRef<MenuTheme>;
|
||||
|
||||
siderCollapsed?: ComputedRef<boolean>;
|
||||
|
||||
// // Mode
|
||||
// mode: MenuMode;
|
||||
mode: Ref<MenuMode>;
|
||||
|
||||
// // Disabled
|
||||
disabled?: ComputedRef<boolean>;
|
||||
|
@ -33,18 +33,18 @@ export interface MenuContextProps {
|
|||
// selectedKeys: string[];
|
||||
|
||||
// // Level
|
||||
// inlineIndent: number;
|
||||
inlineIndent: ComputedRef<number>;
|
||||
|
||||
// // Motion
|
||||
// // motion?: CSSMotionProps;
|
||||
// // defaultMotions?: Partial<{ [key in MenuMode | 'other']: CSSMotionProps }>;
|
||||
motion?: any;
|
||||
defaultMotions?: Partial<{ [key in MenuMode | 'other']: any }>;
|
||||
|
||||
// // Popup
|
||||
// subMenuOpenDelay: number;
|
||||
// subMenuCloseDelay: number;
|
||||
subMenuOpenDelay: ComputedRef<number>;
|
||||
subMenuCloseDelay: ComputedRef<number>;
|
||||
// forceSubMenuRender?: boolean;
|
||||
// builtinPlacements?: BuiltinPlacements;
|
||||
// triggerSubMenuAction?: TriggerSubMenuAction;
|
||||
builtinPlacements?: ComputedRef<BuiltinPlacements>;
|
||||
triggerSubMenuAction?: ComputedRef<TriggerSubMenuAction>;
|
||||
|
||||
// // Icon
|
||||
// itemIcon?: RenderIconType;
|
||||
|
@ -53,7 +53,7 @@ export interface MenuContextProps {
|
|||
// // Function
|
||||
// onItemClick: MenuClickEventHandler;
|
||||
// onOpenChange: (key: string, open: boolean) => void;
|
||||
// getPopupContainer: (node: HTMLElement) => HTMLElement;
|
||||
getPopupContainer: ComputedRef<(node: HTMLElement) => HTMLElement>;
|
||||
}
|
||||
|
||||
const MenuContextKey: InjectionKey<MenuContextProps> = Symbol('menuContextKey');
|
||||
|
@ -66,6 +66,34 @@ const useInjectMenu = () => {
|
|||
return inject(MenuContextKey);
|
||||
};
|
||||
|
||||
export { useProvideMenu, MenuContextKey, useInjectMenu };
|
||||
const MenuFirstLevelContextKey: InjectionKey<Boolean> = Symbol('menuFirstLevelContextKey');
|
||||
const useProvideFirstLevel = (firstLevel: Boolean) => {
|
||||
provide(MenuFirstLevelContextKey, firstLevel);
|
||||
};
|
||||
|
||||
const useInjectFirstLevel = () => {
|
||||
return inject(MenuFirstLevelContextKey, true);
|
||||
};
|
||||
|
||||
const MenuContextProvider: FunctionalComponent<{ props: Record<string, any> }> = (
|
||||
props,
|
||||
{ slots },
|
||||
) => {
|
||||
useProvideMenu({ ...useInjectMenu(), ...props });
|
||||
return slots.default?.();
|
||||
};
|
||||
MenuContextProvider.props = { props: Object };
|
||||
MenuContextProvider.inheritAttrs = false;
|
||||
MenuContextProvider.displayName = 'MenuContextProvider';
|
||||
|
||||
export {
|
||||
useProvideMenu,
|
||||
MenuContextKey,
|
||||
useInjectMenu,
|
||||
MenuFirstLevelContextKey,
|
||||
useProvideFirstLevel,
|
||||
useInjectFirstLevel,
|
||||
MenuContextProvider,
|
||||
};
|
||||
|
||||
export default useProvideMenu;
|
||||
|
|
|
@ -47,7 +47,7 @@ export default defineComponent({
|
|||
showAction: PropTypes.any.def([]),
|
||||
hideAction: PropTypes.any.def([]),
|
||||
getPopupClassNameFromAlign: PropTypes.any.def(returnEmptyString),
|
||||
// onPopupVisibleChange: PropTypes.func.def(noop),
|
||||
onPopupVisibleChange: PropTypes.func.def(noop),
|
||||
afterPopupVisibleChange: PropTypes.func.def(noop),
|
||||
popup: PropTypes.any,
|
||||
popupStyle: PropTypes.object.def(() => ({})),
|
||||
|
@ -443,7 +443,7 @@ export default defineComponent({
|
|||
},
|
||||
|
||||
setPopupVisible(sPopupVisible, event) {
|
||||
const { alignPoint, sPopupVisible: prevPopupVisible, $attrs } = this;
|
||||
const { alignPoint, sPopupVisible: prevPopupVisible, onPopupVisibleChange } = this;
|
||||
this.clearDelayTimer();
|
||||
if (prevPopupVisible !== sPopupVisible) {
|
||||
if (!hasProp(this, 'popupVisible')) {
|
||||
|
@ -452,7 +452,7 @@ export default defineComponent({
|
|||
prevPopupVisible,
|
||||
});
|
||||
}
|
||||
$attrs.onPopupVisibleChange && $attrs.onPopupVisibleChange(sPopupVisible);
|
||||
onPopupVisibleChange && onPopupVisibleChange(sPopupVisible);
|
||||
}
|
||||
// Always record the point position since mouseEnterDelay will delay the show
|
||||
if (alignPoint && event) {
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
import devWarning, { resetWarned } from './warning';
|
||||
|
||||
export { resetWarned };
|
||||
|
||||
export default (valid: boolean, component: string, message: string): void => {
|
||||
devWarning(valid, `[ant-design-vue: ${component}] ${message}`);
|
||||
};
|
|
@ -15,14 +15,11 @@
|
|||
<span>Navigation One</span>
|
||||
</span>
|
||||
</template>
|
||||
<a-menu-item-group key="g1">
|
||||
<template #title>
|
||||
<QqOutlined />
|
||||
<span>Item 1</span>
|
||||
</template>
|
||||
<a-menu-item key="1">Option 1</a-menu-item>
|
||||
<a-menu-item key="2">Option 2</a-menu-item>
|
||||
</a-menu-item-group>
|
||||
<a-menu-item key="1">
|
||||
<template #icon><QqOutlined /></template>
|
||||
Option 1
|
||||
</a-menu-item>
|
||||
<a-menu-item key="2">Option 2</a-menu-item>
|
||||
<a-menu-item-group key="g2" title="Item 2">
|
||||
<a-menu-item key="3">Option 3</a-menu-item>
|
||||
<a-menu-item key="4">Option 4</a-menu-item>
|
||||
|
|
Loading…
Reference in New Issue