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