refactor: popover & popconfirm

pull/6227/head
tangjinzhou 2023-02-02 22:16:55 +08:00
parent 47c84cdbad
commit 9b5a07220b
6 changed files with 98 additions and 84 deletions

View File

@ -55,6 +55,14 @@ export function booleanType(defaultVal?: any) {
return { type: Boolean, default: defaultVal as boolean }; return { type: Boolean, default: defaultVal as boolean };
} }
export function anyType<T = any>() {
return { validator: () => true } as unknown as { type: PropType<T> };
}
export function stringType<T extends string = string>(defaultVal?: string) {
return { type: String as unknown as PropType<T>, default: defaultVal as T };
}
export function someType<T>(types: any[], defaultVal?: any) { export function someType<T>(types: any[], defaultVal?: any) {
return { type: types as PropType<T>, default: defaultVal as T }; return { type: types as PropType<T>, default: defaultVal as T };
} }

View File

@ -133,7 +133,7 @@ export default defineComponent({
}); });
</script> </script>
<style scoped> <style scoped>
#components-a-popconfirm-demo-placement .ant-btn { :deep(#components-a-popconfirm-demo-placement) .ant-btn {
width: 70px; width: 70px;
text-align: center; text-align: center;
padding: 0; padding: 0;

View File

@ -28,15 +28,16 @@ The difference with the 'confirm' modal dialog is that it's more lightweight tha
| okType | Button `type` of the Confirm button | string | `primary` | | | okType | Button `type` of the Confirm button | string | `primary` | |
| showCancel | Show cancel button | boolean | true | 3.0 | | showCancel | Show cancel button | boolean | true | 3.0 |
| title | title of the confirmation box | string\|slot | - | | | title | title of the confirmation box | string\|slot | - | |
| description | The description of the confirmation box title | string\|slot | - | 4.0 |
| visible (v-model) | hide or show | boolean | - | | | visible (v-model) | hide or show | boolean | - | |
### events ### events
| Events Name | Description | Arguments | | | Events Name | Description | Arguments | Version |
| --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| cancel | callback of cancel | function(e) | - | | cancel | callback of cancel | function(e) | - | |
| confirm | callback of confirmation | function(e) | - | | confirm | callback of confirmation | function(e) | - | |
| visibleChange | Callback executed when visibility of the tooltip card is changed | function(visible) | - | | openChange | Callback executed when visibility of the tooltip card is changed | function(open) | - | 4.0 |
Consult [Tooltip's documentation](/components/tooltip/#API) to find more APIs. Consult [Tooltip's documentation](/components/tooltip/#API) to find more APIs.

View File

@ -1,8 +1,7 @@
import type { ExtractPropTypes, HTMLAttributes, PropType } from 'vue'; import type { ExtractPropTypes, HTMLAttributes, PropType } from 'vue';
import { computed, onMounted, ref, toRef, defineComponent } from 'vue'; import { computed, ref, toRef, defineComponent } from 'vue';
import Tooltip from '../tooltip'; import Popover from '../popover';
import abstractTooltipProps from '../tooltip/abstractTooltipProps'; import abstractTooltipProps from '../tooltip/abstractTooltipProps';
import PropTypes from '../_util/vue-types';
import { initDefaultProps } from '../_util/props-util'; import { initDefaultProps } from '../_util/props-util';
import type { ButtonProps, LegacyButtonType } from '../button/buttonTypes'; import type { ButtonProps, LegacyButtonType } from '../button/buttonTypes';
import { convertLegacyProps } from '../button/buttonTypes'; import { convertLegacyProps } from '../button/buttonTypes';
@ -10,9 +9,9 @@ import ExclamationCircleFilled from '@ant-design/icons-vue/ExclamationCircleFill
import Button from '../button'; import Button from '../button';
import { useLocaleReceiver } from '../locale-provider/LocaleReceiver'; import { useLocaleReceiver } from '../locale-provider/LocaleReceiver';
import defaultLocale from '../locale/en_US'; import defaultLocale from '../locale/en_US';
import { withInstall } from '../_util/type'; import { anyType, objectType, stringType, withInstall } from '../_util/type';
import useMergedState from '../_util/hooks/useMergedState'; import useMergedState from '../_util/hooks/useMergedState';
import devWarning from '../vc-util/devWarning';
import KeyCode from '../_util/KeyCode'; import KeyCode from '../_util/KeyCode';
import useConfigInject from '../config-provider/hooks/useConfigInject'; import useConfigInject from '../config-provider/hooks/useConfigInject';
import classNames from '../_util/classNames'; import classNames from '../_util/classNames';
@ -21,28 +20,21 @@ import { cloneVNodes } from '../_util/vnode';
import omit from '../_util/omit'; import omit from '../_util/omit';
import { tooltipDefaultProps } from '../tooltip/Tooltip'; import { tooltipDefaultProps } from '../tooltip/Tooltip';
import ActionButton from '../_util/ActionButton'; import ActionButton from '../_util/ActionButton';
import usePopconfirmStyle from './style';
export const popconfirmProps = () => ({ export const popconfirmProps = () => ({
...abstractTooltipProps(), ...abstractTooltipProps(),
prefixCls: String, prefixCls: String,
content: PropTypes.any, content: anyType(),
title: PropTypes.any, title: anyType<string | number>(),
okType: { description: anyType<string | number>(),
type: String as PropType<LegacyButtonType>, okType: stringType<LegacyButtonType>('primary'),
default: 'primary',
},
disabled: { type: Boolean, default: false }, disabled: { type: Boolean, default: false },
okText: PropTypes.any, okText: anyType(),
cancelText: PropTypes.any, cancelText: anyType(),
icon: PropTypes.any, icon: anyType(),
okButtonProps: { okButtonProps: objectType<ButtonProps & HTMLAttributes>(),
type: Object as PropType<ButtonProps & HTMLAttributes>, cancelButtonProps: objectType<ButtonProps & HTMLAttributes>(),
default: undefined as ButtonProps & HTMLAttributes,
},
cancelButtonProps: {
type: Object as PropType<ButtonProps & HTMLAttributes>,
default: undefined as ButtonProps & HTMLAttributes,
},
showCancel: { type: Boolean, default: true }, showCancel: { type: Boolean, default: true },
onConfirm: Function as PropType<(e: MouseEvent) => void>, onConfirm: Function as PropType<(e: MouseEvent) => void>,
onCancel: Function as PropType<(e: MouseEvent) => void>, onCancel: Function as PropType<(e: MouseEvent) => void>,
@ -58,6 +50,7 @@ export interface PopconfirmLocale {
const Popconfirm = defineComponent({ const Popconfirm = defineComponent({
compatConfig: { MODE: 3 }, compatConfig: { MODE: 3 },
name: 'APopconfirm', name: 'APopconfirm',
inheritAttrs: false,
props: initDefaultProps(popconfirmProps(), { props: initDefaultProps(popconfirmProps(), {
...tooltipDefaultProps(), ...tooltipDefaultProps(),
trigger: 'click', trigger: 'click',
@ -71,37 +64,29 @@ const Popconfirm = defineComponent({
disabled: false, disabled: false,
}), }),
slots: ['title', 'content', 'okText', 'icon', 'cancelText', 'cancelButton', 'okButton'], slots: ['title', 'content', 'okText', 'icon', 'cancelText', 'cancelButton', 'okButton'],
emits: ['update:visible', 'visibleChange'], // emits: ['update:open', 'visibleChange'],
setup(props: PopconfirmProps, { slots, emit, expose }) { setup(props: PopconfirmProps, { slots, emit, expose, attrs }) {
onMounted(() => { const rootRef = ref();
devWarning(
props.defaultVisible === undefined,
'Popconfirm',
`'defaultVisible' is deprecated, please use 'v-model:visible'`,
);
});
const tooltipRef = ref();
expose({ expose({
getPopupDomNode: () => { getPopupDomNode: () => {
return tooltipRef.value?.getPopupDomNode?.(); return rootRef.value?.getPopupDomNode?.();
}, },
}); });
const [visible, setVisible] = useMergedState(false, { const [open, setOpen] = useMergedState(false, {
value: toRef(props, 'visible'), value: toRef(props, 'open'),
defaultValue: props.defaultVisible,
}); });
const settingVisible = (value: boolean, e?: MouseEvent | KeyboardEvent) => { const settingOpen = (value: boolean, e?: MouseEvent | KeyboardEvent) => {
if (props.visible === undefined) { if (props.open === undefined) {
setVisible(value); setOpen(value);
} }
emit('update:visible', value); emit('update:open', value);
emit('visibleChange', value, e); emit('openChange', value, e);
}; };
const close = (e: MouseEvent) => { const close = (e: MouseEvent) => {
settingVisible(false, e); settingOpen(false, e);
}; };
const onConfirm = (e: MouseEvent) => { const onConfirm = (e: MouseEvent) => {
@ -109,37 +94,38 @@ const Popconfirm = defineComponent({
}; };
const onCancel = (e: MouseEvent) => { const onCancel = (e: MouseEvent) => {
settingVisible(false, e); settingOpen(false, e);
props.onCancel?.(e); props.onCancel?.(e);
}; };
const onKeyDown = (e: KeyboardEvent) => { const onKeyDown = (e: KeyboardEvent) => {
if (e.keyCode === KeyCode.ESC && visible) { if (e.keyCode === KeyCode.ESC && open) {
settingVisible(false, e); settingOpen(false, e);
} }
}; };
const onVisibleChange = (value: boolean) => { const onOpenChange = (value: boolean) => {
const { disabled } = props; const { disabled } = props;
if (disabled) { if (disabled) {
return; return;
} }
settingVisible(value); settingOpen(value);
}; };
const { prefixCls: prefixClsConfirm, getPrefixCls } = useConfigInject('popconfirm', props); const { prefixCls: prefixClsConfirm, getPrefixCls } = useConfigInject('popconfirm', props);
const rootPrefixCls = computed(() => getPrefixCls()); const rootPrefixCls = computed(() => getPrefixCls());
const popoverPrefixCls = computed(() => getPrefixCls('popover'));
const btnPrefixCls = computed(() => getPrefixCls('btn')); const btnPrefixCls = computed(() => getPrefixCls('btn'));
const [wrapSSR] = usePopconfirmStyle(prefixClsConfirm);
const [popconfirmLocale] = useLocaleReceiver('Popconfirm', defaultLocale.Popconfirm); const [popconfirmLocale] = useLocaleReceiver('Popconfirm', defaultLocale.Popconfirm);
const renderOverlay = () => { const renderOverlay = () => {
const { const {
okButtonProps, okButtonProps,
cancelButtonProps, cancelButtonProps,
title = slots.title?.(), title = slots.title?.(),
description = slots.description?.(),
cancelText = slots.cancel?.(), cancelText = slots.cancel?.(),
okText = slots.okText?.(), okText = slots.okText?.(),
okType, okType,
icon = slots.icon?.(), icon = slots.icon?.() || <ExclamationCircleFilled />,
showCancel = true, showCancel = true,
} = props; } = props;
const { cancelButton, okButton } = slots; const { cancelButton, okButton } = slots;
@ -155,12 +141,20 @@ const Popconfirm = defineComponent({
...okButtonProps, ...okButtonProps,
}; };
return ( return (
<div class={`${popoverPrefixCls.value}-inner-content`}> <div class={`${prefixClsConfirm.value}-inner-content`}>
<div class={`${popoverPrefixCls.value}-message`}> <div class={`${prefixClsConfirm.value}-message`}>
{icon || <ExclamationCircleFilled />} {icon && <span class={`${prefixClsConfirm.value}-message-icon`}>{icon}</span>}
<div class={`${popoverPrefixCls.value}-message-title`}>{title}</div> <div
class={[
`${prefixClsConfirm.value}-message-title`,
{ [`${prefixClsConfirm.value}-message-title-only`]: !!description },
]}
>
{title}
</div>
</div> </div>
<div class={`${popoverPrefixCls.value}-buttons`}> {description && <div class={`${prefixClsConfirm.value}-description`}>{description}</div>}
<div class={`${prefixClsConfirm.value}-buttons`}>
{showCancel ? ( {showCancel ? (
cancelButton ? ( cancelButton ? (
cancelButton(cancelProps) cancelButton(cancelProps)
@ -188,28 +182,31 @@ const Popconfirm = defineComponent({
}; };
return () => { return () => {
const { placement, overlayClassName, ...restProps } = props; const { placement, overlayClassName, trigger = 'click', ...restProps } = props;
const otherProps = omit(restProps, [ const otherProps = omit(restProps, [
'title', 'title',
'content', 'content',
'cancelText', 'cancelText',
'okText', 'okText',
'onUpdate:visible', 'onUpdate:open',
'onConfirm', 'onConfirm',
'onCancel', 'onCancel',
'prefixCls',
]); ]);
const overlayClassNames = classNames(prefixClsConfirm.value, overlayClassName); const overlayClassNames = classNames(prefixClsConfirm.value, overlayClassName);
return ( return wrapSSR(
<Tooltip <Popover
{...otherProps} {...otherProps}
prefixCls={popoverPrefixCls.value} {...attrs}
trigger={trigger}
placement={placement} placement={placement}
onVisibleChange={onVisibleChange} onOpenChange={onOpenChange}
visible={visible.value} open={open.value}
overlayClassName={overlayClassNames} overlayClassName={overlayClassNames}
transitionName={getTransitionName(rootPrefixCls.value, 'zoom-big', props.transitionName)} transitionName={getTransitionName(rootPrefixCls.value, 'zoom-big', props.transitionName)}
v-slots={{ title: renderOverlay }} v-slots={{ content: renderOverlay }}
ref={tooltipRef} ref={rootRef}
data-popover-inject
> >
{cloneVNodes( {cloneVNodes(
slots.default?.() || [], slots.default?.() || [],
@ -220,7 +217,7 @@ const Popconfirm = defineComponent({
}, },
false, false,
)} )}
</Tooltip> </Popover>,
); );
}; };
}, },

View File

@ -27,15 +27,16 @@ cover: https://gw.alipayobjects.com/zos/alicdn/fjMCD9xRq/Popconfirm.svg
| okType | 确认按钮类型 | string | primary | | | okType | 确认按钮类型 | string | primary | |
| showCancel | 是否显示取消按钮 | boolean | true | 3.0 | | showCancel | 是否显示取消按钮 | boolean | true | 3.0 |
| title | 确认框的描述 | string\|slot | 无 | | | title | 确认框的描述 | string\|slot | 无 | |
| visible (v-model) | 是否显示 | boolean | - | | | description | 确认内容的详细描述 | string\|slot | - | 4.0 |
| open (v-model) | 是否显示 | boolean | - | 4.0 |
### 事件 ### 事件
| 事件名称 | 说明 | 回调参数 | | 事件名称 | 说明 | 回调参数 | 版本 |
| ------------- | -------------- | ----------------- | | ---------- | -------------- | -------------- | ---- |
| cancel | 点击取消的回调 | function(e) | | cancel | 点击取消的回调 | function(e) | |
| confirm | 点击确认的回调 | function(e) | | confirm | 点击确认的回调 | function(e) | |
| visibleChange | 显示隐藏的回调 | function(visible) | | openChange | 显示隐藏的回调 | function(open) | 4.0 |
更多属性请参考 [Tooltip](/components/tooltip-cn/#API)。 更多属性请参考 [Tooltip](/components/tooltip-cn/#API)。

View File

@ -2,18 +2,19 @@ import type { ExtractPropTypes } from 'vue';
import { computed, defineComponent, ref } from 'vue'; import { computed, defineComponent, ref } from 'vue';
import Tooltip from '../tooltip'; import Tooltip from '../tooltip';
import abstractTooltipProps from '../tooltip/abstractTooltipProps'; import abstractTooltipProps from '../tooltip/abstractTooltipProps';
import PropTypes from '../_util/vue-types';
import { filterEmpty, initDefaultProps } from '../_util/props-util'; import { filterEmpty, initDefaultProps } from '../_util/props-util';
import { withInstall } from '../_util/type'; import { anyType, withInstall } from '../_util/type';
import useConfigInject from '../config-provider/hooks/useConfigInject'; import useConfigInject from '../config-provider/hooks/useConfigInject';
import omit from '../_util/omit'; import omit from '../_util/omit';
import { getTransitionName } from '../_util/transition'; import { getTransitionName } from '../_util/transition';
import { tooltipDefaultProps } from '../tooltip/Tooltip'; import { tooltipDefaultProps } from '../tooltip/Tooltip';
import useStyle from './style';
import classNames from '../_util/classNames';
export const popoverProps = () => ({ export const popoverProps = () => ({
...abstractTooltipProps(), ...abstractTooltipProps(),
content: PropTypes.any, content: anyType(),
title: PropTypes.any, title: anyType(),
}); });
export type PopoverProps = Partial<ExtractPropTypes<ReturnType<typeof popoverProps>>>; export type PopoverProps = Partial<ExtractPropTypes<ReturnType<typeof popoverProps>>>;
@ -21,6 +22,7 @@ export type PopoverProps = Partial<ExtractPropTypes<ReturnType<typeof popoverPro
const Popover = defineComponent({ const Popover = defineComponent({
compatConfig: { MODE: 3 }, compatConfig: { MODE: 3 },
name: 'APopover', name: 'APopover',
inheritAttrs: false,
props: initDefaultProps(popoverProps(), { props: initDefaultProps(popoverProps(), {
...tooltipDefaultProps(), ...tooltipDefaultProps(),
trigger: 'hover', trigger: 'hover',
@ -29,7 +31,7 @@ const Popover = defineComponent({
mouseEnterDelay: 0.1, mouseEnterDelay: 0.1,
mouseLeaveDelay: 0.1, mouseLeaveDelay: 0.1,
}), }),
setup(props, { expose, slots }) { setup(props, { expose, slots, attrs }) {
const tooltipRef = ref(); const tooltipRef = ref();
expose({ expose({
@ -38,13 +40,14 @@ const Popover = defineComponent({
}, },
}); });
const { prefixCls, configProvider } = useConfigInject('popover', props); const { prefixCls, configProvider } = useConfigInject('popover', props);
const [wrapSSR, hashId] = useStyle(prefixCls);
const rootPrefixCls = computed(() => configProvider.getPrefixCls()); const rootPrefixCls = computed(() => configProvider.getPrefixCls());
const getOverlay = () => { const getOverlay = () => {
const { title = filterEmpty(slots.title?.()), content = filterEmpty(slots.content?.()) } = const { title = filterEmpty(slots.title?.()), content = filterEmpty(slots.content?.()) } =
props; props;
const hasTitle = !!(Array.isArray(title) ? title.length : title); const hasTitle = !!(Array.isArray(title) ? title.length : title);
const hasContent = !!(Array.isArray(content) ? content.length : title); const hasContent = !!(Array.isArray(content) ? content.length : title);
if (!hasTitle && !hasContent) return undefined; if (!hasTitle && !hasContent) return null;
return ( return (
<> <>
{hasTitle && <div class={`${prefixCls.value}-title`}>{title}</div>} {hasTitle && <div class={`${prefixCls.value}-title`}>{title}</div>}
@ -53,14 +56,18 @@ const Popover = defineComponent({
); );
}; };
return () => { return () => {
return ( const overlayCls = classNames(props.overlayClassName, hashId.value);
return wrapSSR(
<Tooltip <Tooltip
{...omit(props, ['title', 'content'])} {...omit(props, ['title', 'content'])}
{...attrs}
prefixCls={prefixCls.value} prefixCls={prefixCls.value}
ref={tooltipRef} ref={tooltipRef}
overlayClassName={overlayCls}
v-slots={{ title: getOverlay, default: slots.default }} v-slots={{ title: getOverlay, default: slots.default }}
transitionName={getTransitionName(rootPrefixCls.value, 'zoom-big', props.transitionName)} transitionName={getTransitionName(rootPrefixCls.value, 'zoom-big', props.transitionName)}
/> data-popover-inject
/>,
); );
}; };
}, },