refactor: menu

pull/4171/head
tanjinzhou 4 years ago
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 bughack
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…
Cancel
Save