fix: menu parentMenu error

pull/2682/head
tanjinzhou 2020-07-30 15:41:54 +08:00
parent d6a7c041b5
commit 60541d7d1b
12 changed files with 78 additions and 63 deletions

@ -1 +1 @@
Subproject commit e7feb6f5127f054c5ddac633b733369e182ab6f2 Subproject commit a234ba5ccfe4ab14f1619ddc156aef5bf05a7f18

View File

@ -1,3 +1,8 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`DropdownButton should support href like Button 1`] = `<div class="ant-btn-group ant-dropdown-button"><a href="https://ant.design" class="ant-btn ant-btn-default"></a><button type="button" class="ant-btn ant-btn-default ant-dropdown-trigger"><span role="img" aria-label="ellipsis" class="anticon anticon-ellipsis"><svg viewBox="64 64 896 896" focusable="false" data-icon="ellipsis" width="1em" height="1em" fill="currentColor" aria-hidden="true" class=""><path d="M176 511a56 56 0 10112 0 56 56 0 10-112 0zm280 0a56 56 0 10112 0 56 56 0 10-112 0zm280 0a56 56 0 10112 0 56 56 0 10-112 0z"></path></svg></span></button></div>`; exports[`DropdownButton should support href like Button 1`] = `
<div class="ant-btn-group ant-dropdown-button"><a href="https://ant.design" class="ant-btn ant-btn-default">
<!----></a>
<!----><button class="ant-dropdown-trigger ant-btn ant-btn-default" type="button">
<!----><span class="anticon anticon-ellipsis" role="img" aria-label="ellipsis"><svg class="" data-icon="ellipsis" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896" focusable="false"><path d="M176 511a56 56 0 10112 0 56 56 0 10-112 0zm280 0a56 56 0 10112 0 56 56 0 10-112 0zm280 0a56 56 0 10112 0 56 56 0 10-112 0z"></path></svg></span></button></div>
`;

View File

