refactor: menu
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 bug,先用hack方式
|
// data-hack-store-update 初步判断是 vue bug,先用hack方式
|
||||||
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…
Reference in New Issue