diff --git a/components/menu/src/Menu.tsx b/components/menu/src/Menu.tsx index f9e8e7ef0..26d7a2dc4 100644 --- a/components/menu/src/Menu.tsx +++ b/components/menu/src/Menu.tsx @@ -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 + index > lastVisibleIndex.value) }} + > + {child} + + )); + const overflowedIndicator = ; + // data-hack-store-update 初步判断是 vue bug,先用hack方式 return ( - + 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 ( + + {originOmitItems} + + ); + }} + maxCount={ + mergedMode.value !== 'horizontal' || props.disabledOverflow + ? Overflow.INVALIDATE + : Overflow.RESPONSIVE + } + ssr="full" + data-menu-list + onVisibleChange={newLastIndex => { + lastVisibleIndex.value = newLastIndex; + }} + /> ); }; }, diff --git a/components/menu/src/MenuItem.tsx b/components/menu/src/MenuItem.tsx index 33b5a6f75..4608128ad 100644 --- a/components/menu/src/MenuItem.tsx +++ b/components/menu/src/MenuItem.tsx @@ -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`} > -
  • + ); }; diff --git a/components/menu/src/SubMenu.tsx b/components/menu/src/SubMenu.tsx index 38b97b93e..a641ad69b 100644 --- a/components/menu/src/SubMenu.tsx +++ b/components/menu/src/SubMenu.tsx @@ -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, internalPopupClose: Boolean, + eventKey: String, }; export type SubMenuProps = Partial>; @@ -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 ( -
  • )} -
  • + ); }; diff --git a/components/vc-overflow/Item.tsx b/components/vc-overflow/Item.tsx index 7090f02bc..a45dd7021 100644 --- a/components/vc-overflow/Item.tsx +++ b/components/vc-overflow/Item.tsx @@ -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, }; } diff --git a/components/vc-overflow/Overflow.tsx b/components/vc-overflow/Overflow.tsx index d47d95535..54659d237 100644 --- a/components/vc-overflow/Overflow.tsx +++ b/components/vc-overflow/Overflow.tsx @@ -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 */ diff --git a/components/vc-overflow/RawItem.tsx b/components/vc-overflow/RawItem.tsx index 34af10394..65a5c1115 100644 --- a/components/vc-overflow/RawItem.tsx +++ b/components/vc-overflow/RawItem.tsx @@ -9,6 +9,7 @@ export default defineComponent({ inheritAttrs: false, props: { component: PropTypes.any, + title: PropTypes.any, }, setup(props, { slots, attrs }) { const context = useInjectOverflowContext(); diff --git a/components/vc-overflow/assets/index.less b/components/vc-overflow/assets/index.less new file mode 100644 index 000000000..3480ae66c --- /dev/null +++ b/components/vc-overflow/assets/index.less @@ -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%; + } +} diff --git a/components/vc-overflow/examples/basic.tsx b/components/vc-overflow/examples/basic.tsx index 511f276cd..508757b16 100644 --- a/components/vc-overflow/examples/basic.tsx +++ b/components/vc-overflow/examples/basic.tsx @@ -54,7 +54,7 @@ export default defineComponent({