feat: update menu

pull/2468/head
tangjinzhou 5 years ago
parent 75e1661c16
commit 4311c15755

@ -29,3 +29,9 @@ v-model -> v-model:visible
## Tooltip
v-model -> v-model:visible
## Modal
v-model -> v-model:visible
okButtonProps、cancelButtonProps 扁平化处理

@ -54,12 +54,12 @@ function animate(node, show, done) {
}
const animation = {
enter(node, done) {
onEnter(node, done) {
nextTick(() => {
animate(node, true, done);
});
},
leave(node, done) {
onLeave(node, done) {
return animate(node, false, done);
},
};

@ -118,14 +118,26 @@ const getOptionProps = instance => {
return res;
};
const getComponent = (instance, prop, options = instance, execute = true) => {
const temp = instance[prop];
if (temp !== undefined) {
return typeof temp === 'function' && execute ? temp(options) : temp;
} else {
let com = instance.$slots[prop];
com = execute && com ? com(options) : com;
return Array.isArray(com) && com.length === 1 ? com[0] : com;
if (instance.$) {
const temp = instance[prop];
if (temp !== undefined) {
return typeof temp === 'function' && execute ? temp(options) : temp;
} else {
let com = instance.$slots[prop];
com = execute && com ? com(options) : com;
return Array.isArray(com) && com.length === 1 ? com[0] : com;
}
} else if (isVNode(instance)) {
const temp = instance.props && instance.props[prop];
if (temp !== undefined) {
return typeof temp === 'function' && execute ? temp(options) : temp;
} else if (instance.children && instance.children[name]) {
let com = instance.children[prop];
com = execute && com ? com(options) : com;
return Array.isArray(com) && com.length === 1 ? com[0] : com;
}
}
return undefined;
};
const getComponentFromProp = (instance, prop, options = instance, execute = true) => {
if (instance.$createElement) {
@ -169,13 +181,13 @@ const getComponentFromProp = (instance, prop, options = instance, execute = true
};
const getAllProps = ele => {
let data = ele.data || {};
let componentOptions = ele.componentOptions || {};
if (ele.$vnode) {
data = ele.$vnode.data || {};
componentOptions = ele.$vnode.componentOptions || {};
let props = getOptionProps(ele);
if (ele.$) {
props = { ...props, ...this.$attrs };
} else {
props = { ...props, ...ele.props };
}
return { ...data.props, ...data.attrs, ...componentOptions.propsData };
return props;
};
// 使用 getOptionProps 替换 ,待测试

@ -1,5 +1,6 @@
import { createVNode } from 'vue';
import PropTypes from './vue-types';
import { getOptionProps, getListeners } from './props-util';
import { getOptionProps } from './props-util';
function getDisplayName(WrappedComponent) {
return WrappedComponent.name || 'Component';
@ -15,6 +16,7 @@ export default function wrapWithConnect(WrappedComponent) {
WrappedComponent.props.children = PropTypes.array.def([]);
const ProxyWrappedComponent = {
props,
inheritAttrs: false,
model: WrappedComponent.model,
name: `Proxy_${getDisplayName(WrappedComponent)}`,
methods: {
@ -23,30 +25,21 @@ export default function wrapWithConnect(WrappedComponent) {
},
},
render() {
const { $slots = {}, $scopedSlots } = this;
const { $slots = {} } = this;
const props = getOptionProps(this);
const wrapProps = {
props: {
...props,
__propsSymbol__: Symbol(),
componentWillReceiveProps: { ...props },
children: $slots.default || props.children || [],
},
on: getListeners(this),
...props,
__propsSymbol__: Symbol(),
componentWillReceiveProps: { ...props },
children: props.children || $slots?.default() || [],
slots: $slots,
ref: 'wrappedInstance',
};
if (Object.keys($scopedSlots).length) {
wrapProps.scopedSlots = $scopedSlots;
}
const slotsKey = Object.keys($slots);
return (
<WrappedComponent {...wrapProps} ref="wrappedInstance">
{slotsKey.length
? slotsKey.map(name => {
return <template slot={name}>{$slots[name]}</template>;
})
: null}
</WrappedComponent>
);
return createVNode(WrappedComponent, wrapProps);
// return (
// <WrappedComponent {...wrapProps} ref="wrappedInstance">
// </WrappedComponent>
// );
},
};
Object.keys(methods).map(m => {

@ -1,15 +1,15 @@
import { provide } from 'vue';
import { storeShape } from './PropTypes';
import { getSlot } from '../props-util';
export default {
name: 'StoreProvider',
props: {
store: storeShape.isRequired,
},
provide() {
return {
storeContext: this.$props,
};
created() {
provide('storeContext', this.$props);
},
render() {
return this.$slots.default[0];
return getSlot(this);
},
};

@ -1,6 +1,7 @@
import shallowEqual from 'shallowequal';
import { inject, createVNode } from 'vue';
import omit from 'omit.js';
import { getOptionProps, getListeners } from '../props-util';
import { getOptionProps } from '../props-util';
import PropTypes from '../vue-types';
import proxyComponent from '../proxyComponent';
@ -22,9 +23,12 @@ export default function connect(mapStateToProps) {
});
const Connect = {
name: `Connect_${getDisplayName(WrappedComponent)}`,
inheritAttrs: false,
props,
inject: {
storeContext: { default: () => ({}) },
setup() {
return {
storeContext: inject('storeContext', {}),
};
},
data() {
this.store = this.storeContext.store;
@ -80,25 +84,19 @@ export default function connect(mapStateToProps) {
},
},
render() {
const { $slots = {}, $scopedSlots, subscribed, store } = this;
const { $slots = {}, subscribed, store, $attrs } = this;
const props = getOptionProps(this);
this.preProps = { ...omit(props, ['__propsSymbol__']) };
const wrapProps = {
props: {
...props,
...subscribed,
store,
},
on: getListeners(this),
scopedSlots: $scopedSlots,
...props,
...subscribed,
...$attrs,
store,
slots: $slots,
ref: 'wrappedInstance',
};
return (
<WrappedComponent {...wrapProps} ref="wrappedInstance">
{Object.keys($slots).map(name => {
return <template slot={name}>{$slots[name]}</template>;
})}
</WrappedComponent>
);
return createVNode(WrappedComponent, wrapProps);
// return <WrappedComponent {...wrapProps} ref="wrappedInstance"></WrappedComponent>;
},
};
return proxyComponent(Connect);

@ -1,16 +1,20 @@
import { inject } from 'vue';
import { Item, itemProps } from '../vc-menu';
import { getOptionProps, getListeners } from '../_util/props-util';
import { getOptionProps, getSlot } from '../_util/props-util';
import Tooltip from '../tooltip';
function noop() {}
export default {
name: 'MenuItem',
inheritAttrs: false,
props: itemProps,
inject: {
getInlineCollapsed: { default: () => noop },
layoutSiderContext: { default: () => ({}) },
},
isMenuItem: true,
setup() {
return {
getInlineCollapsed: inject('getInlineCollapsed', noop),
layoutSiderContext: inject('layoutSiderContext', {}),
};
},
methods: {
onKeyDown(e) {
this.$refs.menuItem.onKeyDown(e);
@ -33,24 +37,20 @@ export default {
}
const itemProps = {
props: {
...props,
title,
},
attrs,
on: getListeners(this),
...props,
title,
...attrs,
};
const toolTipProps = {
props: {
...tooltipProps,
placement: 'right',
overlayClassName: `${rootPrefixCls}-inline-collapsed-tooltip`,
},
...tooltipProps,
placement: 'right',
overlayClassName: `${rootPrefixCls}-inline-collapsed-tooltip`,
};
// return <div>ddd</div>;
return (
<Tooltip {...toolTipProps}>
<Item {...itemProps} ref="menuItem">
{$slots.default}
{getSlot(this)}
</Item>
</Tooltip>
);

@ -1,13 +1,18 @@
import { inject } from 'vue';
import { SubMenu as VcSubMenu } from '../vc-menu';
import { getListeners } from '../_util/props-util';
import classNames from 'classnames';
import Omit from 'omit.js';
import { getSlot } from '../_util/props-util';
export default {
name: 'ASubMenu',
isSubMenu: true,
inheritAttrs: false,
props: { ...VcSubMenu.props },
inject: {
menuPropsContext: { default: () => ({}) },
setup() {
return {
menuPropsContext: inject('menuPropsContext', {}),
};
},
methods: {
onKeyDown(e) {
@ -16,27 +21,16 @@ export default {
},
render() {
const { $slots, $scopedSlots } = this;
const { $slots, $attrs } = this;
const { rootPrefixCls, popupClassName } = this.$props;
const { theme: antdMenuTheme } = this.menuPropsContext;
const props = {
props: {
...this.$props,
popupClassName: classNames(`${rootPrefixCls}-${antdMenuTheme}`, popupClassName),
},
...this.$props,
popupClassName: classNames(`${rootPrefixCls}-${antdMenuTheme}`, popupClassName),
ref: 'subMenu',
on: getListeners(this),
scopedSlots: $scopedSlots,
...$attrs,
...Omit($slots, ['default']),
};
const slotsKey = Object.keys($slots);
return (
<VcSubMenu {...props}>
{slotsKey.length
? slotsKey.map(name => {
return <template slot={name}>{$slots[name]}</template>;
})
: null}
</VcSubMenu>
);
return <VcSubMenu {...props}>{getSlot(this)}</VcSubMenu>;
},
};

@ -1,3 +1,4 @@
import { inject, provide } from 'vue';
import omit from 'omit.js';
import VcMenu, { Divider, ItemGroup } from '../vc-menu';
import SubMenu from './SubMenu';
@ -5,11 +6,10 @@ import PropTypes from '../_util/vue-types';
import animation from '../_util/openAnimation';
import warning from '../_util/warning';
import Item from './MenuItem';
import { hasProp, getListeners, getOptionProps } from '../_util/props-util';
import { hasProp, getOptionProps, getSlot } from '../_util/props-util';
import BaseMixin from '../_util/BaseMixin';
import commonPropsType from '../vc-menu/commonPropsType';
import { ConfigConsumerProps } from '../config-provider';
import Base from '../base';
// import raf from '../_util/raf';
export const MenuMode = PropTypes.oneOf([
@ -41,26 +41,27 @@ export const menuProps = {
const Menu = {
name: 'AMenu',
inheritAttrs: false,
props: menuProps,
Divider: { ...Divider, name: 'AMenuDivider' },
Item: { ...Item, name: 'AMenuItem' },
SubMenu: { ...SubMenu, name: 'ASubMenu' },
ItemGroup: { ...ItemGroup, name: 'AMenuItemGroup' },
provide() {
return {
getInlineCollapsed: this.getInlineCollapsed,
menuPropsContext: this.$props,
};
},
mixins: [BaseMixin],
inject: {
layoutSiderContext: { default: () => ({}) },
configProvider: { default: () => ConfigConsumerProps },
created() {
provide('getInlineCollapsed', this.getInlineCollapsed);
provide('menuPropsContext', this.$props);
},
model: {
prop: 'selectedKeys',
event: 'selectChange',
setup() {
return {
configProvider: inject('configProvider', ConfigConsumerProps),
layoutSiderContext: inject('layoutSiderContext', {}),
};
},
// model: {
// prop: 'selectedKeys',
// event: 'selectChange',
// },
updated() {
this.propsUpdating = false;
},
@ -165,10 +166,12 @@ const Menu = {
},
handleSelect(info) {
this.$emit('select', info);
this.$emit('update:selectedKeys', info.selectedKeys);
this.$emit('selectChange', info.selectedKeys);
},
handleDeselect(info) {
this.$emit('deselect', info);
this.$emit('update:selectedKeys', info.selectedKeys);
this.$emit('selectChange', info.selectedKeys);
},
handleOpenChange(openKeys) {
@ -203,7 +206,7 @@ const Menu = {
if (menuMode === 'horizontal') {
menuOpenAnimation = 'slide-up';
} else if (menuMode === 'inline') {
menuOpenAnimation = { on: animation };
menuOpenAnimation = animation;
} else {
// When mode switch from inline
// submenu should hide without animation
@ -219,7 +222,7 @@ const Menu = {
},
},
render() {
const { layoutSiderContext, $slots } = this;
const { layoutSiderContext } = this;
const { collapsedWidth } = layoutSiderContext;
const { getPopupContainer: getContextPopupContainer } = this.configProvider;
const props = getOptionProps(this);
@ -235,37 +238,32 @@ const Menu = {
};
const menuProps = {
props: {
...omit(props, ['inlineCollapsed']),
getPopupContainer: getPopupContainer || getContextPopupContainer,
openKeys: this.sOpenKeys,
mode: menuMode,
prefixCls,
},
on: {
...getListeners(this),
select: this.handleSelect,
deselect: this.handleDeselect,
openChange: this.handleOpenChange,
mouseenter: this.handleMouseEnter,
},
nativeOn: {
transitionend: this.handleTransitionEnd,
},
...omit(props, ['inlineCollapsed']),
getPopupContainer: getPopupContainer || getContextPopupContainer,
openKeys: this.sOpenKeys,
mode: menuMode,
prefixCls,
...this.$attrs,
onSelect: this.handleSelect,
onDeselect: this.handleDeselect,
onOpenChange: this.handleOpenChange,
onMouseenter: this.handleMouseEnter,
onTransitionend: this.handleTransitionEnd,
children: getSlot(this),
};
if (!hasProp(this, 'selectedKeys')) {
delete menuProps.props.selectedKeys;
delete menuProps.selectedKeys;
}
if (menuMode !== 'inline') {
// closing vertical popup submenu after click it
menuProps.on.click = this.handleClick;
menuProps.props.openTransitionName = menuOpenAnimation;
menuProps.onClick = this.handleClick;
menuProps.openTransitionName = menuOpenAnimation;
} else {
menuProps.on.click = e => {
menuProps.onClick = e => {
this.$emit('click', e);
};
menuProps.props.openAnimation = menuOpenAnimation;
menuProps.openAnimation = menuOpenAnimation;
}
// https://github.com/ant-design/ant-design/issues/8587
@ -273,24 +271,19 @@ const Menu = {
this.getInlineCollapsed() &&
(collapsedWidth === 0 || collapsedWidth === '0' || collapsedWidth === '0px');
if (hideMenu) {
menuProps.props.openKeys = [];
menuProps.openKeys = [];
}
return (
<VcMenu {...menuProps} class={menuClassName}>
{$slots.default}
</VcMenu>
);
return <VcMenu {...menuProps} class={menuClassName} />;
},
};
/* istanbul ignore next */
Menu.install = function(Vue) {
Vue.use(Base);
Vue.component(Menu.name, Menu);
Vue.component(Menu.Item.name, Menu.Item);
Vue.component(Menu.SubMenu.name, Menu.SubMenu);
Vue.component(Menu.Divider.name, Menu.Divider);
Vue.component(Menu.ItemGroup.name, Menu.ItemGroup);
Menu.install = function(app) {
app.component(Menu.name, Menu);
app.component(Menu.Item.name, Menu.Item);
app.component(Menu.SubMenu.name, Menu.SubMenu);
app.component(Menu.Divider.name, Menu.Divider);
app.component(Menu.ItemGroup.name, Menu.ItemGroup);
};
export default Menu;

@ -4,7 +4,7 @@ import SubMenu from './SubMenu';
import BaseMixin from '../_util/BaseMixin';
import { getWidth, setStyle, menuAllProps } from './util';
import { cloneElement } from '../_util/vnode';
import { getClass, getPropsData, getEvents, getListeners } from '../_util/props-util';
import { getPropsData, getSlot, getAllProps } from '../_util/props-util';
const canUseDOM = !!(
typeof window !== 'undefined' &&
@ -110,9 +110,8 @@ const DOMWrap = {
}
// put all the overflowed item inside a submenu
// with a title of overflow indicator ('...')
const copy = this.$slots.default[0];
const { title, ...rest } = getPropsData(copy); // eslint-disable-line no-unused-vars
const events = getEvents(copy);
const copy = getSlot(this)[0];
const { title, ...rest } = getAllProps(copy); // eslint-disable-line no-unused-vars
let style = {};
let key = `${keyPrefix}-overflowed-indicator`;
let eventKey = `${keyPrefix}-overflowed-indicator`;
@ -133,29 +132,20 @@ const DOMWrap = {
const popupClassName = theme ? `${prefixCls}-${theme}` : '';
const props = {};
const on = {};
menuAllProps.props.forEach(k => {
menuAllProps.forEach(k => {
if (rest[k] !== undefined) {
props[k] = rest[k];
}
});
menuAllProps.on.forEach(k => {
if (events[k] !== undefined) {
on[k] = events[k];
}
});
const subMenuProps = {
props: {
title: overflowedIndicator,
popupClassName,
...props,
eventKey,
disabled: false,
},
title: overflowedIndicator,
popupClassName,
...props,
eventKey,
disabled: false,
class: `${prefixCls}-overflowed-submenu`,
key,
style,
on,
};
return <SubMenu {...subMenuProps}>{overflowedItems}</SubMenu>;
@ -245,7 +235,7 @@ const DOMWrap = {
renderChildren(children) {
// need to take care of overflowed items in horizontal mode
const { lastVisibleIndex } = this.$data;
const className = getClass(this);
const className = this.$attrs.class || '';
return (children || []).reduce((acc, childNode, index) => {
let item = childNode;
const eventKey = getPropsData(childNode).eventKey;
@ -258,7 +248,7 @@ const DOMWrap = {
// eventKey openkeys
{
style: { display: 'none' },
props: { eventKey: `${eventKey}-hidden` },
eventKey: `${eventKey}-hidden`,
class: MENUITEM_OVERFLOWED_CLASSNAME,
},
);
@ -271,7 +261,7 @@ const DOMWrap = {
// we have to overwrite with the correct key explicitly
{
key: getPropsData(c).eventKey,
props: { mode: 'vertical-left' },
mode: 'vertical-left',
},
);
});
@ -295,10 +285,8 @@ const DOMWrap = {
render() {
const Tag = this.$props.tag;
const tagProps = {
on: getListeners(this),
};
return <Tag {...tagProps}>{this.renderChildren(this.$slots.default)}</Tag>;
return <Tag>{this.renderChildren(getSlot(this))}</Tag>;
},
};
@ -311,6 +299,7 @@ DOMWrap.props = {
visible: PropTypes.bool,
hiddenClassName: PropTypes.string,
tag: PropTypes.string.def('div'),
children: PropTypes.any,
};
export default DOMWrap;

@ -2,16 +2,12 @@ import PropTypes from '../_util/vue-types';
import { Provider, create } from '../_util/store';
import { default as SubPopupMenu, getActiveKey } from './SubPopupMenu';
import BaseMixin from '../_util/BaseMixin';
import hasProp, {
getOptionProps,
getComponentFromProp,
filterEmpty,
getListeners,
} from '../_util/props-util';
import hasProp, { getOptionProps, getComponent, filterEmpty } from '../_util/props-util';
import commonPropsType from './commonPropsType';
const Menu = {
name: 'Menu',
inheritAttrs: false,
props: {
...commonPropsType,
selectable: PropTypes.bool.def(true),
@ -33,7 +29,7 @@ const Menu = {
selectedKeys,
openKeys,
activeKey: {
'0-menu-': getActiveKey({ ...props, children: this.$slots.default || [] }, props.activeKey),
'0-menu-': getActiveKey({ ...props, children: props.children || [] }, props.activeKey),
},
});
@ -158,27 +154,20 @@ const Menu = {
},
render() {
const props = getOptionProps(this);
const props = { ...getOptionProps(this), ...this.$attrs };
props.class += ` ${props.prefixCls}-root`;
const subPopupMenuProps = {
props: {
...props,
itemIcon: getComponentFromProp(this, 'itemIcon', props),
expandIcon: getComponentFromProp(this, 'expandIcon', props),
overflowedIndicator: getComponentFromProp(this, 'overflowedIndicator', props) || (
<span>···</span>
),
openTransitionName: this.getOpenTransitionName(),
parentMenu: this,
children: filterEmpty(this.$slots.default || []),
},
class: `${props.prefixCls}-root`,
on: {
...getListeners(this),
click: this.onClick,
openChange: this.onOpenChange,
deselect: this.onDeselect,
select: this.onSelect,
},
...props,
itemIcon: getComponent(this, 'itemIcon', props),
expandIcon: getComponent(this, 'expandIcon', props),
overflowedIndicator: getComponent(this, 'overflowedIndicator', props) || <span>···</span>,
openTransitionName: this.getOpenTransitionName(),
parentMenu: this,
children: filterEmpty(props.children),
onClick: this.onClick,
onOpenChange: this.onOpenChange,
onDeselect: this.onDeselect,
onSelect: this.onSelect,
ref: 'innerMenu',
};
return (

@ -4,7 +4,7 @@ import BaseMixin from '../_util/BaseMixin';
import scrollIntoView from 'dom-scroll-into-view';
import { connect } from '../_util/store';
import { noop, menuAllProps } from './util';
import { getComponentFromProp, getListeners } from '../_util/props-util';
import { getComponent, getSlot } from '../_util/props-util';
const props = {
attribute: PropTypes.object,
@ -36,6 +36,7 @@ const props = {
};
const MenuItem = {
name: 'MenuItem',
inheritAttrs: false,
props,
mixins: [BaseMixin],
isMenuItem: true,
@ -141,8 +142,10 @@ const MenuItem = {
},
render() {
const props = { ...this.$props };
const props = { ...this.$props, ...this.$attrs };
const className = {
[props.class]: props.class,
[this.getPrefixCls()]: true,
[this.getActiveClassName()]: !props.disabled && props.active,
[this.getSelectedClassName()]: props.isSelected,
@ -171,32 +174,27 @@ const MenuItem = {
}
// In case that onClick/onMouseLeave/onMouseEnter is passed down from owner
const mouseEvent = {
click: props.disabled ? noop : this.onClick,
mouseleave: props.disabled ? noop : this.onMouseLeave,
mouseenter: props.disabled ? noop : this.onMouseEnter,
onClick: props.disabled ? noop : this.onClick,
onMouseleave: props.disabled ? noop : this.onMouseLeave,
onMouseenter: props.disabled ? noop : this.onMouseEnter,
};
const style = {};
const style = { ...(props.style || {}) };
if (props.mode === 'inline') {
style.paddingLeft = `${props.inlineIndent * props.level}px`;
}
const listeners = { ...getListeners(this) };
menuAllProps.props.forEach(key => delete props[key]);
menuAllProps.on.forEach(key => delete listeners[key]);
[...menuAllProps, 'children', 'slots', '__propsSymbol__', 'componentWillReceiveProps'].forEach(
key => delete props[key],
);
const liProps = {
attrs: {
...props,
...attrs,
},
on: {
...listeners,
...mouseEvent,
},
...props,
...attrs,
...mouseEvent,
};
return (
<li {...liProps} style={style} class={className}>
{this.$slots.default}
{getComponentFromProp(this, 'itemIcon', props)}
{getSlot(this)}
{getComponent(this, 'itemIcon', props)}
</li>
);
},

@ -1,11 +1,11 @@
import PropTypes from '../_util/vue-types';
import { getComponentFromProp, getListeners } from '../_util/props-util';
import { getComponent, getSlot } from '../_util/props-util';
// import { menuAllProps } from './util'
const MenuItemGroup = {
name: 'MenuItemGroup',
inheritAttrs: false,
props: {
renderMenuItem: PropTypes.func,
index: PropTypes.number,
@ -23,22 +23,19 @@ const MenuItemGroup = {
},
},
render() {
const props = { ...this.$props };
const { rootPrefixCls, title } = props;
const props = { ...this.$props, ...this.$attrs };
const { class: cls = '', rootPrefixCls, title } = props;
const titleClassName = `${rootPrefixCls}-item-group-title`;
const listClassName = `${rootPrefixCls}-item-group-list`;
// menuAllProps.props.forEach(key => delete props[key])
const listeners = { ...getListeners(this) };
delete listeners.click;
delete props.onClick;
const children = getSlot(this);
return (
<li {...{ on: listeners, class: `${rootPrefixCls}-item-group` }}>
<li {...props} class={`${cls} ${rootPrefixCls}-item-group`}>
<div class={titleClassName} title={typeof title === 'string' ? title : undefined}>
{getComponentFromProp(this, 'title')}
{getComponent(this, 'title')}
</div>
<ul class={listClassName}>
{this.$slots.default && this.$slots.default.map(this.renderInnerMenuItem)}
</ul>
<ul class={listClassName}>{children && children.map(this.renderInnerMenuItem)}</ul>
</li>
);
},

@ -6,7 +6,7 @@ import { connect } from '../_util/store';
import SubPopupMenu from './SubPopupMenu';
import placements from './placements';
import BaseMixin from '../_util/BaseMixin';
import { getComponentFromProp, filterEmpty, getListeners } from '../_util/props-util';
import { getComponent, filterEmpty, getSlot, splitAttrs } from '../_util/props-util';
import { requestAnimationTimeout, cancelAnimationTimeout } from '../_util/requestAnimationTimeout';
import { noop, loopMenuItemRecursively, getMenuIdFromSubMenuEventKey } from './util';
import getTransitionProps from '../_util/getTransitionProps';
@ -33,6 +33,7 @@ const updateDefaultActiveFirst = (store, eventKey, defaultActiveFirst) => {
const SubMenu = {
name: 'SubMenu',
inheritAttrs: false,
props: {
parentMenu: PropTypes.object,
title: PropTypes.any,
@ -86,6 +87,9 @@ const SubMenu = {
}
updateDefaultActiveFirst(store, eventKey, value);
this.internalMenuId = undefined;
this.haveRendered = undefined;
this.haveOpened = undefined;
return {
// defaultActiveFirst: false,
};
@ -312,7 +316,7 @@ const SubMenu = {
isChildrenSelected() {
const ret = { find: false };
loopMenuItemRecursively(this.$slots.default, this.$props.selectedKeys, ret);
loopMenuItemRecursively(getSlot(this), this.$props.selectedKeys, ret);
return ret.find;
},
// isOpen () {
@ -334,49 +338,44 @@ const SubMenu = {
},
renderChildren(children) {
const props = this.$props;
const { select, deselect, openChange } = getListeners(this);
const props = { ...this.$props, ...this.$attrs };
const subPopupMenuProps = {
props: {
mode: props.mode === 'horizontal' ? 'vertical' : props.mode,
visible: props.isOpen,
level: props.level + 1,
inlineIndent: props.inlineIndent,
focusable: false,
selectedKeys: props.selectedKeys,
eventKey: `${props.eventKey}-menu-`,
openKeys: props.openKeys,
openTransitionName: props.openTransitionName,
openAnimation: props.openAnimation,
subMenuOpenDelay: props.subMenuOpenDelay,
parentMenu: this,
subMenuCloseDelay: props.subMenuCloseDelay,
forceSubMenuRender: props.forceSubMenuRender,
triggerSubMenuAction: props.triggerSubMenuAction,
builtinPlacements: props.builtinPlacements,
defaultActiveFirst: props.store.getState().defaultActiveFirst[
getMenuIdFromSubMenuEventKey(props.eventKey)
],
multiple: props.multiple,
prefixCls: props.rootPrefixCls,
manualRef: this.saveMenuInstance,
itemIcon: getComponentFromProp(this, 'itemIcon'),
expandIcon: getComponentFromProp(this, 'expandIcon'),
children,
},
on: {
click: this.onSubMenuClick,
select,
deselect,
openChange,
},
mode: props.mode === 'horizontal' ? 'vertical' : props.mode,
visible: props.isOpen,
level: props.level + 1,
inlineIndent: props.inlineIndent,
focusable: false,
selectedKeys: props.selectedKeys,
eventKey: `${props.eventKey}-menu-`,
openKeys: props.openKeys,
openTransitionName: props.openTransitionName,
openAnimation: props.openAnimation,
subMenuOpenDelay: props.subMenuOpenDelay,
parentMenu: this,
subMenuCloseDelay: props.subMenuCloseDelay,
forceSubMenuRender: props.forceSubMenuRender,
triggerSubMenuAction: props.triggerSubMenuAction,
builtinPlacements: props.builtinPlacements,
defaultActiveFirst: props.store.getState().defaultActiveFirst[
getMenuIdFromSubMenuEventKey(props.eventKey)
],
multiple: props.multiple,
prefixCls: props.rootPrefixCls,
manualRef: this.saveMenuInstance,
itemIcon: getComponent(this, 'itemIcon'),
expandIcon: getComponent(this, 'expandIcon'),
children,
click: this.onSubMenuClick,
onSelect: props.onSelect || noop,
onDeselect: props.onDeselect || noop,
onOpenChange: props.onOpenChange || noop,
id: this.internalMenuId,
};
const baseProps = subPopupMenuProps.props;
const haveRendered = this.haveRendered;
this.haveRendered = true;
this.haveOpened = this.haveOpened || baseProps.visible || baseProps.forceSubMenuRender;
this.haveOpened =
this.haveOpened || subPopupMenuProps.visible || subPopupMenuProps.forceSubMenuRender;
// never rendered not planning to, don't render
if (!this.haveOpened) {
return <div />;
@ -385,28 +384,24 @@ const SubMenu = {
// don't show transition on first rendering (no animation for opened menu)
// show appear transition if it's not visible (not sure why)
// show appear transition if it's not inline mode
const transitionAppear = haveRendered || !baseProps.visible || !baseProps.mode === 'inline';
subPopupMenuProps.class = ` ${baseProps.prefixCls}-sub`;
let animProps = { appear: transitionAppear, css: false };
let transitionProps = {
props: animProps,
on: {},
};
if (baseProps.openTransitionName) {
transitionProps = getTransitionProps(baseProps.openTransitionName, {
const transitionAppear =
haveRendered || !subPopupMenuProps.visible || !subPopupMenuProps.mode === 'inline';
subPopupMenuProps.class = ` ${subPopupMenuProps.prefixCls}-sub`;
let transitionProps = { appear: transitionAppear, css: false };
if (subPopupMenuProps.openTransitionName) {
transitionProps = getTransitionProps(subPopupMenuProps.openTransitionName, {
appear: transitionAppear,
});
} else if (typeof baseProps.openAnimation === 'object') {
animProps = { ...animProps, ...(baseProps.openAnimation.props || {}) };
} else if (typeof subPopupMenuProps.openAnimation === 'object') {
transitionProps = { ...transitionProps, ...(subPopupMenuProps.openAnimation || {}) };
if (!transitionAppear) {
animProps.appear = false;
transitionProps.appear = false;
}
} else if (typeof baseProps.openAnimation === 'string') {
transitionProps = getTransitionProps(baseProps.openAnimation, { appear: transitionAppear });
}
if (typeof baseProps.openAnimation === 'object' && baseProps.openAnimation.on) {
transitionProps.on = baseProps.openAnimation.on;
} else if (typeof subPopupMenuProps.openAnimation === 'string') {
transitionProps = getTransitionProps(subPopupMenuProps.openAnimation, {
appear: transitionAppear,
});
}
return (
<transition {...transitionProps}>
@ -417,7 +412,8 @@ const SubMenu = {
},
render() {
const props = this.$props;
const props = { ...this.$props, ...this.$attrs };
const { onEvents } = splitAttrs(props);
const { rootPrefixCls, parentMenu } = this;
const isOpen = props.isOpen;
const prefixCls = this.getPrefixCls();
@ -425,6 +421,7 @@ const SubMenu = {
const className = {
[prefixCls]: true,
[`${prefixCls}-${props.mode}`]: true,
[props.className]: !!props.className,
[this.getOpenClassName()]: isOpen,
[this.getActiveClassName()]: props.active || (isOpen && !isInlineMode),
[this.getDisabledClassName()]: props.disabled,
@ -444,17 +441,17 @@ const SubMenu = {
let titleMouseEvents = {};
if (!props.disabled) {
mouseEvents = {
mouseleave: this.onMouseLeave,
mouseenter: this.onMouseEnter,
onMouseleave: this.onMouseLeave,
onMouseenter: this.onMouseEnter,
};
// only works in title, not outer li
titleClickEvents = {
click: this.onTitleClick,
onClick: this.onTitleClick,
};
titleMouseEvents = {
mouseenter: this.onTitleMouseEnter,
mouseleave: this.onTitleMouseLeave,
onMouseenter: this.onTitleMouseEnter,
onMouseleave: this.onTitleMouseLeave,
};
}
@ -472,16 +469,12 @@ const SubMenu = {
};
}
const titleProps = {
attrs: {
'aria-expanded': isOpen,
...ariaOwns,
'aria-haspopup': 'true',
title: typeof props.title === 'string' ? props.title : undefined,
},
on: {
...titleMouseEvents,
...titleClickEvents,
},
'aria-expanded': isOpen,
...ariaOwns,
'aria-haspopup': 'true',
title: typeof props.title === 'string' ? props.title : undefined,
...titleMouseEvents,
...titleClickEvents,
style,
class: `${prefixCls}-title`,
ref: 'subMenuTitle',
@ -489,15 +482,15 @@ const SubMenu = {
// expand custom icon should NOT be displayed in menu with horizontal mode.
let icon = null;
if (props.mode !== 'horizontal') {
icon = getComponentFromProp(this, 'expandIcon', props);
icon = getComponent(this, 'expandIcon', props);
}
const title = (
<div {...titleProps}>
{getComponentFromProp(this, 'title')}
{getComponent(this, 'title')}
{icon || <i class={`${prefixCls}-arrow`} />}
</div>
);
const children = this.renderChildren(filterEmpty(this.$slots.default));
const children = this.renderChildren(filterEmpty(getSlot(this)));
const getPopupContainer = this.parentMenu.isRootMenu
? this.parentMenu.getPopupContainer
@ -506,7 +499,8 @@ const SubMenu = {
const popupAlign = props.popupOffset ? { offset: props.popupOffset } : {};
const popupClassName = props.mode === 'inline' ? '' : props.popupClassName;
const liProps = {
on: { ...omit(getListeners(this), ['click']), ...mouseEvents },
...omit(onEvents, ['onClick']),
...mouseEvents,
class: className,
};
@ -533,8 +527,8 @@ const SubMenu = {
forceRender={props.forceSubMenuRender}
// popupTransitionName='rc-menu-open-slide-up'
// popupAnimation={transitionProps}
popup={children}
>
<template slot="popup">{children}</template>
{title}
</Trigger>
)}

@ -1,4 +1,4 @@
import omit from 'omit.js';
import { Comment } from 'vue';
import PropTypes from '../_util/vue-types';
import { connect } from '../_util/store';
import BaseMixin from '../_util/BaseMixin';
@ -7,14 +7,7 @@ import classNames from 'classnames';
import { getKeyFromChildrenIndex, loopMenuItem, noop, isMobileDevice } from './util';
import DOMWrap from './DOMWrap';
import { cloneElement } from '../_util/vnode';
import {
initDefaultProps,
getOptionProps,
getPropsData,
getEvents,
getComponentFromProp,
getListeners,
} from '../_util/props-util';
import { initDefaultProps, getOptionProps, getComponent, splitAttrs } from '../_util/props-util';
function allDisabled(arr) {
if (!arr.length) {
@ -77,6 +70,7 @@ export function getActiveKey(props, originalActiveKey) {
const SubPopupMenu = {
name: 'SubPopupMenu',
inheritAttrs: false,
props: initDefaultProps(
{
// onSelect: PropTypes.func,
@ -271,85 +265,57 @@ const SubPopupMenu = {
return null;
},
getIcon(instance, name) {
if (instance.$createElement) {
const temp = instance[name];
if (temp !== undefined) {
return temp;
}
return instance.$slots[name] || instance.$scopedSlots[name];
} else {
const temp = getPropsData(instance)[name];
if (temp !== undefined) {
return temp;
}
const slotsProp = [];
const componentOptions = instance.componentOptions || {};
(componentOptions.children || []).forEach(child => {
if (child.data && child.data.slot === name) {
if (child.tag === 'template') {
slotsProp.push(child.children);
} else {
slotsProp.push(child);
}
}
});
return slotsProp.length ? slotsProp : undefined;
}
return getComponent(instance, name);
},
renderCommonMenuItem(child, i, extraProps) {
if (child.tag === undefined) {
if (child.type === Comment) {
return child;
}
const state = this.$props.store.getState();
const props = this.$props;
const key = getKeyFromChildrenIndex(child, props.eventKey, i);
const childProps = child.componentOptions.propsData || {};
const childProps = { ...getOptionProps(child), ...child.props }; // child.props
const isActive = key === state.activeKey[getEventKey(this.$props)];
if (!childProps.disabled) {
// manualRef使keyrefthis.instanceArray
this.instanceArrayKeyIndexMap[key] = Object.keys(this.instanceArrayKeyIndexMap).length;
}
const childListeners = getEvents(child);
const newChildProps = {
props: {
mode: childProps.mode || props.mode,
level: props.level,
inlineIndent: props.inlineIndent,
renderMenuItem: this.renderMenuItem,
rootPrefixCls: props.prefixCls,
index: i,
parentMenu: props.parentMenu,
// customized ref function, need to be invoked manually in child's componentDidMount
manualRef: childProps.disabled ? noop : saveRef.bind(this, key),
eventKey: key,
active: !childProps.disabled && isActive,
multiple: props.multiple,
openTransitionName: this.getOpenTransitionName(),
openAnimation: props.openAnimation,
subMenuOpenDelay: props.subMenuOpenDelay,
subMenuCloseDelay: props.subMenuCloseDelay,
forceSubMenuRender: props.forceSubMenuRender,
builtinPlacements: props.builtinPlacements,
itemIcon: this.getIcon(child, 'itemIcon') || this.getIcon(this, 'itemIcon'),
expandIcon: this.getIcon(child, 'expandIcon') || this.getIcon(this, 'expandIcon'),
...extraProps,
},
on: {
click: e => {
(childListeners.click || noop)(e);
this.onClick(e);
},
itemHover: this.onItemHover,
openChange: this.onOpenChange,
deselect: this.onDeselect,
// destroy: this.onDestroy,
select: this.onSelect,
mode: childProps.mode || props.mode,
level: props.level,
inlineIndent: props.inlineIndent,
renderMenuItem: this.renderMenuItem,
rootPrefixCls: props.prefixCls,
index: i,
parentMenu: props.parentMenu,
// customized ref function, need to be invoked manually in child's componentDidMount
manualRef: childProps.disabled ? noop : saveRef.bind(this, key),
eventKey: key,
active: !childProps.disabled && isActive,
multiple: props.multiple,
openTransitionName: this.getOpenTransitionName(),
openAnimation: props.openAnimation,
subMenuOpenDelay: props.subMenuOpenDelay,
subMenuCloseDelay: props.subMenuCloseDelay,
forceSubMenuRender: props.forceSubMenuRender,
builtinPlacements: props.builtinPlacements,
itemIcon: this.getIcon(child, 'itemIcon') || this.getIcon(this, 'itemIcon'),
expandIcon: this.getIcon(child, 'expandIcon') || this.getIcon(this, 'expandIcon'),
...extraProps,
onClick: e => {
(childProps.onClick || noop)(e);
this.onClick(e);
},
onItemHover: this.onItemHover,
onOpenChange: this.onOpenChange,
onDeselect: this.onDeselect,
// destroy: this.onDestroy,
onSelect: this.onSelect,
};
// ref: https://github.com/ant-design/ant-design/issues/13943
if (props.mode === 'inline' || isMobileDevice()) {
newChildProps.props.triggerSubMenuAction = 'click';
newChildProps.triggerSubMenuAction = 'click';
}
return cloneElement(child, newChildProps);
},
@ -370,35 +336,34 @@ const SubPopupMenu = {
},
},
render() {
const { ...props } = this.$props;
const props = { ...this.$props };
const { onEvents } = splitAttrs(this.$attrs);
const { eventKey, prefixCls, visible, level, mode, theme } = props;
this.instanceArray = [];
this.instanceArrayKeyIndexMap = {};
const className = classNames(props.prefixCls, `${props.prefixCls}-${props.mode}`);
const className = classNames(props.class, props.prefixCls, `${props.prefixCls}-${props.mode}`);
// Otherwise, the propagated click event will trigger another onClick
delete onEvents.onClick;
const domWrapProps = {
props: {
tag: 'ul',
// hiddenClassName: `${prefixCls}-hidden`,
visible,
prefixCls,
level,
mode,
theme,
overflowedIndicator: getComponentFromProp(this, 'overflowedIndicator'),
},
attrs: {
role: props.role || 'menu',
},
...props,
tag: 'ul',
// hiddenClassName: `${prefixCls}-hidden`,
visible,
prefixCls,
level,
mode,
theme,
overflowedIndicator: getComponent(this, 'overflowedIndicator'),
role: props.role || 'menu',
class: className,
// Otherwise, the propagated click event will trigger another onClick
on: omit(getListeners(this), ['click']),
...onEvents,
};
// if (props.id) {
// domProps.id = props.id
// }
if (props.focusable) {
domWrapProps.attrs.tabIndex = '0';
domWrapProps.on.keydown = this.onKeyDown;
domWrapProps.tabIndex = '0';
domWrapProps.onKeydown = this.onKeyDown;
}
return (
// ESLint is not smart enough to know that the type of `children` was checked.

@ -37,4 +37,5 @@ export default {
itemIcon: PropTypes.any,
expandIcon: PropTypes.any,
overflowedIndicator: PropTypes.any,
children: PropTypes.any,
};

@ -51,68 +51,65 @@ export function loopMenuItemRecursively(children, keys, ret) {
});
}
export const menuAllProps = {
props: [
'defaultSelectedKeys',
'selectedKeys',
'defaultOpenKeys',
'openKeys',
'mode',
'getPopupContainer',
'openTransitionName',
'openAnimation',
'subMenuOpenDelay',
'subMenuCloseDelay',
'forceSubMenuRender',
'triggerSubMenuAction',
'level',
'selectable',
'multiple',
'visible',
'focusable',
'defaultActiveFirst',
'prefixCls',
'inlineIndent',
'parentMenu',
'title',
'rootPrefixCls',
'eventKey',
'active',
'popupAlign',
'popupOffset',
'isOpen',
'renderMenuItem',
'manualRef',
'subMenuKey',
'disabled',
'index',
'isSelected',
'store',
'activeKey',
'builtinPlacements',
'overflowedIndicator',
export const menuAllProps = [
'defaultSelectedKeys',
'selectedKeys',
'defaultOpenKeys',
'openKeys',
'mode',
'getPopupContainer',
'openTransitionName',
'openAnimation',
'subMenuOpenDelay',
'subMenuCloseDelay',
'forceSubMenuRender',
'triggerSubMenuAction',
'level',
'selectable',
'multiple',
'visible',
'focusable',
'defaultActiveFirst',
'prefixCls',
'inlineIndent',
'parentMenu',
'title',
'rootPrefixCls',
'eventKey',
'active',
'popupAlign',
'popupOffset',
'isOpen',
'renderMenuItem',
'manualRef',
'subMenuKey',
'disabled',
'index',
'isSelected',
'store',
'activeKey',
'builtinPlacements',
'overflowedIndicator',
// the following keys found need to be removed from test regression
'attribute',
'value',
'popupClassName',
'inlineCollapsed',
'menu',
'theme',
'itemIcon',
'expandIcon',
],
on: [
'select',
'deselect',
'destroy',
'openChange',
'itemHover',
'titleMouseenter',
'titleMouseleave',
'titleClick',
],
};
// the following keys found need to be removed from test regression
'attribute',
'value',
'popupClassName',
'inlineCollapsed',
'menu',
'theme',
'itemIcon',
'expandIcon',
'onSelect',
'onDeselect',
'onDestroy',
'onOpenChange',
'onItemHover',
'onTitleMouseenter',
'onTitleMouseleave',
'onTitleClick',
];
// ref: https://github.com/ant-design/ant-design/issues/14007
// ref: https://bugs.chromium.org/p/chromium/issues/detail?id=360889

@ -29,6 +29,7 @@ import Popover from 'ant-design-vue/popover';
import notification from 'ant-design-vue/notification';
import message from 'ant-design-vue/message';
import Modal from 'ant-design-vue/modal';
import Menu from 'ant-design-vue/menu';
import 'ant-design-vue/style.js';
const app = createApp(App);
@ -61,4 +62,5 @@ app
.use(Popconfirm)
.use(Popover)
.use(Modal)
.use(Menu)
.mount('#app');

Loading…
Cancel
Save