2021-06-07 09:35:03 +00:00
|
|
|
import { flattenChildren, getPropsSlot, isValidElement } from '../../_util/props-util';
|
|
|
|
import PropTypes from '../../_util/vue-types';
|
2022-03-26 14:52:54 +00:00
|
|
|
import type { ExtractPropTypes, PropType } from 'vue';
|
2023-04-05 14:03:02 +00:00
|
|
|
import {
|
|
|
|
computed,
|
|
|
|
defineComponent,
|
|
|
|
getCurrentInstance,
|
|
|
|
onBeforeUnmount,
|
|
|
|
shallowRef,
|
|
|
|
watch,
|
|
|
|
} from 'vue';
|
2021-12-09 14:48:53 +00:00
|
|
|
import { useInjectKeyPath, useMeasure } from './hooks/useKeyPath';
|
2021-06-07 09:35:03 +00:00
|
|
|
import { useInjectFirstLevel, useInjectMenu } from './hooks/useMenuContext';
|
|
|
|
import { cloneElement } from '../../_util/vnode';
|
|
|
|
import Tooltip from '../../tooltip';
|
2021-06-26 01:35:40 +00:00
|
|
|
import type { MenuInfo } from './interface';
|
2021-06-07 09:35:03 +00:00
|
|
|
import KeyCode from '../../_util/KeyCode';
|
|
|
|
import useDirectionStyle from './hooks/useDirectionStyle';
|
|
|
|
import Overflow from '../../vc-overflow';
|
2021-08-10 06:36:28 +00:00
|
|
|
import devWarning from '../../vc-util/devWarning';
|
2022-03-26 14:52:54 +00:00
|
|
|
import type { MouseEventHandler } from '../../_util/EventInterface';
|
2021-06-07 09:35:03 +00:00
|
|
|
|
|
|
|
let indexGuid = 0;
|
2022-03-26 14:52:54 +00:00
|
|
|
export const menuItemProps = () => ({
|
2021-10-07 01:23:36 +00:00
|
|
|
id: String,
|
2021-06-07 09:35:03 +00:00
|
|
|
role: String,
|
|
|
|
disabled: Boolean,
|
|
|
|
danger: Boolean,
|
|
|
|
title: { type: [String, Boolean], default: undefined },
|
2021-12-16 09:20:18 +00:00
|
|
|
icon: PropTypes.any,
|
2022-03-26 14:52:54 +00:00
|
|
|
onMouseenter: Function as PropType<MouseEventHandler>,
|
|
|
|
onMouseleave: Function as PropType<MouseEventHandler>,
|
|
|
|
onClick: Function as PropType<MouseEventHandler>,
|
|
|
|
onKeydown: Function as PropType<MouseEventHandler>,
|
|
|
|
onFocus: Function as PropType<MouseEventHandler>,
|
|
|
|
});
|
2021-06-07 09:35:03 +00:00
|
|
|
|
2022-03-26 14:52:54 +00:00
|
|
|
export type MenuItemProps = Partial<ExtractPropTypes<ReturnType<typeof menuItemProps>>>;
|
2021-06-07 09:35:03 +00:00
|
|
|
|
|
|
|
export default defineComponent({
|
2022-09-26 13:33:41 +00:00
|
|
|
compatConfig: { MODE: 3 },
|
2021-06-07 09:35:03 +00:00
|
|
|
name: 'AMenuItem',
|
|
|
|
inheritAttrs: false,
|
2022-03-26 14:52:54 +00:00
|
|
|
props: menuItemProps(),
|
|
|
|
// emits: ['mouseenter', 'mouseleave', 'click', 'keydown', 'focus'],
|
2021-06-07 09:35:03 +00:00
|
|
|
slots: ['icon', 'title'],
|
|
|
|
setup(props, { slots, emit, attrs }) {
|
|
|
|
const instance = getCurrentInstance();
|
2021-12-09 14:48:53 +00:00
|
|
|
const isMeasure = useMeasure();
|
2021-08-10 06:36:28 +00:00
|
|
|
const key =
|
|
|
|
typeof instance.vnode.key === 'symbol' ? String(instance.vnode.key) : instance.vnode.key;
|
|
|
|
devWarning(
|
|
|
|
typeof instance.vnode.key !== 'symbol',
|
|
|
|
'MenuItem',
|
|
|
|
`MenuItem \`:key="${String(key)}"\` not support Symbol type`,
|
|
|
|
);
|
|
|
|
|
2021-06-07 09:35:03 +00:00
|
|
|
const eventKey = `menu_item_${++indexGuid}_$$_${key}`;
|
|
|
|
const { parentEventKeys, parentKeys } = useInjectKeyPath();
|
|
|
|
const {
|
|
|
|
prefixCls,
|
|
|
|
activeKeys,
|
|
|
|
disabled,
|
|
|
|
changeActiveKeys,
|
|
|
|
rtl,
|
|
|
|
inlineCollapsed,
|
|
|
|
siderCollapsed,
|
|
|
|
onItemClick,
|
|
|
|
selectedKeys,
|
|
|
|
registerMenuInfo,
|
|
|
|
unRegisterMenuInfo,
|
|
|
|
} = useInjectMenu();
|
|
|
|
const firstLevel = useInjectFirstLevel();
|
2023-04-05 14:03:02 +00:00
|
|
|
const isActive = shallowRef(false);
|
2021-06-07 09:35:03 +00:00
|
|
|
const keysPath = computed(() => {
|
|
|
|
return [...parentKeys.value, key];
|
|
|
|
});
|
|
|
|
|
|
|
|
// const keysPath = computed(() => [...parentEventKeys.value, eventKey]);
|
|
|
|
const menuInfo = {
|
|
|
|
eventKey,
|
|
|
|
key,
|
|
|
|
parentEventKeys,
|
|
|
|
parentKeys,
|
|
|
|
isLeaf: true,
|
|
|
|
};
|
|
|
|
registerMenuInfo(eventKey, menuInfo);
|
|
|
|
|
|
|
|
onBeforeUnmount(() => {
|
|
|
|
unRegisterMenuInfo(eventKey);
|
|
|
|
});
|
|
|
|
|
|
|
|
watch(
|
|
|
|
activeKeys,
|
|
|
|
() => {
|
|
|
|
isActive.value = !!activeKeys.value.find(val => val === key);
|
|
|
|
},
|
|
|
|
{ immediate: true },
|
|
|
|
);
|
|
|
|
const mergedDisabled = computed(() => disabled.value || props.disabled);
|
|
|
|
const selected = computed(() => selectedKeys.value.includes(key));
|
|
|
|
const classNames = computed(() => {
|
|
|
|
const itemCls = `${prefixCls.value}-item`;
|
|
|
|
return {
|
|
|
|
[`${itemCls}`]: true,
|
|
|
|
[`${itemCls}-danger`]: props.danger,
|
|
|
|
[`${itemCls}-active`]: isActive.value,
|
|
|
|
[`${itemCls}-selected`]: selected.value,
|
|
|
|
[`${itemCls}-disabled`]: mergedDisabled.value,
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
|
|
|
const getEventInfo = (e: MouseEvent | KeyboardEvent): MenuInfo => {
|
|
|
|
return {
|
|
|
|
key,
|
|
|
|
eventKey,
|
|
|
|
keyPath: keysPath.value,
|
|
|
|
eventKeyPath: [...parentEventKeys.value, eventKey],
|
|
|
|
domEvent: e,
|
2021-06-29 07:44:41 +00:00
|
|
|
item: {
|
|
|
|
...props,
|
|
|
|
...attrs,
|
|
|
|
},
|
2021-06-07 09:35:03 +00:00
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
// ============================ Events ============================
|
|
|
|
const onInternalClick = (e: MouseEvent) => {
|
|
|
|
if (mergedDisabled.value) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const info = getEventInfo(e);
|
|
|
|
emit('click', e);
|
|
|
|
onItemClick(info);
|
|
|
|
};
|
|
|
|
|
|
|
|
const onMouseEnter = (event: MouseEvent) => {
|
|
|
|
if (!mergedDisabled.value) {
|
|
|
|
changeActiveKeys(keysPath.value);
|
|
|
|
emit('mouseenter', event);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
const onMouseLeave = (event: MouseEvent) => {
|
|
|
|
if (!mergedDisabled.value) {
|
|
|
|
changeActiveKeys([]);
|
|
|
|
emit('mouseleave', event);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const onInternalKeyDown = (e: KeyboardEvent) => {
|
|
|
|
emit('keydown', e);
|
|
|
|
|
|
|
|
if (e.which === KeyCode.ENTER) {
|
|
|
|
const info = getEventInfo(e);
|
|
|
|
|
|
|
|
// Legacy. Key will also trigger click event
|
|
|
|
emit('click', e);
|
|
|
|
onItemClick(info);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Used for accessibility. Helper will focus element without key board.
|
|
|
|
* We should manually trigger an active
|
|
|
|
*/
|
|
|
|
const onInternalFocus = (e: FocusEvent) => {
|
|
|
|
changeActiveKeys(keysPath.value);
|
|
|
|
emit('focus', e);
|
|
|
|
};
|
|
|
|
|
|
|
|
const renderItemChildren = (icon: any, children: any) => {
|
|
|
|
const wrapNode = <span class={`${prefixCls.value}-title-content`}>{children}</span>;
|
|
|
|
// 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 wrapNode;
|
|
|
|
};
|
|
|
|
|
|
|
|
// ========================== DirectionStyle ==========================
|
|
|
|
const directionStyle = useDirectionStyle(computed(() => keysPath.value.length));
|
|
|
|
|
|
|
|
return () => {
|
2021-12-09 14:48:53 +00:00
|
|
|
if (isMeasure) return null;
|
2021-06-07 09:35:03 +00:00
|
|
|
const title = props.title ?? slots.title?.();
|
|
|
|
const children = flattenChildren(slots.default?.());
|
|
|
|
const childrenLength = children.length;
|
|
|
|
let tooltipTitle: any = title;
|
|
|
|
if (typeof title === 'undefined') {
|
2021-06-24 14:47:36 +00:00
|
|
|
tooltipTitle = firstLevel && childrenLength ? children : '';
|
2021-06-07 09:35:03 +00:00
|
|
|
} 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
|
2023-02-23 03:58:23 +00:00
|
|
|
tooltipProps.open = false;
|
2021-06-07 09:35:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// ============================ Render ============================
|
|
|
|
const optionRoleProps = {};
|
|
|
|
|
|
|
|
if (props.role === 'option') {
|
|
|
|
optionRoleProps['aria-selected'] = selected.value;
|
|
|
|
}
|
|
|
|
|
|
|
|
const icon = getPropsSlot(slots, props, 'icon');
|
|
|
|
return (
|
|
|
|
<Tooltip
|
|
|
|
{...tooltipProps}
|
|
|
|
placement={rtl.value ? 'left' : 'right'}
|
|
|
|
overlayClassName={`${prefixCls.value}-inline-collapsed-tooltip`}
|
|
|
|
>
|
|
|
|
<Overflow.Item
|
|
|
|
component="li"
|
|
|
|
{...attrs}
|
2021-10-07 01:23:36 +00:00
|
|
|
id={props.id}
|
2021-06-07 09:35:03 +00:00
|
|
|
style={{ ...((attrs.style as any) || {}), ...directionStyle.value }}
|
|
|
|
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}
|
|
|
|
onClick={onInternalClick}
|
|
|
|
onKeydown={onInternalKeyDown}
|
|
|
|
onFocus={onInternalFocus}
|
|
|
|
title={typeof title === 'string' ? title : undefined}
|
|
|
|
>
|
2022-03-24 15:57:43 +00:00
|
|
|
{cloneElement(
|
|
|
|
icon,
|
|
|
|
{
|
|
|
|
class: `${prefixCls.value}-item-icon`,
|
|
|
|
},
|
|
|
|
false,
|
|
|
|
)}
|
2021-06-07 09:35:03 +00:00
|
|
|
{renderItemChildren(icon, children)}
|
|
|
|
</Overflow.Item>
|
|
|
|
</Tooltip>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
},
|
|
|
|
});
|