feat: update dropdown

pull/2379/head
tangjinzhou 2020-06-21 22:45:30 +08:00
parent 96ba93cf9b
commit a0e637da9c
9 changed files with 110 additions and 131 deletions

@ -1 +1 @@
Subproject commit 78341d7cd7c34015b7369982cfa7914fbc558232 Subproject commit bf1777054fbe55a0d3bbdd037573e3a235137fbb

View File

@ -43,3 +43,7 @@ v-model -> v-model:value
## menu ## menu
v-model -> v-model:selectedKeys :openKeys.sync -> v-mdoel:openKeys v-model -> v-model:selectedKeys :openKeys.sync -> v-mdoel:openKeys
## dropdown
v-model -> v-model:visible

View File

@ -200,7 +200,6 @@ const getAllProps = ele => {
return props; return props;
}; };
// 使用 getOptionProps 替换 ,待测试
const getPropsData = vnode => { const getPropsData = vnode => {
const res = {}; const res = {};
const originProps = vnode.props || {}; const originProps = vnode.props || {};
@ -208,7 +207,7 @@ const getPropsData = vnode => {
Object.keys(originProps).forEach(key => { Object.keys(originProps).forEach(key => {
props[camelize(key)] = originProps[key]; props[camelize(key)] = originProps[key];
}); });
const options = vnode.type.props; const options = isPlainObject(vnode.type) ? vnode.type.props : {};
Object.keys(options).forEach(k => { Object.keys(options).forEach(k => {
const v = resolvePropValue(options, props, k, props[k]); const v = resolvePropValue(options, props, k, props[k]);
if (k in props) { if (k in props) {

View File

@ -1,9 +1,10 @@
import { provide, inject } from 'vue';
import Button from '../button'; import Button from '../button';
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';
import PropTypes from '../_util/vue-types'; import PropTypes from '../_util/vue-types';
import { hasProp, getComponentFromProp } from '../_util/props-util'; import { hasProp, getComponent, getSlot } from '../_util/props-util';
import getDropdownProps from './getDropdownProps'; import getDropdownProps from './getDropdownProps';
import { ConfigConsumerProps } from '../config-provider'; import { ConfigConsumerProps } from '../config-provider';
import EllipsisOutlined from '@ant-design/icons-vue/EllipsisOutlined'; import EllipsisOutlined from '@ant-design/icons-vue/EllipsisOutlined';
@ -27,18 +28,14 @@ const DropdownButtonProps = {
export { DropdownButtonProps }; export { DropdownButtonProps };
export default { export default {
name: 'ADropdownButton', name: 'ADropdownButton',
model: {
prop: 'visible',
event: 'visibleChange',
},
props: DropdownButtonProps, props: DropdownButtonProps,
provide() { setup() {
return { return {
savePopupRef: this.savePopupRef, configProvider: inject('configProvider', ConfigConsumerProps),
}; };
}, },
inject: { created() {
configProvider: { default: () => ConfigConsumerProps }, provide('savePopupRef', this.savePopupRef);
}, },
methods: { methods: {
savePopupRef(ref) { savePopupRef(ref) {
@ -48,6 +45,7 @@ export default {
this.$emit('click', e); this.$emit('click', e);
}, },
onVisibleChange(val) { onVisibleChange(val) {
this.$emit('update:visible', val);
this.$emit('visibleChange', val); this.$emit('visibleChange', val);
}, },
}, },
@ -66,30 +64,24 @@ export default {
title, title,
...restProps ...restProps
} = this.$props; } = this.$props;
const icon = getComponentFromProp(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;
const prefixCls = getPrefixCls('dropdown-button', customizePrefixCls); const prefixCls = getPrefixCls('dropdown-button', customizePrefixCls);
const dropdownProps = { const dropdownProps = {
props: { align,
align, disabled,
disabled, trigger: disabled ? [] : trigger,
trigger: disabled ? [] : trigger, placement,
placement, getPopupContainer: getPopupContainer || getContextPopupContainer,
getPopupContainer: getPopupContainer || getContextPopupContainer, onVisibleChange: this.onVisibleChange,
},
on: {
visibleChange: this.onVisibleChange,
},
}; };
if (hasProp(this, 'visible')) { if (hasProp(this, 'visible')) {
dropdownProps.props.visible = visible; dropdownProps.visible = visible;
} }
const buttonGroupProps = { const buttonGroupProps = {
props: { ...restProps,
...restProps,
},
class: prefixCls, class: prefixCls,
}; };
@ -103,10 +95,9 @@ export default {
href={href} href={href}
title={title} title={title}
> >
{this.$slots.default} {getSlot(this)}
</Button> </Button>
<Dropdown {...dropdownProps}> <Dropdown {...dropdownProps} overlay={getComponent(this, 'overlay')}>
<template slot="overlay">{getComponentFromProp(this, 'overlay')}</template>
<Button type={type}>{icon}</Button> <Button type={type}>{icon}</Button>
</Dropdown> </Dropdown>
</ButtonGroup> </ButtonGroup>

View File

@ -1,3 +1,4 @@
import { provide, inject, cloneVNode } from 'vue';
import RcDropdown from '../vc-dropdown/src/index'; 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';
@ -5,8 +6,9 @@ import { cloneElement } from '../_util/vnode';
import { import {
getOptionProps, getOptionProps,
getPropsData, getPropsData,
getComponentFromProp, getComponent,
getListeners, isValidElement,
getSlot,
} from '../_util/props-util'; } from '../_util/props-util';
import getDropdownProps from './getDropdownProps'; import getDropdownProps from './getDropdownProps';
import { ConfigConsumerProps } from '../config-provider'; import { ConfigConsumerProps } from '../config-provider';
@ -22,17 +24,13 @@ const Dropdown = {
mouseLeaveDelay: PropTypes.number.def(0.1), mouseLeaveDelay: PropTypes.number.def(0.1),
placement: DropdownProps.placement.def('bottomLeft'), placement: DropdownProps.placement.def('bottomLeft'),
}, },
model: { setup() {
prop: 'visible',
event: 'visibleChange',
},
provide() {
return { return {
savePopupRef: this.savePopupRef, configProvider: inject('configProvider', ConfigConsumerProps),
}; };
}, },
inject: { created() {
configProvider: { default: () => ConfigConsumerProps }, provide('savePopupRef', this.savePopupRef);
}, },
methods: { methods: {
savePopupRef(ref) { savePopupRef(ref) {
@ -49,7 +47,7 @@ const Dropdown = {
return 'slide-up'; return 'slide-up';
}, },
renderOverlay(prefixCls) { renderOverlay(prefixCls) {
const overlay = getComponentFromProp(this, 'overlay'); const overlay = getComponent(this, 'overlay');
const overlayNode = Array.isArray(overlay) ? overlay[0] : overlay; const overlayNode = Array.isArray(overlay) ? overlay[0] : overlay;
// menu cannot be selectable in dropdown defaultly // menu cannot be selectable in dropdown defaultly
// menu should be focusable in dropdown defaultly // menu should be focusable in dropdown defaultly
@ -61,57 +59,48 @@ const Dropdown = {
</span> </span>
); );
const fixedModeOverlay = const fixedModeOverlay = isValidElement(overlayNode)
overlayNode && overlayNode.componentOptions ? cloneVNode(overlayNode, {
? cloneElement(overlayNode, { mode: 'vertical',
props: { selectable,
mode: 'vertical', focusable,
selectable, expandIcon,
focusable, })
expandIcon, : overlay;
},
})
: overlay;
return fixedModeOverlay; return fixedModeOverlay;
}, },
}, },
render() { render() {
const { $slots } = this;
const props = getOptionProps(this); const props = getOptionProps(this);
const { prefixCls: customizePrefixCls, trigger, disabled, getPopupContainer } = props; const { prefixCls: customizePrefixCls, trigger, disabled, getPopupContainer } = props;
const { getPopupContainer: getContextPopupContainer } = this.configProvider; const { getPopupContainer: getContextPopupContainer } = this.configProvider;
const getPrefixCls = this.configProvider.getPrefixCls; const getPrefixCls = this.configProvider.getPrefixCls;
const prefixCls = getPrefixCls('dropdown', customizePrefixCls); const prefixCls = getPrefixCls('dropdown', customizePrefixCls);
const dropdownTrigger = cloneElement($slots.default, { const dropdownTrigger = cloneElement(
class: `${prefixCls}-trigger`, getSlot(this),
props: { {
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) {
alignPoint = true; alignPoint = true;
} }
const dropdownProps = { const dropdownProps = {
props: { alignPoint,
alignPoint, ...props,
...props, prefixCls,
prefixCls, getPopupContainer: getPopupContainer || getContextPopupContainer,
getPopupContainer: getPopupContainer || getContextPopupContainer, transitionName: this.getTransitionName(),
transitionName: this.getTransitionName(), trigger: triggerActions,
trigger: triggerActions, overlay: this.renderOverlay(prefixCls),
},
on: getListeners(this),
}; };
return ( return <RcDropdown {...dropdownProps}>{dropdownTrigger}</RcDropdown>;
<RcDropdown {...dropdownProps}>
{dropdownTrigger}
<template slot="overlay">{this.renderOverlay(prefixCls)}</template>
</RcDropdown>
);
}, },
}; };

View File

@ -5,7 +5,7 @@ import addEventListener from '../vc-util/Dom/addEventListener';
import { isWindow, buffer, isSamePoint, isSimilarValue, restoreFocus } from './util'; import { isWindow, buffer, isSamePoint, isSimilarValue, restoreFocus } from './util';
import { cloneElement } from '../_util/vnode.js'; import { cloneElement } from '../_util/vnode.js';
import clonedeep from 'lodash/cloneDeep'; import clonedeep from 'lodash/cloneDeep';
import { getSlot, getListeners } from '../_util/props-util'; import { getSlot, findDOMNode } from '../_util/props-util';
function getElement(func) { function getElement(func) {
if (typeof func !== 'function' || !func) return null; if (typeof func !== 'function' || !func) return null;
@ -117,8 +117,7 @@ export default {
forceAlign() { forceAlign() {
const { disabled, target, align } = this.$props; const { disabled, target, align } = this.$props;
if (!disabled && target) { if (!disabled && target) {
const source = this.$el; const source = findDOMNode(this);
const listeners = getListeners(this);
let result; let result;
const element = getElement(target); const element = getElement(target);
const point = getPoint(target); const point = getPoint(target);
@ -134,7 +133,7 @@ export default {
} }
restoreFocus(activeElement, source); restoreFocus(activeElement, source);
this.aligned = true; this.aligned = true;
listeners.align && listeners.align(source, result); this.$attrs.onAlign && this.$attrs.onAlign(source, result);
} }
}, },
}, },
@ -143,7 +142,7 @@ export default {
const { childrenProps } = this.$props; const { childrenProps } = this.$props;
const child = getSlot(this); const child = getSlot(this);
if (child && childrenProps) { if (child && childrenProps) {
return cloneElement(child[0], { props: childrenProps }); return cloneElement(child[0], childrenProps);
} }
return child && child[0]; return child && child[0];
}, },

View File

@ -1,7 +1,14 @@
import { Text } from 'vue';
import PropTypes from '../../_util/vue-types'; import PropTypes from '../../_util/vue-types';
import Trigger from '../../vc-trigger'; import Trigger from '../../vc-trigger';
import placements from './placements'; import placements from './placements';
import { hasProp, getEvents, getOptionProps } from '../../_util/props-util'; import {
hasProp,
getComponent,
getOptionProps,
getSlot,
findDOMNode,
} from '../../_util/props-util';
import BaseMixin from '../../_util/BaseMixin'; import BaseMixin from '../../_util/BaseMixin';
import { cloneElement } from '../../_util/vnode'; import { cloneElement } from '../../_util/vnode';
@ -48,6 +55,7 @@ export default {
}, },
methods: { methods: {
onClick(e) { onClick(e) {
const overlayProps = this.getOverlayElement().props;
// do no call onVisibleChange, if you need click to hide, use onClick and control visible // do no call onVisibleChange, if you need click to hide, use onClick and control visible
if (!hasProp(this, 'visible')) { if (!hasProp(this, 'visible')) {
this.setState({ this.setState({
@ -55,8 +63,8 @@ export default {
}); });
} }
this.$emit('overlayClick', e); this.$emit('overlayClick', e);
if (this.childOriginEvents.click) { if (overlayProps.onClick) {
this.childOriginEvents.click(e); overlayProps.onClick(e);
} }
}, },
@ -66,6 +74,7 @@ export default {
sVisible: visible, sVisible: visible,
}); });
} }
this.$emit('update:visible', visible);
this.__emit('visibleChange', visible); this.__emit('visibleChange', visible);
}, },
@ -80,37 +89,26 @@ export default {
}, },
getOverlayElement() { getOverlayElement() {
const overlay = this.overlay || this.$slots.overlay || this.$scopedSlots.overlay; const overlay = getComponent(this, 'overlay');
let overlayElement; return Array.isArray(overlay) ? overlay[0] : overlay;
if (typeof overlay === 'function') {
overlayElement = overlay();
} else {
overlayElement = overlay;
}
return overlayElement;
}, },
getMenuElement() { getMenuElement() {
const { onClick, prefixCls, $slots } = this; const { onClick, prefixCls } = this;
this.childOriginEvents = getEvents($slots.overlay[0]);
const overlayElement = this.getOverlayElement(); const overlayElement = this.getOverlayElement();
const extraOverlayProps = { const extraOverlayProps = {
props: { prefixCls: `${prefixCls}-menu`,
prefixCls: `${prefixCls}-menu`, getPopupContainer: () => this.getPopupDomNode(),
getPopupContainer: () => this.getPopupDomNode(), onClick,
},
on: {
click: onClick,
},
}; };
if (typeof overlayElement.type === 'string') { if (overlayElement && overlayElement.type === Text) {
delete extraOverlayProps.props.prefixCls; delete extraOverlayProps.prefixCls;
} }
return cloneElement($slots.overlay[0], extraOverlayProps); return cloneElement(overlayElement, extraOverlayProps);
}, },
getMenuElementOrLambda() { getMenuElementOrLambda() {
const overlay = this.overlay || this.$slots.overlay || this.$scopedSlots.overlay; const overlay = this.overlay || this.$slots.overlay;
if (typeof overlay === 'function') { if (typeof overlay === 'function') {
return this.getMenuElement; return this.getMenuElement;
} }
@ -132,7 +130,7 @@ export default {
afterVisibleChange(visible) { afterVisibleChange(visible) {
if (visible && this.getMinOverlayWidthMatchTrigger()) { if (visible && this.getMinOverlayWidthMatchTrigger()) {
const overlayNode = this.getPopupDomNode(); const overlayNode = this.getPopupDomNode();
const rootNode = this.$el; const rootNode = findDOMNode(this);
if (rootNode && overlayNode && rootNode.offsetWidth > overlayNode.offsetWidth) { if (rootNode && overlayNode && rootNode.offsetWidth > overlayNode.offsetWidth) {
overlayNode.style.minWidth = `${rootNode.offsetWidth}px`; overlayNode.style.minWidth = `${rootNode.offsetWidth}px`;
if ( if (
@ -147,10 +145,10 @@ export default {
}, },
renderChildren() { renderChildren() {
const children = this.$slots.default && this.$slots.default[0]; const children = getSlot(this);
const { sVisible } = this; const { sVisible } = this;
return sVisible && children return sVisible && children
? cloneElement(children, { class: this.getOpenClassName() }) ? cloneElement(children[0], { class: this.getOpenClassName() }, false)
: children; : children;
}, },
}, },
@ -176,33 +174,25 @@ export default {
} }
const triggerProps = { const triggerProps = {
props: { ...otherProps,
...otherProps, prefixCls,
prefixCls, popupClassName: overlayClassName,
popupClassName: overlayClassName, popupStyle: overlayStyle,
popupStyle: overlayStyle, builtinPlacements: placements,
builtinPlacements: placements, action: trigger,
action: trigger, showAction,
showAction, hideAction: triggerHideAction || [],
hideAction: triggerHideAction || [], popupPlacement: placement,
popupPlacement: placement, popupAlign: align,
popupAlign: align, popupTransitionName: transitionName,
popupTransitionName: transitionName, popupAnimation: animation,
popupAnimation: animation, popupVisible: this.sVisible,
popupVisible: this.sVisible, afterPopupVisibleChange: this.afterVisibleChange,
afterPopupVisibleChange: this.afterVisibleChange, getPopupContainer,
getPopupContainer, onPopupVisibleChange: this.onVisibleChange,
}, popup: this.getMenuElementOrLambda(),
on: {
popupVisibleChange: this.onVisibleChange,
},
ref: 'trigger', ref: 'trigger',
}; };
return ( return <Trigger {...triggerProps}>{this.renderChildren()}</Trigger>;
<Trigger {...triggerProps}>
{this.renderChildren()}
<template slot="popup">{this.$slots.overlay && this.getMenuElement()}</template>
</Trigger>
);
}, },
}; };

View File

@ -1,3 +1,4 @@
import { Text } from 'vue';
import PropTypes from '../_util/vue-types'; import PropTypes from '../_util/vue-types';
import { getSlot } from '../_util/props-util'; import { getSlot } from '../_util/props-util';
@ -10,7 +11,11 @@ export default {
render() { render() {
const { hiddenClassName } = this.$props; const { hiddenClassName } = this.$props;
const child = getSlot(this); const child = getSlot(this);
if (hiddenClassName || (child && child.length > 1)) { if (
hiddenClassName ||
(child && child.length > 1) ||
(child && child[0] && child[0].type === Text)
) {
// const cls = ''; // const cls = '';
// if (!visible && hiddenClassName) { // if (!visible && hiddenClassName) {
// // cls += ` ${hiddenClassName}` // // cls += ` ${hiddenClassName}`

View File

@ -32,6 +32,7 @@ import message from 'ant-design-vue/message';
import Modal from 'ant-design-vue/modal'; import Modal from 'ant-design-vue/modal';
import Menu from 'ant-design-vue/menu'; import Menu from 'ant-design-vue/menu';
import Mentions from 'ant-design-vue/mentions'; import Mentions from 'ant-design-vue/mentions';
import Dropdown from 'ant-design-vue/dropdown';
import 'ant-design-vue/style.js'; import 'ant-design-vue/style.js';
const basic = { const basic = {
@ -77,4 +78,5 @@ app
.use(Modal) .use(Modal)
.use(Menu) .use(Menu)
.use(Mentions) .use(Mentions)
.use(Dropdown)
.mount('#app'); .mount('#app');