feat: update message notification popover popconfirm

pull/2376/head
tangjinzhou 2020-06-14 21:41:29 +08:00
parent 48dda04b75
commit e2c86fe21e
12 changed files with 146 additions and 155 deletions

View File

@ -1,3 +1,5 @@
# break change
## Tag
### CheckableTag
@ -9,3 +11,21 @@ v-model -> v-model:checked
v-model -> v-model:visible
移除 afterClose 属性
## Radio
radioGroup radio v-model -> v-model:value
## popconfirm
okButtonProps、cancelButtonProps 扁平化处理
v-model -> v-model:visible
## popover
v-model -> v-model:visible
## Tooltip
v-model -> v-model:visible

View File

@ -27,7 +27,7 @@ export default {
let eventName = args[0];
// TODO: 后续统一改成onXxxx不在运行时转提升性能
eventName = `on${eventName[0].toUpperCase()}${eventName.substring(1)}`;
const event = this.$attrs[eventName];
const event = this.$props[eventName] || this.$attrs[eventName];
if (args.length && event) {
if (Array.isArray(event)) {
for (let i = 0, l = event.length; i < l; i++) {

View File

@ -1,6 +1,6 @@
import isPlainObject from 'lodash/isPlainObject';
import classNames from 'classnames';
import { isVNode } from 'vue';
import { isVNode, Fragment, Comment, Text } from 'vue';
import { camelize, hyphenate, isOn, resolvePropValue } from './util';
// function getType(fn) {
// const match = fn && fn.toString().match(/^\s*function (\w+)/);
@ -68,7 +68,11 @@ const getSlots = ele => {
return { ...slots, ...getScopedSlots(ele) };
};
const getSlot = (self, name = 'default', options = {}) => {
return self.$slots[name] && self.$slots[name](options);
let res = self.$slots[name] && self.$slots[name](options);
while (res && res.length === 1 && res[0].type === Fragment) {
res = res[0].children;
}
return res;
};
const getAllChildren = ele => {
@ -267,7 +271,7 @@ export function getComponentName(opts) {
}
export function isEmptyElement(c) {
return typeof c.type.toString() === 'Symbol(Text)' && c.children.trim() === '';
return c.type === Comment || (c.type === Text && c.children.trim() === '');
}
export function isStringElement(c) {

View File

@ -63,13 +63,13 @@ function notice(args) {
key: target,
duration,
style: {},
content: h => {
content: () => {
return (
<div
class={`${prefixCls}-custom-content${args.type ? ` ${prefixCls}-${args.type}` : ''}`}
>
{args.icon ? typeof args.icon === 'function' ? args.icon(h) : args.icon : <Icon />}
<span>{typeof args.content === 'function' ? args.content(h) : args.content}</span>
{args.icon ? typeof args.icon === 'function' ? args.icon() : args.icon : <Icon />}
<span>{typeof args.content === 'function' ? args.content() : args.content}</span>
</div>
);
},

View File

@ -40,28 +40,28 @@ function getPlacementStyle(placement, top = defaultTop, bottom = defaultBottom)
switch (placement) {
case 'topLeft':
style = {
left: 0,
left: '0px',
top,
bottom: 'auto',
};
break;
case 'topRight':
style = {
right: 0,
right: '0px',
top,
bottom: 'auto',
};
break;
case 'bottomLeft':
style = {
left: 0,
left: '0px',
top: 'auto',
bottom,
};
break;
default:
style = {
right: 0,
right: '0px',
top: 'auto',
bottom,
};
@ -92,8 +92,8 @@ function getNotificationInstance(
class: `${prefixCls}-${placement}`,
style: getPlacementStyle(placement, top, bottom),
getContainer,
closeIcon: h => {
const icon = typeof closeIcon === 'function' ? closeIcon(h) : closeIcon;
closeIcon: () => {
const icon = typeof closeIcon === 'function' ? closeIcon() : closeIcon;
const closeIconToRender = (
<span class={`${prefixCls}-close-x`}>
{icon || <CloseOutlined class={`${prefixCls}-close-icon`} />}
@ -124,12 +124,12 @@ function notice(args) {
let iconNode = null;
if (icon) {
iconNode = h => (
<span class={`${prefixCls}-icon`}>{typeof icon === 'function' ? icon(h) : icon}</span>
iconNode = () => (
<span class={`${prefixCls}-icon`}>{typeof icon === 'function' ? icon() : icon}</span>
);
} else if (type) {
const Icon = typeToIcon[type];
iconNode = h => <Icon class={`${prefixCls}-icon ${prefixCls}-icon-${type}`} />; // eslint-disable-line
iconNode = () => <Icon class={`${prefixCls}-icon ${prefixCls}-icon-${type}`} />;
}
const { placement, top, bottom, getContainer, closeIcon } = args;
getNotificationInstance(
@ -143,20 +143,20 @@ function notice(args) {
},
notification => {
notification.notice({
content: h => (
content: () => (
<div class={iconNode ? `${prefixCls}-with-icon` : ''}>
{iconNode && iconNode(h)}
<div class={`${prefixCls}-message`}>
{!description && iconNode ? (
<span class={`${prefixCls}-message-single-line-auto-margin`} />
) : null}
{typeof message === 'function' ? message(h) : message}
{typeof message === 'function' ? message() : message}
</div>
<div class={`${prefixCls}-description`}>
{typeof description === 'function' ? description(h) : description}
{typeof description === 'function' ? description() : description}
</div>
{btn ? (
<span class={`${prefixCls}-btn`}>{typeof btn === 'function' ? btn(h) : btn}</span>
<span class={`${prefixCls}-btn`}>{typeof btn === 'function' ? btn() : btn}</span>
) : null}
</div>
),

View File

@ -1,8 +1,9 @@
import omit from 'omit.js';
import { inject } from 'vue';
import Tooltip from '../tooltip';
import abstractTooltipProps from '../tooltip/abstractTooltipProps';
import PropTypes from '../_util/vue-types';
import { getOptionProps, hasProp, getComponentFromProp, mergeProps } from '../_util/props-util';
import { getOptionProps, hasProp, getComponent, mergeProps } from '../_util/props-util';
import BaseMixin from '../_util/BaseMixin';
import buttonTypes from '../button/buttonTypes';
import ExclamationCircleFilled from '@ant-design/icons-vue/ExclamationCircleFilled';
@ -10,7 +11,6 @@ import Button from '../button';
import LocaleReceiver from '../locale-provider/LocaleReceiver';
import defaultLocale from '../locale-provider/default';
import { ConfigConsumerProps } from '../config-provider';
import Base from '../base';
const tooltipProps = abstractTooltipProps();
const btnProps = buttonTypes();
@ -30,19 +30,20 @@ const Popconfirm = {
icon: PropTypes.any,
okButtonProps: PropTypes.object,
cancelButtonProps: PropTypes.object,
onConfirm: PropTypes.func,
onCancel: PropTypes.func,
onVisibleChange: PropTypes.func,
},
mixins: [BaseMixin],
model: {
prop: 'visible',
event: 'visibleChange',
},
watch: {
visible(val) {
this.sVisible = val;
},
},
inject: {
configProvider: { default: () => ConfigConsumerProps },
setup() {
return {
configProvider: inject('configProvider', ConfigConsumerProps),
};
},
data() {
const props = getOptionProps(this);
@ -56,17 +57,17 @@ const Popconfirm = {
return state;
},
methods: {
onConfirm(e) {
onConfirmHandle(e) {
this.setVisible(false, e);
this.$emit('confirm', e);
},
onCancel(e) {
onCancelHandle(e) {
this.setVisible(false, e);
this.$emit('cancel', e);
},
onVisibleChange(sVisible) {
onVisibleChangeHandle(sVisible) {
const { disabled } = this.$props;
if (disabled) {
return;
@ -78,6 +79,7 @@ const Popconfirm = {
if (!hasProp(this, 'visible')) {
this.setState({ sVisible });
}
this.$emit('update:visible', sVisible);
this.$emit('visibleChange', sVisible, e);
},
getPopupDomNode() {
@ -85,44 +87,30 @@ const Popconfirm = {
},
renderOverlay(prefixCls, popconfirmLocale) {
const { okType, okButtonProps, cancelButtonProps } = this;
const icon = getComponentFromProp(this, 'icon') || (
<ExclamationCircleFilled/>
);
const cancelBtnProps = mergeProps(
{
props: {
size: 'small',
},
on: {
click: this.onCancel,
},
},
cancelButtonProps,
);
const okBtnProps = mergeProps(
{
props: {
type: okType,
size: 'small',
},
on: {
click: this.onConfirm,
},
},
okButtonProps,
);
const icon = getComponent(this, 'icon') || <ExclamationCircleFilled />;
const cancelBtnProps = mergeProps({
size: 'small',
onClick: this.onCancelHandle,
...cancelButtonProps,
});
const okBtnProps = mergeProps({
type: okType,
size: 'small',
onClick: this.onConfirmHandle,
...okButtonProps,
});
return (
<div class={`${prefixCls}-inner-content`}>
<div class={`${prefixCls}-message`}>
{icon}
<div class={`${prefixCls}-message-title`}>{getComponentFromProp(this, 'title')}</div>
<div class={`${prefixCls}-message-title`}>{getComponent(this, 'title')}</div>
</div>
<div class={`${prefixCls}-buttons`}>
<Button {...cancelBtnProps}>
{getComponentFromProp(this, 'cancelText') || popconfirmLocale.cancelText}
{getComponent(this, 'cancelText') || popconfirmLocale.cancelText}
</Button>
<Button {...okBtnProps}>
{getComponentFromProp(this, 'okText') || popconfirmLocale.okText}
{getComponent(this, 'okText') || popconfirmLocale.okText}
</Button>
</div>
</div>
@ -136,39 +124,28 @@ const Popconfirm = {
const prefixCls = getPrefixCls('popover', customizePrefixCls);
const otherProps = omit(props, ['title', 'content', 'cancelText', 'okText']);
const tooltipProps = {
props: {
...otherProps,
prefixCls,
visible: this.sVisible,
},
ref: 'tooltip',
on: {
visibleChange: this.onVisibleChange,
},
};
const overlay = (
<LocaleReceiver
componentName="Popconfirm"
defaultLocale={defaultLocale.Popconfirm}
scopedSlots={{
default: popconfirmLocale => this.renderOverlay(prefixCls, popconfirmLocale),
}}
children={popconfirmLocale => this.renderOverlay(prefixCls, popconfirmLocale)}
/>
);
return (
<Tooltip {...tooltipProps}>
<template slot="title">{overlay}</template>
{this.$slots.default}
</Tooltip>
);
const tooltipProps = {
...otherProps,
title: overlay,
prefixCls,
visible: this.sVisible,
ref: 'tooltip',
onVisibleChange: this.onVisibleChangeHandle,
};
return <Tooltip {...tooltipProps}>{this.$slots?.default()}</Tooltip>;
},
};
/* istanbul ignore next */
Popconfirm.install = function(Vue) {
Vue.use(Base);
Vue.component(Popconfirm.name, Popconfirm);
Popconfirm.install = function(app) {
app.component(Popconfirm.name, Popconfirm);
};
export default Popconfirm;

View File

@ -1,9 +1,9 @@
import { inject } from 'vue';
import Tooltip from '../tooltip';
import abstractTooltipProps from '../tooltip/abstractTooltipProps';
import PropTypes from '../_util/vue-types';
import { getOptionProps, getComponentFromProp, getListeners } from '../_util/props-util';
import { getOptionProps, getComponent, getSlot } from '../_util/props-util';
import { ConfigConsumerProps } from '../config-provider';
import Base from '../base';
const props = abstractTooltipProps();
const Popover = {
@ -15,12 +15,10 @@ const Popover = {
content: PropTypes.any,
title: PropTypes.any,
},
model: {
prop: 'visible',
event: 'visibleChange',
},
inject: {
configProvider: { default: () => ConfigConsumerProps },
setup() {
return {
configProvider: inject('configProvider', ConfigConsumerProps),
};
},
methods: {
getPopupDomNode() {
@ -37,33 +35,25 @@ const Popover = {
delete props.title;
delete props.content;
const tooltipProps = {
props: {
...props,
prefixCls,
},
...props,
prefixCls,
ref: 'tooltip',
on: getListeners(this),
title: (
<div>
{(title || $slots.title) && (
<div class={`${prefixCls}-title`}>{getComponent(this, 'title')}</div>
)}
<div class={`${prefixCls}-inner-content`}>{getComponent(this, 'content')}</div>
</div>
),
};
return (
<Tooltip {...tooltipProps}>
<template slot="title">
<div>
{(title || $slots.title) && (
<div class={`${prefixCls}-title`}>{getComponentFromProp(this, 'title')}</div>
)}
<div class={`${prefixCls}-inner-content`}>{getComponentFromProp(this, 'content')}</div>
</div>
</template>
{this.$slots.default}
</Tooltip>
);
return <Tooltip {...tooltipProps}>{getSlot(this)}</Tooltip>;
},
};
/* istanbul ignore next */
Popover.install = function(Vue) {
Vue.use(Base);
Vue.component(Popover.name, Popover);
Popover.install = function(app) {
app.component(Popover.name, Popover);
};
export default Popover;

View File

@ -29,10 +29,6 @@ const props = abstractTooltipProps();
export default {
name: 'ATooltip',
inheritAttrs: false,
model: {
prop: 'visible',
event: 'visibleChange',
},
props: {
...props,
title: PropTypes.any,
@ -58,6 +54,7 @@ export default {
this.sVisible = this.isNoTitle() ? false : visible;
}
if (!this.isNoTitle()) {
this.$emit('update:visible', visible);
this.$emit('visibleChange', visible);
}
},

View File

@ -1,9 +1,7 @@
import PropTypes from '../_util/vue-types';
import { getStyle, getComponentFromProp, getListeners } from '../_util/props-util';
import { getComponent, getSlot } from '../_util/props-util';
import BaseMixin from '../_util/BaseMixin';
function noop() {}
export default {
mixins: [BaseMixin],
props: {
@ -12,6 +10,7 @@ export default {
prefixCls: PropTypes.string,
update: PropTypes.bool,
closeIcon: PropTypes.any,
onClose: PropTypes.func,
},
watch: {
duration() {
@ -63,23 +62,21 @@ export default {
},
render() {
const { prefixCls, closable, clearCloseTimer, startCloseTimer, $slots, close } = this;
const { prefixCls, closable, clearCloseTimer, startCloseTimer, close, $attrs } = this;
const componentClass = `${prefixCls}-notice`;
const className = {
[`${componentClass}`]: 1,
[`${componentClass}-closable`]: closable,
};
const style = getStyle(this);
const closeIcon = getComponentFromProp(this, 'closeIcon');
const closeIcon = getComponent(this, 'closeIcon');
return (
<div
class={className}
style={style || { right: '50%' }}
style={$attrs.style || { right: '50%' }}
onMouseenter={clearCloseTimer}
onMouseleave={startCloseTimer}
onClick={getListeners(this).click || noop}
>
<div class={`${componentClass}-content`}>{$slots.default}</div>
<div class={`${componentClass}-content`}>{getSlot(this)}</div>
{closable ? (
<a tabIndex="0" onClick={close} class={`${componentClass}-close`}>
{closeIcon || <span class={`${componentClass}-close-x`} />}

View File

@ -1,11 +1,10 @@
import Vue from 'vue';
import { createApp, TransitionGroup } from 'vue';
import PropTypes from '../_util/vue-types';
import { getStyle, getComponentFromProp } from '../_util/props-util';
import { getComponent } from '../_util/props-util';
import BaseMixin from '../_util/BaseMixin';
import createChainedFunction from '../_util/createChainedFunction';
import getTransitionProps from '../_util/getTransitionProps';
import Notice from './Notice';
import Base from '../base';
function noop() {}
@ -74,8 +73,8 @@ const Notification = {
},
},
render(h) {
const { prefixCls, notices, remove, getTransitionName } = this;
render() {
const { prefixCls, notices, remove, getTransitionName, $attrs } = this;
const transitionProps = getTransitionProps(getTransitionName());
const noticeNodes = notices.map((notice, index) => {
const update = Boolean(index === notices.length - 1 && notice.updateKey);
@ -84,40 +83,37 @@ const Notification = {
const { content, duration, closable, onClose, style, class: className } = notice;
const close = createChainedFunction(remove.bind(this, notice.key), onClose);
const noticeProps = {
props: {
prefixCls,
duration,
closable,
update,
closeIcon: getComponentFromProp(this, 'closeIcon'),
},
on: {
close,
click: notice.onClick || noop,
},
prefixCls,
duration,
closable,
update,
closeIcon: getComponent(this, 'closeIcon'),
onClose: close,
onClick: notice.onClick || noop,
style,
class: className,
key,
};
return (
<Notice {...noticeProps}>{typeof content === 'function' ? content(h) : content}</Notice>
<Notice {...noticeProps}>{typeof content === 'function' ? content() : content}</Notice>
);
});
const className = {
[prefixCls]: 1,
};
const style = getStyle(this);
return (
<div
class={className}
style={
style || {
$attrs.style || {
top: '65px',
left: '50%',
}
}
>
<transition-group {...transitionProps}>{noticeNodes}</transition-group>
<TransitionGroup tag="span" {...transitionProps}>
{noticeNodes}
</TransitionGroup>
</div>
);
},
@ -132,9 +128,7 @@ Notification.newInstance = function newNotificationInstance(properties, callback
} else {
document.body.appendChild(div);
}
const V = Base.Vue || Vue;
new V({
el: div,
const app = createApp({
mounted() {
const self = this;
this.$nextTick(() => {
@ -147,15 +141,17 @@ Notification.newInstance = function newNotificationInstance(properties, callback
},
component: self,
destroy() {
self.$destroy();
self.$el.parentNode.removeChild(self.$el);
app.unmount(div);
if (div.parentNode) {
div.parentNode.removeChild(div);
}
},
});
});
},
render() {
const p = {
props,
...props,
ref: 'notification',
style,
class: className,
@ -163,6 +159,7 @@ Notification.newInstance = function newNotificationInstance(properties, callback
return <Notification {...p} />;
},
});
app.mount(div);
};
export default Notification;

View File

@ -1,21 +1,21 @@
import PropTypes from '../_util/vue-types';
import { getSlot } from '../_util/props-util';
export default {
name: 'LazyRenderBox',
inheritAttrs: false,
props: {
visible: PropTypes.bool,
hiddenClassName: PropTypes.string,
},
render() {
const { hiddenClassName } = this.$props;
const child = this.$slots.default && this.$slots.default();
const child = getSlot(this);
if (hiddenClassName || (child && child.length > 1)) {
// const cls = '';
// if (!visible && hiddenClassName) {
// // cls += ` ${hiddenClassName}`
// }
return <div {...this.$attrs}>{child}</div>;
return <div>{child}</div>;
}
return child && child[0];
},

View File

@ -24,9 +24,16 @@ import Tooltip from 'ant-design-vue/tooltip';
import Descriptions from 'ant-design-vue/descriptions';
import BackTop from 'ant-design-vue/back-top';
import Tag from 'ant-design-vue/tag';
import Popconfirm from 'ant-design-vue/popconfirm';
import Popover from 'ant-design-vue/popover';
import notification from 'ant-design-vue/notification';
import message from 'ant-design-vue/message';
import 'ant-design-vue/style.js';
createApp(App)
const app = createApp(App);
app.config.globalProperties.$notification = notification;
app.config.globalProperties.$message = message;
app
.use(Avatar)
.use(Breadcrumb)
.use(Button)
@ -50,4 +57,6 @@ createApp(App)
.use(Descriptions)
.use(BackTop)
.use(Tag)
.use(Popconfirm)
.use(Popover)
.mount('#app');