refactor: menu

pull/4171/head
tanjinzhou 4 years ago
parent 08a5ff30ca
commit 1281e4a4c9

@ -13,7 +13,11 @@ import {
UnwrapRef, UnwrapRef,
} from 'vue'; } from 'vue';
import shallowEqual from '../../_util/shallowequal'; 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 useConfigInject from '../../_util/hooks/useConfigInject';
import { import {
MenuTheme, MenuTheme,
@ -27,12 +31,17 @@ import devWarning from '../../vc-util/devWarning';
import { collapseMotion, CSSMotionProps } from '../../_util/transition'; import { collapseMotion, CSSMotionProps } from '../../_util/transition';
import uniq from 'lodash-es/uniq'; import uniq from 'lodash-es/uniq';
import { SiderCollapsedKey } from '../../layout/injectionKey'; 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 = { export const menuProps = {
prefixCls: String, prefixCls: String,
disabled: Boolean, disabled: Boolean,
inlineCollapsed: Boolean, inlineCollapsed: Boolean,
overflowDisabled: Boolean, disabledOverflow: Boolean,
openKeys: Array, openKeys: Array,
selectedKeys: Array, selectedKeys: Array,
activeKey: String, // 使 activeKey: String, // 使
@ -341,6 +350,8 @@ export default defineComponent({
store.value = { ...store.value }; store.value = { ...store.value };
}; };
const lastVisibleIndex = ref(0);
useProvideMenu({ useProvideMenu({
store, store,
prefixCls, prefixCls,
@ -362,7 +373,7 @@ export default defineComponent({
siderCollapsed, siderCollapsed,
defaultMotions: computed(() => (isMounted.value ? defaultMotions : null)), defaultMotions: computed(() => (isMounted.value ? defaultMotions : null)),
motion: computed(() => (isMounted.value ? props.motion : null)), motion: computed(() => (isMounted.value ? props.motion : null)),
overflowDisabled: computed(() => props.overflowDisabled), overflowDisabled: computed(() => props.disabledOverflow),
onOpenChange: onInternalOpenChange, onOpenChange: onInternalOpenChange,
onItemClick: onInternalClick, onItemClick: onInternalClick,
registerMenuInfo, registerMenuInfo,
@ -371,11 +382,66 @@ export default defineComponent({
isRootMenu: true, isRootMenu: true,
}); });
return () => { 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 // data-hack-store-update vue bughack
return ( return (
<ul data-hack-store-update={store.value} class={className.value} tabindex="0"> <Overflow
{slots.default?.()} data-hack-store-update={store.value}
</ul> 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 { MenuInfo } from './interface';
import KeyCode from '../../_util/KeyCode'; import KeyCode from '../../_util/KeyCode';
import useDirectionStyle from './hooks/useDirectionStyle'; import useDirectionStyle from './hooks/useDirectionStyle';
import Overflow from '../../vc-overflow';
let indexGuid = 0; let indexGuid = 0;
const menuItemProps = { const menuItemProps = {
@ -200,7 +201,8 @@ export default defineComponent({
placement={rtl.value ? 'left' : 'right'} placement={rtl.value ? 'left' : 'right'}
overlayClassName={`${prefixCls.value}-inline-collapsed-tooltip`} overlayClassName={`${prefixCls.value}-inline-collapsed-tooltip`}
> >
<li <Overflow.Item
component="li"
{...attrs} {...attrs}
style={{ ...((attrs.style as any) || {}), ...directionStyle.value }} style={{ ...((attrs.style as any) || {}), ...directionStyle.value }}
class={[ class={[
@ -227,7 +229,7 @@ export default defineComponent({
class: `${prefixCls.value}-item-icon`, class: `${prefixCls.value}-item-icon`,
})} })}
{renderItemChildren(icon, children)} {renderItemChildren(icon, children)}
</li> </Overflow.Item>
</Tooltip> </Tooltip>
); );
}; };

@ -19,6 +19,7 @@ import SubMenuList from './SubMenuList';
import InlineSubMenuList from './InlineSubMenuList'; import InlineSubMenuList from './InlineSubMenuList';
import Transition, { getTransitionProps } from '../../_util/transition'; import Transition, { getTransitionProps } from '../../_util/transition';
import { cloneElement } from '../../_util/vnode'; import { cloneElement } from '../../_util/vnode';
import Overflow from '../../vc-overflow';
let indexGuid = 0; let indexGuid = 0;
@ -30,6 +31,7 @@ const subMenuProps = {
popupClassName: String, popupClassName: String,
popupOffset: Array as PropType<number[]>, popupOffset: Array as PropType<number[]>,
internalPopupClose: Boolean, internalPopupClose: Boolean,
eventKey: String,
}; };
export type SubMenuProps = Partial<ExtractPropTypes<typeof subMenuProps>>; 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`; instance.vnode.key !== null ? instance.vnode.key : `sub_menu_${++indexGuid}_$$_not_set_key`;
const eventKey = const eventKey =
instance.vnode.key !== null props.eventKey ??
(instance.vnode.key !== null
? `sub_menu_${++indexGuid}_$$_${instance.vnode.key}` ? `sub_menu_${++indexGuid}_$$_${instance.vnode.key}`
: (key as string); : (key as string));
const { parentEventKeys, parentInfo, parentKeys } = useInjectKeyPath(); const { parentEventKeys, parentInfo, parentKeys } = useInjectKeyPath();
const keysPath = computed(() => [...parentKeys.value, key]); const keysPath = computed(() => [...parentKeys.value, key]);
const eventKeysPath = computed(() => [...parentEventKeys.value, eventKey]); const eventKeysPath = computed(() => [...parentEventKeys.value, eventKey]);
@ -291,7 +294,8 @@ export default defineComponent({
} }
return ( return (
<MenuContextProvider props={{ mode: renderMode }}> <MenuContextProvider props={{ mode: renderMode }}>
<li <Overflow.Item
component="li"
{...attrs} {...attrs}
role="none" role="none"
class={classNames( class={classNames(
@ -316,7 +320,7 @@ export default defineComponent({
{slots.default?.()} {slots.default?.()}
</InlineSubMenuList> </InlineSubMenuList>
)} )}
</li> </Overflow.Item>
</MenuContextProvider> </MenuContextProvider>
); );
}; };

@ -12,6 +12,8 @@ import classNames from '../_util/classNames';
import { Key, VueNode } from '../_util/type'; import { Key, VueNode } from '../_util/type';
import PropTypes from '../_util/vue-types'; import PropTypes from '../_util/vue-types';
const UNDEFINED = undefined;
export default defineComponent({ export default defineComponent({
name: 'Item', name: 'Item',
props: { props: {
@ -57,16 +59,17 @@ export default defineComponent({
} = props; } = props;
const children = slots.default?.(); const children = slots.default?.();
// ================================ Render ================================ // ================================ Render ================================
const childNode = renderItem && item !== undefined ? renderItem(item) : children; const childNode = renderItem && item !== UNDEFINED ? renderItem(item) : children;
let overflowStyle: CSSProperties | undefined; let overflowStyle: CSSProperties | undefined;
if (!invalidate) { if (!invalidate) {
overflowStyle = { overflowStyle = {
opacity: mergedHidden.value ? 0 : 1, opacity: mergedHidden.value ? 0 : 1,
height: mergedHidden.value ? 0 : undefined, height: mergedHidden.value ? 0 : UNDEFINED,
overflowY: mergedHidden.value ? 'hidden' : undefined, overflowY: mergedHidden.value ? 'hidden' : UNDEFINED,
order: responsive ? order : undefined, order: responsive ? order : UNDEFINED,
pointerEvents: mergedHidden.value ? 'none' : 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>, renderRawRest: Function as PropType<(items: any[]) => VueNode>,
suffix: PropTypes.any, suffix: PropTypes.any,
component: String, component: String,
itemComponent: String, itemComponent: PropTypes.any,
/** @private This API may be refactor since not well design */ /** @private This API may be refactor since not well design */
onVisibleChange: Function as PropType<(visibleCount: number) => void>, onVisibleChange: Function as PropType<(visibleCount: number) => void>,
/** When set to `full`, ssr will render full items by default and remove at client side */ /** 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, inheritAttrs: false,
props: { props: {
component: PropTypes.any, component: PropTypes.any,
title: PropTypes.any,
}, },
setup(props, { slots, attrs }) { setup(props, { slots, attrs }) {
const context = useInjectOverflowContext(); 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 <button
type="button" type="button"
onClick={() => { onClick={() => {
responsive.value != !responsive.value; responsive.value = !responsive.value;
}} }}
> >
{responsive.value ? 'Responsive' : 'MaxCount: 6'} {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