@ -11,36 +11,30 @@ describe('DropdownButton', () => {
disabled: false, disabled: false,
trigger: ['hover'], trigger: ['hover'],
visible: true, visible: true,
onVisibleChange: () => {},
}; };
const wrapper = mount(Dropdown.Button, { const wrapper = mount(Dropdown.Button, {
props, props,
}); });
const dropdownProps = wrapper.find({ name: 'ADropdown' }).props(); const dropdownProps = wrapper.findComponent({ name: 'ADropdown' }).props();
Object.keys(props).forEach(key => { Object.keys(props).forEach(key => {
expect(dropdownProps[key]).toBe(props[key]); expect(dropdownProps[key]).toStrictEqual(props[key]);
}); });
}); });
it("don't pass visible to Dropdown if it's not exits", () => { it("don't pass visible to Dropdown if it's not exits", () => {
const wrapper = mount({ const wrapper = mount(Dropdown.Button, {
render() { props: {
return ( overlay: (
<Dropdown.Button
overlay={
<Menu> <Menu>
<Menu.Item>foo</Menu.Item> <Menu.Item>foo</Menu.Item>
</Menu> </Menu>
} ),
/>
);
}, },
}); });
const dropdownProps = wrapper.find({ name: 'ADropdown' }).props(); const dropdownProps = wrapper.findComponent({ name: 'ADropdown' }).props();
expect('visible' in dropdownProps).toBe(false); expect(dropdownProps.visible).toBe(undefined);
}); });
it('should support href like Button', () => { it('should support href like Button', () => {

View File

@ -1,5 +1,6 @@
import { provide, inject } from 'vue'; import { provide, inject } from 'vue';
import Button from '../button'; import Button from '../button';
import classNames from 'classnames';
import buttonTypes from '../button/buttonTypes'; import buttonTypes from '../button/buttonTypes';
import { ButtonGroupProps } from '../button/button-group'; import { ButtonGroupProps } from '../button/button-group';
import Dropdown from './dropdown'; import Dropdown from './dropdown';
@ -24,10 +25,14 @@ const DropdownButtonProps = {
placement: DropdownProps.placement.def('bottomRight'), placement: DropdownProps.placement.def('bottomRight'),
icon: PropTypes.any, icon: PropTypes.any,
title: PropTypes.string, title: PropTypes.string,
onClick: PropTypes.func,
onVisibleChange: PropTypes.func,
'onUpdate:visible': PropTypes.func,
}; };
export { DropdownButtonProps }; export { DropdownButtonProps };
export default { export default {
name: 'ADropdownButton', name: 'ADropdownButton',
inheritAttrs: false,
props: DropdownButtonProps, props: DropdownButtonProps,
setup() { setup() {
return { return {
@ -41,10 +46,10 @@ export default {
savePopupRef(ref) { savePopupRef(ref) {
this.popupRef = ref; this.popupRef = ref;
}, },
onClick(e) { handleClick(e) {
this.$emit('click', e); this.$emit('click', e);
}, },
onVisibleChange(val) { handleVisibleChange(val) {
this.$emit('update:visible', val); this.$emit('update:visible', val);
this.$emit('visibleChange', val); this.$emit('visibleChange', val);
}, },
@ -53,17 +58,21 @@ export default {
const { const {
type, type,
disabled, disabled,
onClick,
htmlType, htmlType,
class: className,
prefixCls: customizePrefixCls, prefixCls: customizePrefixCls,
overlay,
trigger, trigger,
align, align,
visible, visible,
onVisibleChange,
placement, placement,
getPopupContainer, getPopupContainer,
href, href,
title, title,
...restProps ...restProps
} = this.$props; } = { ...this.$props, ...this.$attrs };
const icon = getComponent(this, 'icon') || <EllipsisOutlined />; const icon = getComponent(this, 'icon') || <EllipsisOutlined />;
const { getPopupContainer: getContextPopupContainer } = this.configProvider; const { getPopupContainer: getContextPopupContainer } = this.configProvider;
const getPrefixCls = this.configProvider.getPrefixCls; const getPrefixCls = this.configProvider.getPrefixCls;
@ -74,7 +83,7 @@ export default {
trigger: disabled ? [] : trigger, trigger: disabled ? [] : trigger,
placement, placement,
getPopupContainer: getPopupContainer || getContextPopupContainer, getPopupContainer: getPopupContainer || getContextPopupContainer,
onVisibleChange: this.onVisibleChange, onVisibleChange: this.handleVisibleChange,
}; };
if (hasProp(this, 'visible')) { if (hasProp(this, 'visible')) {
dropdownProps.visible = visible; dropdownProps.visible = visible;
@ -82,7 +91,7 @@ export default {
const buttonGroupProps = { const buttonGroupProps = {
...restProps, ...restProps,
class: prefixCls, class: classNames(prefixCls, className),
}; };
return ( return (
@ -90,7 +99,7 @@ export default {
<Button <Button
type={type} type={type}
disabled={disabled} disabled={disabled}
onClick={this.onClick} onClick={this.handleClick}
htmlType={htmlType} htmlType={htmlType}
href={href} href={href}
title={title} title={title}

View File

@ -3,6 +3,7 @@ import RcDropdown from '../vc-dropdown/src/index';
import DropdownButton from './dropdown-button'; import DropdownButton from './dropdown-button';
import PropTypes from '../_util/vue-types'; import PropTypes from '../_util/vue-types';
import { cloneElement } from '../_util/vnode'; import { cloneElement } from '../_util/vnode';
import classNames from 'classnames';
import { import {
getOptionProps, getOptionProps,
getPropsData, getPropsData,
@ -17,12 +18,14 @@ import RightOutlined from '@ant-design/icons-vue/RightOutlined';
const DropdownProps = getDropdownProps(); const DropdownProps = getDropdownProps();
const Dropdown = { const Dropdown = {
name: 'ADropdown', name: 'ADropdown',
inheritAttrs: false,
props: { props: {
...DropdownProps, ...DropdownProps,
prefixCls: PropTypes.string, prefixCls: PropTypes.string,
mouseEnterDelay: PropTypes.number.def(0.15), mouseEnterDelay: PropTypes.number.def(0.15),
mouseLeaveDelay: PropTypes.number.def(0.1), mouseLeaveDelay: PropTypes.number.def(0.1),
placement: DropdownProps.placement.def('bottomLeft'), placement: DropdownProps.placement.def('bottomLeft'),
onVisibleChange: PropTypes.func,
}, },
setup() { setup() {
return { return {
@ -69,6 +72,10 @@ const Dropdown = {
: overlay; : overlay;
return fixedModeOverlay; return fixedModeOverlay;
}, },
handleVisibleChange(val) {
this.$emit('update:visible', val);
this.$emit('visibleChange', val);
},
}, },
render() { render() {
@ -78,14 +85,10 @@ const Dropdown = {
const getPrefixCls = this.configProvider.getPrefixCls; const getPrefixCls = this.configProvider.getPrefixCls;
const prefixCls = getPrefixCls('dropdown', customizePrefixCls); const prefixCls = getPrefixCls('dropdown', customizePrefixCls);
const dropdownTrigger = cloneElement( const dropdownTrigger = cloneElement(getSlot(this), {
getSlot(this), class: classNames(this.$attrs?.class, `${prefixCls}-trigger`),
{
class: `${prefixCls}-trigger`,
disabled, disabled,
}, });
false,
);
const triggerActions = disabled ? [] : trigger; const triggerActions = disabled ? [] : trigger;
let alignPoint; let alignPoint;
if (triggerActions && triggerActions.indexOf('contextmenu') !== -1) { if (triggerActions && triggerActions.indexOf('contextmenu') !== -1) {
@ -94,11 +97,13 @@ const Dropdown = {
const dropdownProps = { const dropdownProps = {
alignPoint, alignPoint,
...props, ...props,
...this.$attrs,
prefixCls, prefixCls,
getPopupContainer: getPopupContainer || getContextPopupContainer, getPopupContainer: getPopupContainer || getContextPopupContainer,
transitionName: this.getTransitionName(), transitionName: this.getTransitionName(),
trigger: triggerActions, trigger: triggerActions,
overlay: this.renderOverlay(prefixCls), overlay: this.renderOverlay(prefixCls),
onVisibleChange: this.handleVisibleChange,
}; };
return <RcDropdown {...dropdownProps}>{dropdownTrigger}</RcDropdown>; return <RcDropdown {...dropdownProps}>{dropdownTrigger}</RcDropdown>;
}, },

View File

@ -1,5 +1,6 @@
export default { export default {
name: 'MenuDivider', name: 'MenuDivider',
inheritAttrs: false,
props: { props: {
disabled: { disabled: {
type: Boolean, type: Boolean,
@ -9,6 +10,7 @@ export default {
}, },
render() { render() {
const { rootPrefixCls } = this.$props; const { rootPrefixCls } = this.$props;
return <li class={`${rootPrefixCls}-item-divider`} />; const { class: className = '', style } = this.$attrs;
return <li class={`${className} ${rootPrefixCls}-item-divider`} style={style} />;
}, },
}; };

View File

@ -4,6 +4,7 @@ import { default as SubPopupMenu, getActiveKey } from './SubPopupMenu';
import BaseMixin from '../_util/BaseMixin'; import BaseMixin from '../_util/BaseMixin';
import hasProp, { getOptionProps, getComponent, filterEmpty } from '../_util/props-util'; import hasProp, { getOptionProps, getComponent, filterEmpty } from '../_util/props-util';
import commonPropsType from './commonPropsType'; import commonPropsType from './commonPropsType';
import { provide } from 'vue';
const Menu = { const Menu = {
name: 'Menu', name: 'Menu',
@ -35,6 +36,9 @@ const Menu = {
// this.isRootMenu = true // props // this.isRootMenu = true // props
return {}; return {};
}, },
created() {
provide('parentMenu', this);
},
mounted() { mounted() {
this.updateMiniStore(); this.updateMiniStore();
}, },
@ -166,7 +170,6 @@ const Menu = {
expandIcon: getComponent(this, 'expandIcon', props), expandIcon: getComponent(this, 'expandIcon', props),
overflowedIndicator: getComponent(this, 'overflowedIndicator', props) || <span>···</span>, overflowedIndicator: getComponent(this, 'overflowedIndicator', props) || <span>···</span>,
openTransitionName: this.getOpenTransitionName(), openTransitionName: this.getOpenTransitionName(),
parentMenu: this,
children: filterEmpty(props.children), children: filterEmpty(props.children),
onClick: this.onClick, onClick: this.onClick,
onOpenChange: this.onOpenChange, onOpenChange: this.onOpenChange,

View File

@ -5,6 +5,7 @@ import scrollIntoView from 'dom-scroll-into-view';
import { connect } from '../_util/store'; import { connect } from '../_util/store';
import { noop, menuAllProps } from './util'; import { noop, menuAllProps } from './util';
import { getComponent, getSlot, findDOMNode } from '../_util/props-util'; import { getComponent, getSlot, findDOMNode } from '../_util/props-util';
import { inject } from 'vue';
const props = { const props = {
attribute: PropTypes.object, attribute: PropTypes.object,
@ -18,7 +19,6 @@ const props = {
inlineIndent: PropTypes.number.def(24), inlineIndent: PropTypes.number.def(24),
level: PropTypes.number.def(1), level: PropTypes.number.def(1),
mode: PropTypes.oneOf(['horizontal', 'vertical', 'vertical-left', 'vertical-right', 'inline']), mode: PropTypes.oneOf(['horizontal', 'vertical', 'vertical-left', 'vertical-right', 'inline']),
parentMenu: PropTypes.object,
multiple: PropTypes.bool, multiple: PropTypes.bool,
value: PropTypes.any, value: PropTypes.any,
isSelected: PropTypes.bool, isSelected: PropTypes.bool,
@ -34,6 +34,9 @@ const MenuItem = {
props, props,
mixins: [BaseMixin], mixins: [BaseMixin],
isMenuItem: true, isMenuItem: true,
setup() {
return { parentMenu: inject('parentMenu', undefined) };
},
created() { created() {
this.prevActive = this.active; this.prevActive = this.active;
// invoke customized ref to expose component to mixin // invoke customized ref to expose component to mixin
@ -41,9 +44,9 @@ const MenuItem = {
}, },
updated() { updated() {
this.$nextTick(() => { this.$nextTick(() => {
const { active, parentMenu, eventKey } = this.$props; const { active, parentMenu, eventKey } = this;
if (!this.prevActive && active && (!parentMenu || !parentMenu[`scrolled-${eventKey}`])) { if (!this.prevActive && active && (!parentMenu || !parentMenu[`scrolled-${eventKey}`])) {
scrollIntoView(this.$refs.node, findDOMNode(this.parentMenu), { scrollIntoView(findDOMNode(this.node), findDOMNode(parentMenu), {
onlyScrollIfNeeded: true, onlyScrollIfNeeded: true,
}); });
parentMenu[`scrolled-${eventKey}`] = true; parentMenu[`scrolled-${eventKey}`] = true;
@ -127,7 +130,9 @@ const MenuItem = {
getDisabledClassName() { getDisabledClassName() {
return `${this.getPrefixCls()}-disabled`; return `${this.getPrefixCls()}-disabled`;
}, },
saveNode(node) {
this.node = node;
},
callRef() { callRef() {
if (this.manualRef) { if (this.manualRef) {
this.manualRef(this); this.manualRef(this);
@ -182,7 +187,7 @@ const MenuItem = {
...props, ...props,
...attrs, ...attrs,
...mouseEvent, ...mouseEvent,
ref: 'node', ref: this.saveNode,
}; };
delete liProps.children; delete liProps.children;
return ( return (

View File

@ -1,4 +1,4 @@
import { Transition } from 'vue'; import { Transition, inject, provide } from 'vue';
import omit from 'omit.js'; import omit from 'omit.js';
import PropTypes from '../_util/vue-types'; import PropTypes from '../_util/vue-types';
import Trigger from '../vc-trigger'; import Trigger from '../vc-trigger';
@ -36,7 +36,6 @@ const SubMenu = {
name: 'SubMenu', name: 'SubMenu',
inheritAttrs: false, inheritAttrs: false,
props: { props: {
parentMenu: PropTypes.object,
title: PropTypes.any, title: PropTypes.any,
selectedKeys: PropTypes.array.def([]), selectedKeys: PropTypes.array.def([]),
openKeys: PropTypes.array.def([]), openKeys: PropTypes.array.def([]),
@ -77,6 +76,12 @@ const SubMenu = {
}, },
mixins: [BaseMixin], mixins: [BaseMixin],
isSubMenu: true, isSubMenu: true,
setup() {
return { parentMenu: inject('parentMenu', undefined) };
},
created() {
provide('parentMenu', this);
},
data() { data() {
const props = this.$props; const props = this.$props;
const store = props.store; const store = props.store;
@ -127,7 +132,7 @@ const SubMenu = {
}, },
methods: { methods: {
handleUpdated() { handleUpdated() {
const { mode, parentMenu, manualRef } = this.$props; const { mode, parentMenu, manualRef } = this;
// invoke customized ref to expose component to mixin // invoke customized ref to expose component to mixin
if (manualRef) { if (manualRef) {
@ -196,26 +201,15 @@ const SubMenu = {
}, },
onMouseLeave(e) { onMouseLeave(e) {
const { eventKey, parentMenu } = this; const { eventKey } = this;
parentMenu.subMenuInstance = this;
// parentMenu.subMenuLeaveFn = () => {
// // trigger mouseleave
// this.__emit('mouseleave', {
// key: eventKey,
// domEvent: e,
// })
// }
this.__emit('mouseleave', { this.__emit('mouseleave', {
key: eventKey, key: eventKey,
domEvent: e, domEvent: e,
}); });
// prevent popup menu and submenu gap
// parentMenu.subMenuLeaveTimer = setTimeout(parentMenu.subMenuLeaveFn, 100)
}, },
onTitleMouseEnter(domEvent) { onTitleMouseEnter(domEvent) {
const { eventKey: key } = this.$props; const { eventKey: key } = this.$props;
// this.clearSubMenuTitleLeaveTimer()
this.__emit('itemHover', { this.__emit('itemHover', {
key, key,
hover: true, hover: true,
@ -227,8 +221,7 @@ const SubMenu = {
}, },
onTitleMouseLeave(e) { onTitleMouseLeave(e) {
const { eventKey, parentMenu } = this; const { eventKey } = this;
parentMenu.subMenuInstance = this;
this.__emit('itemHover', { this.__emit('itemHover', {
key: eventKey, key: eventKey,
hover: false, hover: false,
@ -356,7 +349,6 @@ const SubMenu = {
openTransitionName: props.openTransitionName, openTransitionName: props.openTransitionName,
openAnimation: props.openAnimation, openAnimation: props.openAnimation,
subMenuOpenDelay: props.subMenuOpenDelay, subMenuOpenDelay: props.subMenuOpenDelay,
parentMenu: this,
subMenuCloseDelay: props.subMenuCloseDelay, subMenuCloseDelay: props.subMenuCloseDelay,
forceSubMenuRender: props.forceSubMenuRender, forceSubMenuRender: props.forceSubMenuRender,
triggerSubMenuAction: props.triggerSubMenuAction, triggerSubMenuAction: props.triggerSubMenuAction,

View File

@ -1,4 +1,4 @@
import { Comment } from 'vue'; import { Comment, inject } from 'vue';
import PropTypes from '../_util/vue-types'; import PropTypes from '../_util/vue-types';
import { connect } from '../_util/store'; import { connect } from '../_util/store';
import BaseMixin from '../_util/BaseMixin'; import BaseMixin from '../_util/BaseMixin';
@ -89,7 +89,6 @@ const SubPopupMenu = {
openAnimation: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), openAnimation: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
openKeys: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])), openKeys: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])),
visible: PropTypes.bool, visible: PropTypes.bool,
parentMenu: PropTypes.object,
eventKey: PropTypes.string, eventKey: PropTypes.string,
store: PropTypes.object, store: PropTypes.object,
@ -131,6 +130,9 @@ const SubPopupMenu = {
), ),
mixins: [BaseMixin], mixins: [BaseMixin],
setup() {
return { parentMenu: inject('parentMenu', undefined) };
},
created() { created() {
const props = getOptionProps(this); const props = getOptionProps(this);
this.prevProps = { ...props }; this.prevProps = { ...props };
@ -293,7 +295,6 @@ const SubPopupMenu = {
renderMenuItem: this.renderMenuItem, renderMenuItem: this.renderMenuItem,
rootPrefixCls: props.prefixCls, rootPrefixCls: props.prefixCls,
index: i, index: i,
parentMenu: props.parentMenu,
// customized ref function, need to be invoked manually in child's componentDidMount // customized ref function, need to be invoked manually in child's componentDidMount
manualRef: childProps.disabled ? noop : saveRef.bind(this, key), manualRef: childProps.disabled ? noop : saveRef.bind(this, key),
eventKey: key, eventKey: key,

View File

@ -74,7 +74,6 @@ export const menuAllProps = [
'defaultActiveFirst', 'defaultActiveFirst',
'prefixCls', 'prefixCls',
'inlineIndent', 'inlineIndent',
'parentMenu',
'title', 'title',
'rootPrefixCls', 'rootPrefixCls',
'eventKey', 'eventKey',

View File

@ -4,7 +4,7 @@
</div> </div>
</template> </template>
<script> <script>
import demo from '../antdv-demo/docs/drawer/demo/index'; import demo from '../antdv-demo/docs/dropdown/demo/index';
export default { export default {
components: { components: {