refactor: menu
parent
08a5ff30ca
commit
1281e4a4c9
|
@ -13,7 +13,11 @@ import {
|
|||
UnwrapRef,
|
||||
} from 'vue';
|
||||
import shallowEqual from '../../_util/shallowequal';
|
||||
import useProvideMenu, { StoreMenuInfo, useProvideFirstLevel } from './hooks/useMenuContext';
|
||||
import useProvideMenu, {
|
||||
MenuContextProvider,
|
||||
StoreMenuInfo,
|
||||
useProvideFirstLevel,
|
||||
} from './hooks/useMenuContext';
|
||||
import useConfigInject from '../../_util/hooks/useConfigInject';
|
||||
import {
|
||||
MenuTheme,
|
||||
|
@ -27,12 +31,17 @@ import devWarning from '../../vc-util/devWarning';
|
|||
import { collapseMotion, CSSMotionProps } from '../../_util/transition';
|
||||
import uniq from 'lodash-es/uniq';
|
||||
import { SiderCollapsedKey } from '../../layout/injectionKey';
|
||||
import { flattenChildren } from '../../_util/props-util';
|
||||
import Overflow from '../../vc-overflow';
|
||||
import MenuItem from './MenuItem';
|
||||
import SubMenu from './SubMenu';
|
||||
import EllipsisOutlined from '@ant-design/icons-vue/EllipsisOutlined';
|
||||
|
||||
export const menuProps = {
|
||||
prefixCls: String,
|
||||
disabled: Boolean,
|
||||
inlineCollapsed: Boolean,
|
||||
overflowDisabled: Boolean,
|
||||
disabledOverflow: Boolean,
|
||||
openKeys: Array,
|
||||
selectedKeys: Array,
|
||||
activeKey: String, // 内部组件使用
|
||||
|
@ -341,6 +350,8 @@ export default defineComponent({
|
|||
store.value = { ...store.value };
|
||||
};
|
||||
|
||||
const lastVisibleIndex = ref(0);
|
||||
|
||||
useProvideMenu({
|
||||
store,
|
||||
prefixCls,
|
||||
|
@ -362,7 +373,7 @@ export default defineComponent({
|
|||
siderCollapsed,
|
||||
defaultMotions: computed(() => (isMounted.value ? defaultMotions : null)),
|
||||
motion: computed(() => (isMounted.value ? props.motion : null)),
|
||||
overflowDisabled: computed(() => props.overflowDisabled),
|
||||
overflowDisabled: computed(() => props.disabledOverflow),
|
||||
onOpenChange: onInternalOpenChange,
|
||||
onItemClick: onInternalClick,
|
||||
registerMenuInfo,
|
||||
|
@ -371,11 +382,66 @@ export default defineComponent({
|
|||
isRootMenu: true,
|
||||
});
|
||||
return () => {
|
||||
const childList = flattenChildren(slots.default?.());
|
||||
const allVisible =
|
||||
lastVisibleIndex.value >= childList.length - 1 ||
|
||||
mergedMode.value !== 'horizontal' ||
|
||||
props.disabledOverflow;
|
||||
// >>>>> Children
|
||||
const wrappedChildList =
|
||||
mergedMode.value !== 'horizontal' || props.disabledOverflow
|
||||
? childList
|
||||
: // Need wrap for overflow dropdown that do not response for open
|
||||
childList.map((child, index) => (
|
||||
// Always wrap provider to avoid sub node re-mount
|
||||
<MenuContextProvider
|
||||
key={child.key}
|
||||
props={{ overflowDisabled: computed(() => index > lastVisibleIndex.value) }}
|
||||
>
|
||||
{child}
|
||||
</MenuContextProvider>
|
||||
));
|
||||
const overflowedIndicator = <EllipsisOutlined />;
|
||||
|
||||
// data-hack-store-update 初步判断是 vue bug,先用hack方式
|
||||
return (
|
||||
<ul data-hack-store-update={store.value} class={className.value} tabindex="0">
|
||||
{slots.default?.()}
|
||||
</ul>
|
||||
<Overflow
|
||||
data-hack-store-update={store.value}
|
||||
prefixCls={`${prefixCls.value}-overflow`}
|
||||
component="ul"
|
||||
itemComponent={MenuItem}
|
||||
class={className.value}
|
||||
role="menu"
|
||||
data={wrappedChildList}
|
||||
renderRawItem={node => node}
|
||||
renderRawRest={omitItems => {
|
||||
// We use origin list since wrapped list use context to prevent open
|
||||
const len = omitItems.length;
|
||||
|
||||
const originOmitItems = len ? childList.slice(-len) : null;
|
||||
|
||||
return (
|
||||
<SubMenu
|
||||
eventKey={Overflow.OVERFLOW_KEY}
|
||||
title={overflowedIndicator}
|
||||
disabled={allVisible}
|
||||
internalPopupClose={len === 0}
|
||||
>
|
||||
{originOmitItems}
|
||||
</SubMenu>
|
||||
);
|
||||
}}
|
||||
maxCount={
|
||||
mergedMode.value !== 'horizontal' || props.disabledOverflow
|
||||
? Overflow.INVALIDATE
|
||||
: Overflow.RESPONSIVE
|
||||
}
|
||||
ssr="full"
|
||||
data-menu-list
|
||||
onVisibleChange={newLastIndex => {
|
||||
lastVisibleIndex.value = newLastIndex;
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
},
|
||||
|
|
|
@ -16,6 +16,7 @@ import Tooltip from '../../tooltip';
|
|||
import { MenuInfo } from './interface';
|
||||
import KeyCode from '../../_util/KeyCode';
|
||||
import useDirectionStyle from './hooks/useDirectionStyle';
|
||||
import Overflow from '../../vc-overflow';
|
||||
|
||||
let indexGuid = 0;
|
||||
const menuItemProps = {
|
||||
|
@ -200,7 +201,8 @@ export default defineComponent({
|
|||
placement={rtl.value ? 'left' : 'right'}
|
||||
overlayClassName={`${prefixCls.value}-inline-collapsed-tooltip`}
|
||||
>
|
||||
<li
|
||||
<Overflow.Item
|
||||
component="li"
|
||||
{...attrs}
|
||||
style={{ ...((attrs.style as any) || {}), ...directionStyle.value }}
|
||||
class={[
|
||||
|
@ -227,7 +229,7 @@ export default defineComponent({
|
|||
class: `${prefixCls.value}-item-icon`,
|
||||
})}
|
||||
{renderItemChildren(icon, children)}
|
||||
</li>
|
||||
</Overflow.Item>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -19,6 +19,7 @@ import SubMenuList from './SubMenuList';
|
|||
import InlineSubMenuList from './InlineSubMenuList';
|
||||
import Transition, { getTransitionProps } from '../../_util/transition';
|
||||
import { cloneElement } from '../../_util/vnode';
|
||||
import Overflow from '../../vc-overflow';
|
||||
|
||||
let indexGuid = 0;
|
||||
|
||||
|
@ -30,6 +31,7 @@ const subMenuProps = {
|
|||
popupClassName: String,
|
||||
popupOffset: Array as PropType<number[]>,
|
||||
internalPopupClose: Boolean,
|
||||
eventKey: String,
|
||||
};
|
||||
|
||||
export type SubMenuProps = Partial<ExtractPropTypes<typeof subMenuProps>>;
|
||||
|
@ -48,9 +50,10 @@ export default defineComponent({
|
|||
instance.vnode.key !== null ? instance.vnode.key : `sub_menu_${++indexGuid}_$$_not_set_key`;
|
||||
|
||||
const eventKey =
|
||||
instance.vnode.key !== null
|
||||
props.eventKey ??
|
||||
(instance.vnode.key !== null
|
||||
? `sub_menu_${++indexGuid}_$$_${instance.vnode.key}`
|
||||
: (key as string);
|
||||
: (key as string));
|
||||
const { parentEventKeys, parentInfo, parentKeys } = useInjectKeyPath();
|
||||
const keysPath = computed(() => [...parentKeys.value, key]);
|
||||
const eventKeysPath = computed(() => [...parentEventKeys.value, eventKey]);
|
||||
|
@ -291,7 +294,8 @@ export default defineComponent({
|
|||
}
|
||||
return (
|
||||
<MenuContextProvider props={{ mode: renderMode }}>
|
||||
<li
|
||||
<Overflow.Item
|
||||
component="li"
|
||||
{...attrs}
|
||||
role="none"
|
||||
class={classNames(
|
||||
|
@ -316,7 +320,7 @@ export default defineComponent({
|
|||
{slots.default?.()}
|
||||
</InlineSubMenuList>
|
||||
)}
|
||||
</li>
|
||||
</Overflow.Item>
|
||||
</MenuContextProvider>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -12,6 +12,8 @@ import classNames from '../_util/classNames';
|
|||
import { Key, VueNode } from '../_util/type';
|
||||
import PropTypes from '../_util/vue-types';
|
||||
|
||||
const UNDEFINED = undefined;
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Item',
|
||||
props: {
|
||||
|
@ -57,16 +59,17 @@ export default defineComponent({
|
|||
} = props;
|
||||
const children = slots.default?.();
|
||||
// ================================ Render ================================
|
||||
const childNode = renderItem && item !== undefined ? renderItem(item) : children;
|
||||
const childNode = renderItem && item !== UNDEFINED ? renderItem(item) : children;
|
||||
|
||||
let overflowStyle: CSSProperties | undefined;
|
||||
if (!invalidate) {
|
||||
overflowStyle = {
|
||||
opacity: mergedHidden.value ? 0 : 1,
|
||||
height: mergedHidden.value ? 0 : undefined,
|
||||
overflowY: mergedHidden.value ? 'hidden' : undefined,
|
||||
order: responsive ? order : undefined,
|
||||
pointerEvents: mergedHidden.value ? 'none' : undefined,
|
||||
height: mergedHidden.value ? 0 : UNDEFINED,
|
||||
overflowY: mergedHidden.value ? 'hidden' : UNDEFINED,
|
||||
order: responsive ? order : UNDEFINED,
|
||||
pointerEvents: mergedHidden.value ? 'none' : UNDEFINED,
|
||||
position: mergedHidden.value ? 'absolute' : UNDEFINED,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -64,7 +64,7 @@ const Overflow = defineComponent({
|
|||
renderRawRest: Function as PropType<(items: any[]) => VueNode>,
|
||||
suffix: PropTypes.any,
|
||||
component: String,
|
||||
itemComponent: String,
|
||||
itemComponent: PropTypes.any,
|
||||
/** @private This API may be refactor since not well design */
|
||||
onVisibleChange: Function as PropType<(visibleCount: number) => void>,
|
||||
/** When set to `full`, ssr will render full items by default and remove at client side */
|
||||
|
|
|
@ -9,6 +9,7 @@ export default defineComponent({
|
|||
inheritAttrs: false,
|
||||
props: {
|
||||
component: PropTypes.any,
|
||||
title: PropTypes.any,
|
||||
},
|
||||
setup(props, { slots, attrs }) {
|
||||
const context = useInjectOverflowContext();
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
@overflow-prefix-cls: rc-overflow;
|
||||
|
||||
.@{overflow-prefix-cls} {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
max-width: 100%;
|
||||
position: relative;
|
||||
|
||||
&-item {
|
||||
background: rgba(0, 255, 0, 0.2);
|
||||
box-shadow: 0 0 1px black;
|
||||
flex: none;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
|
@ -54,7 +54,7 @@ export default defineComponent({
|
|||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
responsive.value != !responsive.value;
|
||||
responsive.value = !responsive.value;
|
||||
}}
|
||||
>
|
||||
{responsive.value ? 'Responsive' : 'MaxCount: 6'}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
import 'vue';
|
||||
|
||||
type EventHandler = (...args: any[]) => void;
|
||||
|
||||
declare module 'vue' {
|
||||
interface ComponentCustomProps {
|
||||
role?: string;
|
||||
tabindex?: number;
|
||||
// should be removed after Vue supported component events typing
|
||||
// see: https://github.com/vuejs/vue-next/issues/1553
|
||||
// https://github.com/vuejs/vue-next/issues/3029
|
||||
onBlur?: EventHandler;
|
||||
onOpen?: EventHandler;
|
||||
onEdit?: EventHandler;
|
||||
onLoad?: EventHandler;
|
||||
onClose?: EventHandler;
|
||||
onFocus?: EventHandler;
|
||||
onInput?: EventHandler;
|
||||
onClick?: EventHandler;
|
||||
onPress?: EventHandler;
|
||||
onScale?: EventHandler;
|
||||
onCancel?: EventHandler;
|
||||
onClosed?: EventHandler;
|
||||
onChange?: EventHandler;
|
||||
onDelete?: EventHandler;
|
||||
onOpened?: EventHandler;
|
||||
onScroll?: EventHandler;
|
||||
onSubmit?: EventHandler;
|
||||
onSelect?: EventHandler;
|
||||
onToggle?: EventHandler;
|
||||
onConfirm?: EventHandler;
|
||||
onPreview?: EventHandler;
|
||||
onKeypress?: EventHandler;
|
||||
onTouchend?: EventHandler;
|
||||
onClickStep?: EventHandler;
|
||||
onTouchmove?: EventHandler;
|
||||
onTouchstart?: EventHandler;
|
||||
onTouchcancel?: EventHandler;
|
||||
onSelectSearch?: EventHandler;
|
||||
onMouseenter?: EventHandler;
|
||||
onMouseleave?: EventHandler;
|
||||
onMousemove?: EventHandler;
|
||||
onKeydown?: EventHandler;
|
||||
onKeyup?: EventHandler;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue