Skip to content

Commit 1281e4a

Browse files
committed
refactor: menu
1 parent 08a5ff3 commit 1281e4a

File tree

10 files changed

+159
-19
lines changed

10 files changed

+159
-19
lines changed

components/menu/src/Menu.tsx

Lines changed: 72 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@ import {
1313
UnwrapRef,
1414
} from 'vue';
1515
import shallowEqual from '../../_util/shallowequal';
16-
import useProvideMenu, { StoreMenuInfo, useProvideFirstLevel } from './hooks/useMenuContext';
16+
import useProvideMenu, {
17+
MenuContextProvider,
18+
StoreMenuInfo,
19+
useProvideFirstLevel,
20+
} from './hooks/useMenuContext';
1721
import useConfigInject from '../../_util/hooks/useConfigInject';
1822
import {
1923
MenuTheme,
@@ -27,12 +31,17 @@ import devWarning from '../../vc-util/devWarning';
2731
import { collapseMotion, CSSMotionProps } from '../../_util/transition';
2832
import uniq from 'lodash-es/uniq';
2933
import { SiderCollapsedKey } from '../../layout/injectionKey';
34+
import { flattenChildren } from '../../_util/props-util';
35+
import Overflow from '../../vc-overflow';
36+
import MenuItem from './MenuItem';
37+
import SubMenu from './SubMenu';
38+
import EllipsisOutlined from '@ant-design/icons-vue/EllipsisOutlined';
3039

3140
export const menuProps = {
3241
prefixCls: String,
3342
disabled: Boolean,
3443
inlineCollapsed: Boolean,
35-
overflowDisabled: Boolean,
44+
disabledOverflow: Boolean,
3645
openKeys: Array,
3746
selectedKeys: Array,
3847
activeKey: String, // 内部组件使用
@@ -341,6 +350,8 @@ export default defineComponent({
341350
store.value = { ...store.value };
342351
};
343352

353+
const lastVisibleIndex = ref(0);
354+
344355
useProvideMenu({
345356
store,
346357
prefixCls,
@@ -362,7 +373,7 @@ export default defineComponent({
362373
siderCollapsed,
363374
defaultMotions: computed(() => (isMounted.value ? defaultMotions : null)),
364375
motion: computed(() => (isMounted.value ? props.motion : null)),
365-
overflowDisabled: computed(() => props.overflowDisabled),
376+
overflowDisabled: computed(() => props.disabledOverflow),
366377
onOpenChange: onInternalOpenChange,
367378
onItemClick: onInternalClick,
368379
registerMenuInfo,
@@ -371,11 +382,66 @@ export default defineComponent({
371382
isRootMenu: true,
372383
});
373384
return () => {
385+
const childList = flattenChildren(slots.default?.());
386+
const allVisible =
387+
lastVisibleIndex.value >= childList.length - 1 ||
388+
mergedMode.value !== 'horizontal' ||
389+
props.disabledOverflow;
390+
// >>>>> Children
391+
const wrappedChildList =
392+
mergedMode.value !== 'horizontal' || props.disabledOverflow
393+
? childList
394+
: // Need wrap for overflow dropdown that do not response for open
395+
childList.map((child, index) => (
396+
// Always wrap provider to avoid sub node re-mount
397+
<MenuContextProvider
398+
key={child.key}
399+
props={{ overflowDisabled: computed(() => index > lastVisibleIndex.value) }}
400+
>
401+
{child}
402+
</MenuContextProvider>
403+
));
404+
const overflowedIndicator = <EllipsisOutlined />;
405+
374406
// data-hack-store-update 初步判断是 vue bug,先用hack方式
375407
return (
376-
<ul data-hack-store-update={store.value} class={className.value} tabindex="0">
377-
{slots.default?.()}
378-
</ul>
408+
<Overflow
409+
data-hack-store-update={store.value}
410+
prefixCls={`${prefixCls.value}-overflow`}
411+
component="ul"
412+
itemComponent={MenuItem}
413+
class={className.value}
414+
role="menu"
415+
data={wrappedChildList}
416+
renderRawItem={node => node}
417+
renderRawRest={omitItems => {
418+
// We use origin list since wrapped list use context to prevent open
419+
const len = omitItems.length;
420+
421+
const originOmitItems = len ? childList.slice(-len) : null;
422+
423+
return (
424+
<SubMenu
425+
eventKey={Overflow.OVERFLOW_KEY}
426+
title={overflowedIndicator}
427+
disabled={allVisible}
428+
internalPopupClose={len === 0}
429+
>
430+
{originOmitItems}
431+
</SubMenu>
432+
);
433+
}}
434+
maxCount={
435+
mergedMode.value !== 'horizontal' || props.disabledOverflow
436+
? Overflow.INVALIDATE
437+
: Overflow.RESPONSIVE
438+
}
439+
ssr="full"
440+
data-menu-list
441+
onVisibleChange={newLastIndex => {
442+
lastVisibleIndex.value = newLastIndex;
443+
}}
444+
/>
379445
);
380446
};
381447
},

components/menu/src/MenuItem.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import Tooltip from '../../tooltip';
1616
import { MenuInfo } from './interface';
1717
import KeyCode from '../../_util/KeyCode';
1818
import useDirectionStyle from './hooks/useDirectionStyle';
19+
import Overflow from '../../vc-overflow';
1920

2021
let indexGuid = 0;
2122
const menuItemProps = {
@@ -200,7 +201,8 @@ export default defineComponent({
200201
placement={rtl.value ? 'left' : 'right'}
201202
overlayClassName={`${prefixCls.value}-inline-collapsed-tooltip`}
202203
>
203-
<li
204+
<Overflow.Item
205+
component="li"
204206
{...attrs}
205207
style={{ ...((attrs.style as any) || {}), ...directionStyle.value }}
206208
class={[
@@ -227,7 +229,7 @@ export default defineComponent({
227229
class: `${prefixCls.value}-item-icon`,
228230
})}
229231
{renderItemChildren(icon, children)}
230-
</li>
232+
</Overflow.Item>
231233
</Tooltip>
232234
);
233235
};

components/menu/src/SubMenu.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import SubMenuList from './SubMenuList';
1919
import InlineSubMenuList from './InlineSubMenuList';
2020
import Transition, { getTransitionProps } from '../../_util/transition';
2121
import { cloneElement } from '../../_util/vnode';
22+
import Overflow from '../../vc-overflow';
2223

2324
let indexGuid = 0;
2425

@@ -30,6 +31,7 @@ const subMenuProps = {
3031
popupClassName: String,
3132
popupOffset: Array as PropType<number[]>,
3233
internalPopupClose: Boolean,
34+
eventKey: String,
3335
};
3436

3537
export type SubMenuProps = Partial<ExtractPropTypes<typeof subMenuProps>>;
@@ -48,9 +50,10 @@ export default defineComponent({
4850
instance.vnode.key !== null ? instance.vnode.key : `sub_menu_${++indexGuid}_$$_not_set_key`;
4951

5052
const eventKey =
51-
instance.vnode.key !== null
53+
props.eventKey ??
54+
(instance.vnode.key !== null
5255
? `sub_menu_${++indexGuid}_$$_${instance.vnode.key}`
53-
: (key as string);
56+
: (key as string));
5457
const { parentEventKeys, parentInfo, parentKeys } = useInjectKeyPath();
5558
const keysPath = computed(() => [...parentKeys.value, key]);
5659
const eventKeysPath = computed(() => [...parentEventKeys.value, eventKey]);
@@ -291,7 +294,8 @@ export default defineComponent({
291294
}
292295
return (
293296
<MenuContextProvider props={{ mode: renderMode }}>
294-
<li
297+
<Overflow.Item
298+
component="li"
295299
{...attrs}
296300
role="none"
297301
class={classNames(
@@ -316,7 +320,7 @@ export default defineComponent({
316320
{slots.default?.()}
317321
</InlineSubMenuList>
318322
)}
319-
</li>
323+
</Overflow.Item>
320324
</MenuContextProvider>
321325
);
322326
};

components/vc-overflow/Item.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import classNames from '../_util/classNames';
1212
import { Key, VueNode } from '../_util/type';
1313
import PropTypes from '../_util/vue-types';
1414

15+
const UNDEFINED = undefined;
16+
1517
export default defineComponent({
1618
name: 'Item',
1719
props: {
@@ -57,16 +59,17 @@ export default defineComponent({
5759
} = props;
5860
const children = slots.default?.();
5961
// ================================ Render ================================
60-
const childNode = renderItem && item !== undefined ? renderItem(item) : children;
62+
const childNode = renderItem && item !== UNDEFINED ? renderItem(item) : children;
6163

6264
let overflowStyle: CSSProperties | undefined;
6365
if (!invalidate) {
6466
overflowStyle = {
6567
opacity: mergedHidden.value ? 0 : 1,
66-
height: mergedHidden.value ? 0 : undefined,
67-
overflowY: mergedHidden.value ? 'hidden' : undefined,
68-
order: responsive ? order : undefined,
69-
pointerEvents: mergedHidden.value ? 'none' : undefined,
68+
height: mergedHidden.value ? 0 : UNDEFINED,
69+
overflowY: mergedHidden.value ? 'hidden' : UNDEFINED,
70+
order: responsive ? order : UNDEFINED,
71+
pointerEvents: mergedHidden.value ? 'none' : UNDEFINED,
72+
position: mergedHidden.value ? 'absolute' : UNDEFINED,
7073
};
7174
}
7275

components/vc-overflow/Overflow.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ const Overflow = defineComponent({
6464
renderRawRest: Function as PropType<(items: any[]) => VueNode>,
6565
suffix: PropTypes.any,
6666
component: String,
67-
itemComponent: String,
67+
itemComponent: PropTypes.any,
6868
/** @private This API may be refactor since not well design */
6969
onVisibleChange: Function as PropType<(visibleCount: number) => void>,
7070
/** When set to `full`, ssr will render full items by default and remove at client side */

components/vc-overflow/RawItem.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export default defineComponent({
99
inheritAttrs: false,
1010
props: {
1111
component: PropTypes.any,
12+
title: PropTypes.any,
1213
},
1314
setup(props, { slots, attrs }) {
1415
const context = useInjectOverflowContext();
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
@overflow-prefix-cls: rc-overflow;
2+
3+
.@{overflow-prefix-cls} {
4+
display: flex;
5+
flex-wrap: wrap;
6+
max-width: 100%;
7+
position: relative;
8+
9+
&-item {
10+
background: rgba(0, 255, 0, 0.2);
11+
box-shadow: 0 0 1px black;
12+
flex: none;
13+
max-width: 100%;
14+
}
15+
}

components/vc-overflow/examples/basic.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export default defineComponent({
5454
<button
5555
type="button"
5656
onClick={() => {
57-
responsive.value != !responsive.value;
57+
responsive.value = !responsive.value;
5858
}}
5959
>
6060
{responsive.value ? 'Responsive' : 'MaxCount: 6'}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
* {
2+
box-sizing: border-box;
3+
}

typings/vue-tsx-shim.d.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import 'vue';
2+
3+
type EventHandler = (...args: any[]) => void;
4+
5+
declare module 'vue' {
6+
interface ComponentCustomProps {
7+
role?: string;
8+
tabindex?: number;
9+
// should be removed after Vue supported component events typing
10+
// see: https://github.com/vuejs/vue-next/issues/1553
11+
// https://github.com/vuejs/vue-next/issues/3029
12+
onBlur?: EventHandler;
13+
onOpen?: EventHandler;
14+
onEdit?: EventHandler;
15+
onLoad?: EventHandler;
16+
onClose?: EventHandler;
17+
onFocus?: EventHandler;
18+
onInput?: EventHandler;
19+
onClick?: EventHandler;
20+
onPress?: EventHandler;
21+
onScale?: EventHandler;
22+
onCancel?: EventHandler;
23+
onClosed?: EventHandler;
24+
onChange?: EventHandler;
25+
onDelete?: EventHandler;
26+
onOpened?: EventHandler;
27+
onScroll?: EventHandler;
28+
onSubmit?: EventHandler;
29+
onSelect?: EventHandler;
30+
onToggle?: EventHandler;
31+
onConfirm?: EventHandler;
32+
onPreview?: EventHandler;
33+
onKeypress?: EventHandler;
34+
onTouchend?: EventHandler;
35+
onClickStep?: EventHandler;
36+
onTouchmove?: EventHandler;
37+
onTouchstart?: EventHandler;
38+
onTouchcancel?: EventHandler;
39+
onSelectSearch?: EventHandler;
40+
onMouseenter?: EventHandler;
41+
onMouseleave?: EventHandler;
42+
onMousemove?: EventHandler;
43+
onKeydown?: EventHandler;
44+
onKeyup?: EventHandler;
45+
}
46+
}

0 commit comments

Comments
 (0)