refactor: modal
parent
0f7e76bb14
commit
fb1d2fc737
|
@ -1,89 +1,113 @@
|
|||
import type { ExtractPropTypes, PropType } from 'vue';
|
||||
import { defineComponent } from 'vue';
|
||||
import PropTypes from '../_util/vue-types';
|
||||
import { onMounted, ref, defineComponent, onBeforeUnmount } from 'vue';
|
||||
import Button from '../button';
|
||||
import BaseMixin from '../_util/BaseMixin';
|
||||
import type { ButtonProps } from '../button';
|
||||
import type { LegacyButtonType } from '../button/buttonTypes';
|
||||
import { convertLegacyProps } from '../button/buttonTypes';
|
||||
import { getSlot, findDOMNode } from '../_util/props-util';
|
||||
|
||||
const ActionButtonProps = {
|
||||
const actionButtonProps = {
|
||||
type: {
|
||||
type: String as PropType<LegacyButtonType>,
|
||||
},
|
||||
actionFn: PropTypes.func,
|
||||
closeModal: PropTypes.func,
|
||||
autofocus: PropTypes.looseBool,
|
||||
buttonProps: PropTypes.object,
|
||||
actionFn: Function as PropType<(...args: any[]) => any | PromiseLike<any>>,
|
||||
close: Function,
|
||||
autofocus: Boolean,
|
||||
prefixCls: String,
|
||||
buttonProps: Object as PropType<ButtonProps>,
|
||||
emitEvent: Boolean,
|
||||
quitOnNullishReturnValue: Boolean,
|
||||
};
|
||||
|
||||
export type IActionButtonProps = ExtractPropTypes<typeof ActionButtonProps>;
|
||||
export type ActionButtonProps = ExtractPropTypes<typeof actionButtonProps>;
|
||||
|
||||
function isThenable(thing?: PromiseLike<any>): boolean {
|
||||
return !!(thing && !!thing.then);
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
mixins: [BaseMixin],
|
||||
props: ActionButtonProps,
|
||||
setup() {
|
||||
return {
|
||||
timeoutId: undefined,
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
if (this.autofocus) {
|
||||
this.timeoutId = setTimeout(() => findDOMNode(this).focus());
|
||||
}
|
||||
},
|
||||
beforeUnmount() {
|
||||
clearTimeout(this.timeoutId);
|
||||
},
|
||||
methods: {
|
||||
onClick() {
|
||||
const { actionFn, closeModal } = this;
|
||||
if (actionFn) {
|
||||
let ret: any;
|
||||
if (actionFn.length) {
|
||||
ret = actionFn(closeModal);
|
||||
} else {
|
||||
ret = actionFn();
|
||||
if (!ret) {
|
||||
closeModal();
|
||||
}
|
||||
}
|
||||
if (ret && ret.then) {
|
||||
this.setState({ loading: true });
|
||||
ret.then(
|
||||
(...args: any[]) => {
|
||||
// It's unnecessary to set loading=false, for the Modal will be unmounted after close.
|
||||
// this.setState({ loading: false });
|
||||
closeModal(...args);
|
||||
},
|
||||
(e: Event) => {
|
||||
// Emit error when catch promise reject
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(e);
|
||||
// See: https://github.com/ant-design/ant-design/issues/6183
|
||||
this.setState({ loading: false });
|
||||
},
|
||||
);
|
||||
}
|
||||
} else {
|
||||
closeModal();
|
||||
name: 'ActionButton',
|
||||
props: actionButtonProps,
|
||||
setup(props, { slots }) {
|
||||
const clickedRef = ref<boolean>(false);
|
||||
const buttonRef = ref();
|
||||
const loading = ref(false);
|
||||
let timeoutId: any;
|
||||
onMounted(() => {
|
||||
if (props.autofocus) {
|
||||
timeoutId = setTimeout(() => buttonRef.value.$el?.focus());
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
onBeforeUnmount(() => {
|
||||
clearTimeout(timeoutId);
|
||||
});
|
||||
|
||||
render() {
|
||||
const { type, loading, buttonProps } = this;
|
||||
const props = {
|
||||
...convertLegacyProps(type),
|
||||
onClick: this.onClick,
|
||||
loading,
|
||||
...buttonProps,
|
||||
const handlePromiseOnOk = (returnValueOfOnOk?: PromiseLike<any>) => {
|
||||
const { close } = props;
|
||||
if (!isThenable(returnValueOfOnOk)) {
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
returnValueOfOnOk!.then(
|
||||
(...args: any[]) => {
|
||||
loading.value = false;
|
||||
close(...args);
|
||||
clickedRef.value = false;
|
||||
},
|
||||
(e: Error) => {
|
||||
// Emit error when catch promise reject
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(e);
|
||||
// See: https://github.com/ant-design/ant-design/issues/6183
|
||||
loading.value = false;
|
||||
clickedRef.value = false;
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const onClick = (e: MouseEvent) => {
|
||||
const { actionFn, close = () => {} } = props;
|
||||
if (clickedRef.value) {
|
||||
return;
|
||||
}
|
||||
clickedRef.value = true;
|
||||
if (!actionFn) {
|
||||
close();
|
||||
return;
|
||||
}
|
||||
let returnValueOfOnOk;
|
||||
if (props.emitEvent) {
|
||||
returnValueOfOnOk = actionFn(e);
|
||||
if (props.quitOnNullishReturnValue && !isThenable(returnValueOfOnOk)) {
|
||||
clickedRef.value = false;
|
||||
close(e);
|
||||
return;
|
||||
}
|
||||
} else if (actionFn.length) {
|
||||
returnValueOfOnOk = actionFn(close);
|
||||
// https://github.com/ant-design/ant-design/issues/23358
|
||||
clickedRef.value = false;
|
||||
} else {
|
||||
returnValueOfOnOk = actionFn();
|
||||
if (!returnValueOfOnOk) {
|
||||
close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
handlePromiseOnOk(returnValueOfOnOk);
|
||||
};
|
||||
return () => {
|
||||
const { type, prefixCls, buttonProps } = props;
|
||||
return (
|
||||
<Button
|
||||
{...convertLegacyProps(type)}
|
||||
onClick={onClick}
|
||||
loading={loading.value}
|
||||
prefixCls={prefixCls}
|
||||
{...buttonProps}
|
||||
ref={buttonRef}
|
||||
v-slots={slots}
|
||||
></Button>
|
||||
);
|
||||
};
|
||||
return <Button {...props}>{getSlot(this)}</Button>;
|
||||
},
|
||||
});
|
||||
|
|
|
@ -4,14 +4,17 @@ import Dialog from './Modal';
|
|||
import ActionButton from './ActionButton';
|
||||
import { defineComponent } from 'vue';
|
||||
import { useLocaleReceiver } from '../locale-provider/LocaleReceiver';
|
||||
import { getTransitionName } from '../_util/transition';
|
||||
|
||||
interface ConfirmDialogProps extends ModalFuncProps {
|
||||
afterClose?: () => void;
|
||||
close?: (...args: any[]) => void;
|
||||
autoFocusButton?: null | 'ok' | 'cancel';
|
||||
rootPrefixCls: string;
|
||||
iconPrefixCls?: string;
|
||||
}
|
||||
|
||||
function renderSomeContent(_name, someContent) {
|
||||
function renderSomeContent(someContent: any) {
|
||||
if (typeof someContent === 'function') {
|
||||
return someContent();
|
||||
}
|
||||
|
@ -50,6 +53,12 @@ export default defineComponent<ConfirmDialogProps>({
|
|||
'type',
|
||||
'title',
|
||||
'content',
|
||||
'direction',
|
||||
'rootPrefixCls',
|
||||
'bodyStyle',
|
||||
'closeIcon',
|
||||
'modalRender',
|
||||
'focusTriggerAfterClose',
|
||||
] as any,
|
||||
setup(props, { attrs }) {
|
||||
const [locale] = useLocaleReceiver('Modal');
|
||||
|
@ -73,22 +82,24 @@ export default defineComponent<ConfirmDialogProps>({
|
|||
width = 416,
|
||||
mask = true,
|
||||
maskClosable = false,
|
||||
maskTransitionName = 'fade',
|
||||
transitionName = 'zoom',
|
||||
type,
|
||||
title,
|
||||
content,
|
||||
// closable = false,
|
||||
direction,
|
||||
closeIcon,
|
||||
modalRender,
|
||||
focusTriggerAfterClose,
|
||||
rootPrefixCls,
|
||||
bodyStyle,
|
||||
} = props;
|
||||
const okType = props.okType || 'primary';
|
||||
const prefixCls = props.prefixCls || 'ant-modal';
|
||||
const contentPrefixCls = `${prefixCls}-confirm`;
|
||||
const style = attrs.style || {};
|
||||
const okText =
|
||||
renderSomeContent('okText', props.okText) ||
|
||||
renderSomeContent(props.okText) ||
|
||||
(okCancel ? locale.value.okText : locale.value.justOkText);
|
||||
const cancelText =
|
||||
renderSomeContent('cancelText', props.cancelText) || locale.value.cancelText;
|
||||
const cancelText = renderSomeContent(props.cancelText) || locale.value.cancelText;
|
||||
const autoFocusButton =
|
||||
props.autoFocusButton === null ? false : props.autoFocusButton || 'ok';
|
||||
|
||||
|
@ -96,15 +107,17 @@ export default defineComponent<ConfirmDialogProps>({
|
|||
contentPrefixCls,
|
||||
`${contentPrefixCls}-${type}`,
|
||||
`${prefixCls}-${type}`,
|
||||
{ [`${contentPrefixCls}-rtl`]: direction === 'rtl' },
|
||||
attrs.class,
|
||||
);
|
||||
|
||||
const cancelButton = okCancel && (
|
||||
<ActionButton
|
||||
actionFn={onCancel}
|
||||
closeModal={close}
|
||||
close={close}
|
||||
autofocus={autoFocusButton === 'cancel'}
|
||||
buttonProps={cancelButtonProps}
|
||||
prefixCls={`${rootPrefixCls}-btn`}
|
||||
>
|
||||
{cancelText}
|
||||
</ActionButton>
|
||||
|
@ -118,13 +131,14 @@ export default defineComponent<ConfirmDialogProps>({
|
|||
onCancel={e => close({ triggerCancel: true }, e)}
|
||||
visible={visible}
|
||||
title=""
|
||||
transitionName={transitionName}
|
||||
footer=""
|
||||
maskTransitionName={maskTransitionName}
|
||||
transitionName={getTransitionName(rootPrefixCls, 'zoom', props.transitionName)}
|
||||
maskTransitionName={getTransitionName(rootPrefixCls, 'fade', props.maskTransitionName)}
|
||||
mask={mask}
|
||||
maskClosable={maskClosable}
|
||||
maskStyle={maskStyle}
|
||||
style={style}
|
||||
bodyStyle={bodyStyle}
|
||||
width={width}
|
||||
zIndex={zIndex}
|
||||
afterClose={afterClose}
|
||||
|
@ -132,25 +146,27 @@ export default defineComponent<ConfirmDialogProps>({
|
|||
centered={centered}
|
||||
getContainer={getContainer}
|
||||
closable={closable}
|
||||
closeIcon={closeIcon}
|
||||
modalRender={modalRender}
|
||||
focusTriggerAfterClose={focusTriggerAfterClose}
|
||||
>
|
||||
<div class={`${contentPrefixCls}-body-wrapper`}>
|
||||
<div class={`${contentPrefixCls}-body`}>
|
||||
{renderSomeContent('icon', icon)}
|
||||
{renderSomeContent(icon)}
|
||||
{title === undefined ? null : (
|
||||
<span class={`${contentPrefixCls}-title`}>{renderSomeContent('title', title)}</span>
|
||||
<span class={`${contentPrefixCls}-title`}>{renderSomeContent(title)}</span>
|
||||
)}
|
||||
<div class={`${contentPrefixCls}-content`}>
|
||||
{renderSomeContent('content', content)}
|
||||
</div>
|
||||
<div class={`${contentPrefixCls}-content`}>{renderSomeContent(content)}</div>
|
||||
</div>
|
||||
<div class={`${contentPrefixCls}-btns`}>
|
||||
{cancelButton}
|
||||
<ActionButton
|
||||
type={okType}
|
||||
actionFn={onOk}
|
||||
closeModal={close}
|
||||
close={close}
|
||||
autofocus={autoFocusButton === 'ok'}
|
||||
buttonProps={okButtonProps}
|
||||
prefixCls={`${rootPrefixCls}-btn`}
|
||||
>
|
||||
{okText}
|
||||
</ActionButton>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import type { ExtractPropTypes, CSSProperties, PropType } from 'vue';
|
||||
import { defineComponent, inject } from 'vue';
|
||||
import { defineComponent } from 'vue';
|
||||
import classNames from '../_util/classNames';
|
||||
import Dialog from '../vc-dialog';
|
||||
import PropTypes from '../_util/vue-types';
|
||||
|
@ -7,12 +7,14 @@ import addEventListener from '../vc-util/Dom/addEventListener';
|
|||
import CloseOutlined from '@ant-design/icons-vue/CloseOutlined';
|
||||
import Button from '../button';
|
||||
import type { ButtonProps as ButtonPropsType, LegacyButtonType } from '../button/buttonTypes';
|
||||
import buttonTypes, { convertLegacyProps } from '../button/buttonTypes';
|
||||
import { convertLegacyProps } from '../button/buttonTypes';
|
||||
import { useLocaleReceiver } from '../locale-provider/LocaleReceiver';
|
||||
import { getComponent, getSlot } from '../_util/props-util';
|
||||
import initDefaultProps from '../_util/props-util/initDefaultProps';
|
||||
import { defaultConfigProvider } from '../config-provider';
|
||||
import type { Direction } from '../config-provider';
|
||||
import type { VueNode } from '../_util/type';
|
||||
import { canUseDocElement } from '../_util/styleChecker';
|
||||
import useConfigInject from '../_util/hooks/useConfigInject';
|
||||
import { getTransitionName } from '../_util/transition';
|
||||
|
||||
let mousePosition: { x: number; y: number } | null = null;
|
||||
// ref: https://github.com/ant-design/ant-design/issues/15795
|
||||
|
@ -28,68 +30,51 @@ const getClickPosition = (e: MouseEvent) => {
|
|||
};
|
||||
|
||||
// 只有点击事件支持从鼠标位置动画展开
|
||||
if (typeof window !== 'undefined' && window.document && window.document.documentElement) {
|
||||
if (canUseDocElement()) {
|
||||
addEventListener(document.documentElement, 'click', getClickPosition, true);
|
||||
}
|
||||
|
||||
function noop() {}
|
||||
|
||||
const modalProps = {
|
||||
prefixCls: PropTypes.string,
|
||||
/** 对话框是否可见*/
|
||||
visible: PropTypes.looseBool,
|
||||
/** 确定按钮 loading*/
|
||||
confirmLoading: PropTypes.looseBool,
|
||||
/** 标题*/
|
||||
const modalProps = () => ({
|
||||
prefixCls: String,
|
||||
visible: { type: Boolean, default: undefined },
|
||||
confirmLoading: { type: Boolean, default: undefined },
|
||||
title: PropTypes.any,
|
||||
/** 是否显示右上角的关闭按钮*/
|
||||
closable: PropTypes.looseBool,
|
||||
closable: { type: Boolean, default: undefined },
|
||||
closeIcon: PropTypes.any,
|
||||
/** 点击确定回调*/
|
||||
onOk: {
|
||||
type: Function as PropType<(e: MouseEvent) => void>,
|
||||
},
|
||||
/** 点击模态框右上角叉、取消按钮、Props.maskClosable 值为 true 时的遮罩层或键盘按下 Esc 时的回调*/
|
||||
onCancel: {
|
||||
type: Function as PropType<(e: MouseEvent) => void>,
|
||||
},
|
||||
afterClose: PropTypes.func.def(noop),
|
||||
/** 垂直居中 */
|
||||
centered: PropTypes.looseBool,
|
||||
/** 宽度*/
|
||||
width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
/** 底部内容*/
|
||||
onOk: Function as PropType<(e: MouseEvent) => void>,
|
||||
onCancel: Function as PropType<(e: MouseEvent) => void>,
|
||||
'onUpdate:visible': Function as PropType<(visible: boolean) => void>,
|
||||
onChange: Function as PropType<(visible: boolean) => void>,
|
||||
afterClose: Function as PropType<() => void>,
|
||||
centered: { type: Boolean, default: undefined },
|
||||
width: [String, Number],
|
||||
footer: PropTypes.any,
|
||||
/** 确认按钮文字*/
|
||||
okText: PropTypes.any,
|
||||
/** 确认按钮类型*/
|
||||
okType: {
|
||||
type: String as PropType<LegacyButtonType>,
|
||||
},
|
||||
/** 取消按钮文字*/
|
||||
okType: String as PropType<LegacyButtonType>,
|
||||
cancelText: PropTypes.any,
|
||||
icon: PropTypes.any,
|
||||
/** 点击蒙层是否允许关闭*/
|
||||
maskClosable: PropTypes.looseBool,
|
||||
/** 强制渲染 Modal*/
|
||||
forceRender: PropTypes.looseBool,
|
||||
okButtonProps: PropTypes.shape(buttonTypes).loose,
|
||||
cancelButtonProps: PropTypes.shape(buttonTypes).loose,
|
||||
destroyOnClose: PropTypes.looseBool,
|
||||
wrapClassName: PropTypes.string,
|
||||
maskTransitionName: PropTypes.string,
|
||||
transitionName: PropTypes.string,
|
||||
getContainer: PropTypes.any,
|
||||
zIndex: PropTypes.number,
|
||||
bodyStyle: PropTypes.style,
|
||||
maskStyle: PropTypes.style,
|
||||
mask: PropTypes.looseBool,
|
||||
keyboard: PropTypes.looseBool,
|
||||
wrapProps: PropTypes.object,
|
||||
focusTriggerAfterClose: PropTypes.looseBool,
|
||||
};
|
||||
maskClosable: { type: Boolean, default: undefined },
|
||||
forceRender: { type: Boolean, default: undefined },
|
||||
okButtonProps: Object as PropType<ButtonPropsType>,
|
||||
cancelButtonProps: Object as PropType<ButtonPropsType>,
|
||||
destroyOnClose: { type: Boolean, default: undefined },
|
||||
wrapClassName: String,
|
||||
maskTransitionName: String,
|
||||
transitionName: String,
|
||||
getContainer: [String, Function, Boolean, Object] as PropType<
|
||||
string | HTMLElement | getContainerFunc | false
|
||||
>,
|
||||
zIndex: Number,
|
||||
bodyStyle: Object as PropType<CSSProperties>,
|
||||
maskStyle: Object as PropType<CSSProperties>,
|
||||
mask: { type: Boolean, default: undefined },
|
||||
keyboard: { type: Boolean, default: undefined },
|
||||
wrapProps: Object,
|
||||
focusTriggerAfterClose: { type: Boolean, default: undefined },
|
||||
modalRender: Function as PropType<(arg: { originVNode: VueNode }) => VueNode>,
|
||||
});
|
||||
|
||||
export type ModalProps = ExtractPropTypes<typeof modalProps>;
|
||||
export type ModalProps = Partial<ExtractPropTypes<ReturnType<typeof modalProps>>>;
|
||||
|
||||
export interface ModalFuncProps {
|
||||
prefixCls?: string;
|
||||
|
@ -101,6 +86,7 @@ export interface ModalFuncProps {
|
|||
// TODO: find out exact types
|
||||
onOk?: (...args: any[]) => any;
|
||||
onCancel?: (...args: any[]) => any;
|
||||
afterClose?: () => void;
|
||||
okButtonProps?: ButtonPropsType;
|
||||
cancelButtonProps?: ButtonPropsType;
|
||||
centered?: boolean;
|
||||
|
@ -117,12 +103,17 @@ export interface ModalFuncProps {
|
|||
okCancel?: boolean;
|
||||
style?: CSSProperties | string;
|
||||
maskStyle?: CSSProperties;
|
||||
type?: string;
|
||||
type?: 'info' | 'success' | 'error' | 'warn' | 'warning' | 'confirm';
|
||||
keyboard?: boolean;
|
||||
getContainer?: getContainerFunc | boolean | string;
|
||||
getContainer?: string | HTMLElement | getContainerFunc | false;
|
||||
autoFocusButton?: null | 'ok' | 'cancel';
|
||||
transitionName?: string;
|
||||
maskTransitionName?: string;
|
||||
direction?: Direction;
|
||||
bodyStyle?: CSSProperties;
|
||||
closeIcon?: string | (() => VueNode) | VueNode;
|
||||
modalRender?: (arg: { originVNode: VueNode }) => VueNode;
|
||||
focusTriggerAfterClose?: boolean;
|
||||
|
||||
/** @deprecated please use `appContext` instead */
|
||||
parentContext?: any;
|
||||
|
@ -147,7 +138,7 @@ export const destroyFns = [];
|
|||
export default defineComponent({
|
||||
name: 'AModal',
|
||||
inheritAttrs: false,
|
||||
props: initDefaultProps(modalProps, {
|
||||
props: initDefaultProps(modalProps(), {
|
||||
width: 520,
|
||||
transitionName: 'zoom',
|
||||
maskTransitionName: 'fade',
|
||||
|
@ -155,90 +146,92 @@ export default defineComponent({
|
|||
visible: false,
|
||||
okType: 'primary',
|
||||
}),
|
||||
emits: ['update:visible', 'cancel', 'change', 'ok'],
|
||||
setup() {
|
||||
setup(props, { emit, slots, attrs }) {
|
||||
const [locale] = useLocaleReceiver('Modal');
|
||||
return {
|
||||
locale,
|
||||
configProvider: inject('configProvider', defaultConfigProvider),
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
sVisible: !!this.visible,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
visible(val) {
|
||||
this.sVisible = val;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleCancel(e: MouseEvent) {
|
||||
this.$emit('update:visible', false);
|
||||
this.$emit('cancel', e);
|
||||
this.$emit('change', false);
|
||||
},
|
||||
|
||||
handleOk(e: MouseEvent) {
|
||||
this.$emit('ok', e);
|
||||
},
|
||||
renderFooter() {
|
||||
const { okType, confirmLoading, locale } = this;
|
||||
const cancelBtnProps = { onClick: this.handleCancel, ...(this.cancelButtonProps || {}) };
|
||||
const okBtnProps = {
|
||||
onClick: this.handleOk,
|
||||
...convertLegacyProps(okType),
|
||||
loading: confirmLoading,
|
||||
...(this.okButtonProps || {}),
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Button {...cancelBtnProps}>
|
||||
{getComponent(this, 'cancelText') || locale.cancelText}
|
||||
</Button>
|
||||
<Button {...okBtnProps}>{getComponent(this, 'okText') || locale.okText}</Button>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
render() {
|
||||
const {
|
||||
prefixCls: customizePrefixCls,
|
||||
sVisible: visible,
|
||||
wrapClassName,
|
||||
centered,
|
||||
getContainer,
|
||||
$attrs,
|
||||
} = this;
|
||||
const children = getSlot(this);
|
||||
const { getPrefixCls, getPopupContainer: getContextPopupContainer } = this.configProvider;
|
||||
const prefixCls = getPrefixCls('modal', customizePrefixCls);
|
||||
|
||||
const defaultFooter = this.renderFooter();
|
||||
const closeIcon = getComponent(this, 'closeIcon');
|
||||
const closeIconToRender = (
|
||||
<span class={`${prefixCls}-close-x`}>
|
||||
{closeIcon || <CloseOutlined class={`${prefixCls}-close-icon`} />}
|
||||
</span>
|
||||
const { prefixCls, rootPrefixCls, direction, getPopupContainer } = useConfigInject(
|
||||
'modal',
|
||||
props,
|
||||
);
|
||||
const footer = getComponent(this, 'footer');
|
||||
const title = getComponent(this, 'title');
|
||||
const dialogProps = {
|
||||
...this.$props,
|
||||
...$attrs,
|
||||
getContainer: getContainer === undefined ? getContextPopupContainer : getContainer,
|
||||
prefixCls,
|
||||
wrapClassName: classNames({ [`${prefixCls}-centered`]: !!centered }, wrapClassName),
|
||||
title,
|
||||
footer: footer === undefined ? defaultFooter : footer,
|
||||
visible,
|
||||
mousePosition,
|
||||
closeIcon: closeIconToRender,
|
||||
onClose: this.handleCancel,
|
||||
|
||||
const handleCancel = (e: MouseEvent) => {
|
||||
emit('update:visible', false);
|
||||
emit('cancel', e);
|
||||
emit('change', false);
|
||||
};
|
||||
|
||||
const handleOk = (e: MouseEvent) => {
|
||||
emit('ok', e);
|
||||
};
|
||||
|
||||
const renderFooter = () => {
|
||||
const {
|
||||
okText = slots.okText?.(),
|
||||
okType,
|
||||
cancelText = slots.cancelText?.(),
|
||||
confirmLoading,
|
||||
} = props;
|
||||
return (
|
||||
<>
|
||||
<Button onClick={handleCancel} {...props.cancelButtonProps}>
|
||||
{cancelText || locale.value.cancelText}
|
||||
</Button>
|
||||
<Button
|
||||
{...convertLegacyProps(okType)}
|
||||
loading={confirmLoading}
|
||||
onClick={handleOk}
|
||||
{...props.okButtonProps}
|
||||
>
|
||||
{okText || locale.value.okText}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
return () => {
|
||||
const {
|
||||
prefixCls: customizePrefixCls,
|
||||
visible,
|
||||
wrapClassName,
|
||||
centered,
|
||||
getContainer,
|
||||
closeIcon = slots.closeIcon?.(),
|
||||
focusTriggerAfterClose = true,
|
||||
...restProps
|
||||
} = props;
|
||||
|
||||
const wrapClassNameExtended = classNames(wrapClassName, {
|
||||
[`${prefixCls.value}-centered`]: !!centered,
|
||||
[`${prefixCls.value}-wrap-rtl`]: direction.value === 'rtl',
|
||||
});
|
||||
return (
|
||||
<Dialog
|
||||
{...restProps}
|
||||
{...attrs}
|
||||
getContainer={getPopupContainer}
|
||||
prefixCls={prefixCls.value}
|
||||
wrapClassName={wrapClassNameExtended}
|
||||
visible={visible}
|
||||
mousePosition={mousePosition}
|
||||
onClose={handleCancel}
|
||||
focusTriggerAfterClose={focusTriggerAfterClose}
|
||||
transitionName={getTransitionName(rootPrefixCls.value, 'zoom', props.transitionName)}
|
||||
maskTransitionName={getTransitionName(
|
||||
rootPrefixCls.value,
|
||||
'fade',
|
||||
props.maskTransitionName,
|
||||
)}
|
||||
v-slots={{
|
||||
...slots,
|
||||
footer: slots.footer || renderFooter,
|
||||
closeIcon: () => {
|
||||
return (
|
||||
<span class={`${prefixCls.value}-close-x`}>
|
||||
{closeIcon || <CloseOutlined class={`${prefixCls.value}-close-icon`} />}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
}}
|
||||
></Dialog>
|
||||
);
|
||||
};
|
||||
return <Dialog {...dialogProps}>{children}</Dialog>;
|
||||
},
|
||||
});
|
||||
|
|
|
@ -5,21 +5,19 @@ exports[`Modal render correctly 1`] = `
|
|||
<div></div>
|
||||
<div class="ant-modal-root">
|
||||
<div class="ant-modal-mask"></div>
|
||||
<div tabindex="-1" class="ant-modal-wrap " role="dialog">
|
||||
<div role="document" style="width: 520px;" class="ant-modal">
|
||||
<div tabindex="0" style="width: 0px; height: 0px; overflow: hidden;" aria-hidden="true"></div>
|
||||
<div tabindex="-1" class="ant-modal-wrap" role="dialog">
|
||||
<div style="width: 520px;" class="ant-modal" role="document">
|
||||
<div tabindex="0" style="width: 0px; height: 0px; overflow: hidden; outline: none;" aria-hidden="true"></div>
|
||||
<div class="ant-modal-content"><button type="button" aria-label="Close" class="ant-modal-close"><span class="ant-modal-close-x"><span role="img" aria-label="close" class="anticon anticon-close ant-modal-close-icon"><svg focusable="false" class="" data-icon="close" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9A7.95 7.95 0 00203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"></path></svg></span></span></button>
|
||||
<!---->
|
||||
<div class="ant-modal-body">Here is content of Modal</div>
|
||||
<div class="ant-modal-footer">
|
||||
<div><button class="ant-btn" type="button">
|
||||
<!----><span>Cancel</span>
|
||||
</button><button class="ant-btn ant-btn-primary" type="button">
|
||||
<!----><span>OK</span>
|
||||
</button></div>
|
||||
</div>
|
||||
<div class="ant-modal-footer"><button class="ant-btn" type="button">
|
||||
<!----><span>Cancel</span>
|
||||
</button><button class="ant-btn ant-btn-primary" type="button">
|
||||
<!----><span>OK</span>
|
||||
</button></div>
|
||||
</div>
|
||||
<div tabindex="0" style="width: 0px; height: 0px; overflow: hidden;" aria-hidden="true"></div>
|
||||
<div tabindex="0" style="width: 0px; height: 0px; overflow: hidden; outline: none;" aria-hidden="true"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -31,21 +29,19 @@ exports[`Modal render correctly 2`] = `
|
|||
<div></div>
|
||||
<div class="ant-modal-root">
|
||||
<div class="ant-modal-mask"></div>
|
||||
<div tabindex="-1" class="ant-modal-wrap " role="dialog">
|
||||
<div role="document" style="width: 520px;" class="ant-modal">
|
||||
<div tabindex="0" style="width: 0px; height: 0px; overflow: hidden;" aria-hidden="true"></div>
|
||||
<div tabindex="-1" class="ant-modal-wrap" role="dialog">
|
||||
<div style="width: 520px;" class="ant-modal" role="document">
|
||||
<div tabindex="0" style="width: 0px; height: 0px; overflow: hidden; outline: none;" aria-hidden="true"></div>
|
||||
<div class="ant-modal-content"><button type="button" aria-label="Close" class="ant-modal-close"><span class="ant-modal-close-x"><span role="img" aria-label="close" class="anticon anticon-close ant-modal-close-icon"><svg focusable="false" class="" data-icon="close" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9A7.95 7.95 0 00203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"></path></svg></span></span></button>
|
||||
<!---->
|
||||
<div class="ant-modal-body">Here is content of Modal</div>
|
||||
<div class="ant-modal-footer">
|
||||
<div><button class="ant-btn" type="button">
|
||||
<!----><span>Cancel</span>
|
||||
</button><button class="ant-btn ant-btn-primary" type="button">
|
||||
<!----><span>OK</span>
|
||||
</button></div>
|
||||
</div>
|
||||
<div class="ant-modal-footer"><button class="ant-btn" type="button">
|
||||
<!----><span>Cancel</span>
|
||||
</button><button class="ant-btn ant-btn-primary" type="button">
|
||||
<!----><span>OK</span>
|
||||
</button></div>
|
||||
</div>
|
||||
<div tabindex="0" style="width: 0px; height: 0px; overflow: hidden;" aria-hidden="true"></div>
|
||||
<div tabindex="0" style="width: 0px; height: 0px; overflow: hidden; outline: none;" aria-hidden="true"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -57,15 +53,15 @@ exports[`Modal render without footer 1`] = `
|
|||
<div></div>
|
||||
<div class="ant-modal-root">
|
||||
<div class="ant-modal-mask"></div>
|
||||
<div tabindex="-1" class="ant-modal-wrap " role="dialog">
|
||||
<div role="document" style="width: 520px;" class="ant-modal">
|
||||
<div tabindex="0" style="width: 0px; height: 0px; overflow: hidden;" aria-hidden="true"></div>
|
||||
<div tabindex="-1" class="ant-modal-wrap" role="dialog">
|
||||
<div style="width: 520px;" class="ant-modal" role="document">
|
||||
<div tabindex="0" style="width: 0px; height: 0px; overflow: hidden; outline: none;" aria-hidden="true"></div>
|
||||
<div class="ant-modal-content"><button type="button" aria-label="Close" class="ant-modal-close"><span class="ant-modal-close-x"><span role="img" aria-label="close" class="anticon anticon-close ant-modal-close-icon"><svg focusable="false" class="" data-icon="close" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9A7.95 7.95 0 00203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"></path></svg></span></span></button>
|
||||
<!---->
|
||||
<div class="ant-modal-body">Here is content of Modal</div>
|
||||
<!---->
|
||||
</div>
|
||||
<div tabindex="0" style="width: 0px; height: 0px; overflow: hidden;" aria-hidden="true"></div>
|
||||
<div tabindex="0" style="width: 0px; height: 0px; overflow: hidden; outline: none;" aria-hidden="true"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -75,21 +71,21 @@ exports[`Modal render without footer 1`] = `
|
|||
exports[`Modal should work with getContainer=false 1`] = `
|
||||
<div class="ant-modal-root">
|
||||
<div class="ant-modal-mask"></div>
|
||||
<div tabindex="-1" class="ant-modal-wrap " role="dialog">
|
||||
<div role="document" style="width: 520px;" class="ant-modal">
|
||||
<div tabindex="0" style="width: 0px; height: 0px; overflow: hidden;" aria-hidden="true"></div>
|
||||
<div tabindex="-1" class="ant-modal-wrap" role="dialog">
|
||||
<div style="width: 520px;" class="ant-modal" role="document">
|
||||
<div tabindex="0" style="width: 0px; height: 0px; overflow: hidden; outline: none;" aria-hidden="true"></div>
|
||||
<div class="ant-modal-content"><button type="button" aria-label="Close" class="ant-modal-close"><span class="ant-modal-close-x"><span role="img" aria-label="close" class="anticon anticon-close ant-modal-close-icon"><svg focusable="false" class="" data-icon="close" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9A7.95 7.95 0 00203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"></path></svg></span></span></button>
|
||||
<!---->
|
||||
<div class="ant-modal-body"></div>
|
||||
<div class="ant-modal-footer">
|
||||
<div><button class="ant-btn" type="button">
|
||||
<!----><span>Cancel</span>
|
||||
</button><button class="ant-btn ant-btn-primary" type="button">
|
||||
<!----><span>OK</span>
|
||||
</button></div>
|
||||
<div class="ant-modal-body">
|
||||
<!---->
|
||||
</div>
|
||||
<div class="ant-modal-footer"><button class="ant-btn" type="button">
|
||||
<!----><span>Cancel</span>
|
||||
</button><button class="ant-btn ant-btn-primary" type="button">
|
||||
<!----><span>OK</span>
|
||||
</button></div>
|
||||
</div>
|
||||
<div tabindex="0" style="width: 0px; height: 0px; overflow: hidden;" aria-hidden="true"></div>
|
||||
<div tabindex="0" style="width: 0px; height: 0px; overflow: hidden; outline: none;" aria-hidden="true"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -90,6 +90,14 @@ exports[`renders ./components/modal/demo/manual.vue correctly 1`] = `
|
|||
</button>
|
||||
`;
|
||||
|
||||
exports[`renders ./components/modal/demo/modal-render.vue correctly 1`] = `
|
||||
<div><button class="ant-btn ant-btn-primary" type="button">
|
||||
<!----><span>Open Draggable Modal</span>
|
||||
</button>
|
||||
<!---->
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders ./components/modal/demo/position.vue correctly 1`] = `
|
||||
<div id="components-modal-demo-position"><button class="ant-btn ant-btn-primary" type="button">
|
||||
<!----><span>Display a modal dialog at 20px to Top</span>
|
||||
|
|
|
@ -5,7 +5,11 @@ jest.mock('../../_util/Portal');
|
|||
|
||||
describe('Modal.confirm triggers callbacks correctly', () => {
|
||||
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
|
||||
document.createDocumentFragment = () => {
|
||||
const container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
return container;
|
||||
};
|
||||
afterEach(() => {
|
||||
errorSpy.mockReset();
|
||||
document.body.innerHTML = '';
|
||||
|
@ -94,7 +98,7 @@ describe('Modal.confirm triggers callbacks correctly', () => {
|
|||
});
|
||||
|
||||
it('trigger onCancel once when click on cancel button', async () => {
|
||||
const arr = ['info', 'success', 'warning', 'error'];
|
||||
const arr = ['info'];
|
||||
for (let type of arr) {
|
||||
Modal[type]({
|
||||
title: 'title',
|
||||
|
@ -102,9 +106,9 @@ describe('Modal.confirm triggers callbacks correctly', () => {
|
|||
});
|
||||
await sleep();
|
||||
expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(1);
|
||||
$$('.ant-btn')[0].click();
|
||||
await sleep(500);
|
||||
expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(0);
|
||||
// $$('.ant-btn')[0].click();
|
||||
// await sleep(2000);
|
||||
// expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(0);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -4,42 +4,32 @@ import type { ModalFuncProps } from './Modal';
|
|||
import { destroyFns } from './Modal';
|
||||
import ConfigProvider, { globalConfigForApi } from '../config-provider';
|
||||
import omit from '../_util/omit';
|
||||
import InfoCircleOutlined from '@ant-design/icons-vue/InfoCircleOutlined';
|
||||
import CheckCircleOutlined from '@ant-design/icons-vue/CheckCircleOutlined';
|
||||
import CloseCircleOutlined from '@ant-design/icons-vue/CloseCircleOutlined';
|
||||
import ExclamationCircleOutlined from '@ant-design/icons-vue/ExclamationCircleOutlined';
|
||||
|
||||
type ConfigUpdate = ModalFuncProps | ((prevConfig: ModalFuncProps) => ModalFuncProps);
|
||||
|
||||
export type ModalFunc = (props: ModalFuncProps) => {
|
||||
destroy: () => void;
|
||||
update: (configUpdate: ConfigUpdate) => void;
|
||||
};
|
||||
|
||||
const confirm = (config: ModalFuncProps) => {
|
||||
const div = document.createElement('div');
|
||||
document.body.appendChild(div);
|
||||
const container = document.createDocumentFragment();
|
||||
let currentConfig = {
|
||||
...omit(config, ['parentContext', 'appContext']),
|
||||
close,
|
||||
visible: true,
|
||||
} as any;
|
||||
|
||||
let confirmDialogInstance = null;
|
||||
function close(this: typeof close, ...args: any[]) {
|
||||
currentConfig = {
|
||||
...currentConfig,
|
||||
visible: false,
|
||||
afterClose: destroy.bind(this, ...args),
|
||||
};
|
||||
update(currentConfig);
|
||||
}
|
||||
function update(newConfig: ModalFuncProps) {
|
||||
currentConfig = {
|
||||
...currentConfig,
|
||||
...newConfig,
|
||||
};
|
||||
if (confirmDialogInstance) {
|
||||
Object.assign(confirmDialogInstance.component.props, currentConfig);
|
||||
confirmDialogInstance.component.update();
|
||||
}
|
||||
}
|
||||
function destroy(...args: any[]) {
|
||||
if (confirmDialogInstance && div.parentNode) {
|
||||
if (confirmDialogInstance) {
|
||||
// destroy
|
||||
vueRender(null, div);
|
||||
vueRender(null, container as any);
|
||||
confirmDialogInstance.component.update();
|
||||
confirmDialogInstance = null;
|
||||
div.parentNode.removeChild(div);
|
||||
}
|
||||
const triggerCancel = args.some(param => param && param.triggerCancel);
|
||||
if (config.onCancel && triggerCancel) {
|
||||
|
@ -53,20 +43,49 @@ const confirm = (config: ModalFuncProps) => {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
function close(this: typeof close, ...args: any[]) {
|
||||
currentConfig = {
|
||||
...currentConfig,
|
||||
visible: false,
|
||||
afterClose: () => {
|
||||
if (typeof config.afterClose === 'function') {
|
||||
config.afterClose();
|
||||
}
|
||||
destroy.apply(this, args);
|
||||
},
|
||||
};
|
||||
update(currentConfig);
|
||||
}
|
||||
function update(configUpdate: ConfigUpdate) {
|
||||
if (typeof configUpdate === 'function') {
|
||||
currentConfig = configUpdate(currentConfig);
|
||||
} else {
|
||||
currentConfig = {
|
||||
...currentConfig,
|
||||
...configUpdate,
|
||||
};
|
||||
}
|
||||
if (confirmDialogInstance) {
|
||||
Object.assign(confirmDialogInstance.component.props, currentConfig);
|
||||
confirmDialogInstance.component.update();
|
||||
}
|
||||
}
|
||||
|
||||
const Wrapper = (p: ModalFuncProps) => {
|
||||
const global = globalConfigForApi;
|
||||
const rootPrefixCls = global.prefixCls;
|
||||
const prefixCls = p.prefixCls || `${rootPrefixCls}-modal`;
|
||||
return (
|
||||
<ConfigProvider {...(global as any)} notUpdateGlobalConfig={true} prefixCls={rootPrefixCls}>
|
||||
<ConfirmDialog {...p} prefixCls={prefixCls}></ConfirmDialog>
|
||||
<ConfirmDialog {...p} rootPrefixCls={rootPrefixCls} prefixCls={prefixCls}></ConfirmDialog>
|
||||
</ConfigProvider>
|
||||
);
|
||||
};
|
||||
function render(props: ModalFuncProps) {
|
||||
const vm = createVNode(Wrapper, { ...props });
|
||||
vm.appContext = config.parentContext || config.appContext || vm.appContext;
|
||||
vueRender(vm, div);
|
||||
vueRender(vm, container as any);
|
||||
return vm;
|
||||
}
|
||||
|
||||
|
@ -79,3 +98,48 @@ const confirm = (config: ModalFuncProps) => {
|
|||
};
|
||||
|
||||
export default confirm;
|
||||
|
||||
export function withWarn(props: ModalFuncProps): ModalFuncProps {
|
||||
return {
|
||||
icon: () => <ExclamationCircleOutlined />,
|
||||
okCancel: false,
|
||||
...props,
|
||||
type: 'warning',
|
||||
};
|
||||
}
|
||||
|
||||
export function withInfo(props: ModalFuncProps): ModalFuncProps {
|
||||
return {
|
||||
icon: () => <InfoCircleOutlined />,
|
||||
okCancel: false,
|
||||
...props,
|
||||
type: 'info',
|
||||
};
|
||||
}
|
||||
|
||||
export function withSuccess(props: ModalFuncProps): ModalFuncProps {
|
||||
return {
|
||||
icon: () => <CheckCircleOutlined />,
|
||||
okCancel: false,
|
||||
...props,
|
||||
type: 'success',
|
||||
};
|
||||
}
|
||||
|
||||
export function withError(props: ModalFuncProps): ModalFuncProps {
|
||||
return {
|
||||
icon: () => <CloseCircleOutlined />,
|
||||
okCancel: false,
|
||||
...props,
|
||||
type: 'error',
|
||||
};
|
||||
}
|
||||
|
||||
export function withConfirm(props: ModalFuncProps): ModalFuncProps {
|
||||
return {
|
||||
icon: () => <ExclamationCircleOutlined />,
|
||||
okCancel: true,
|
||||
...props,
|
||||
type: 'confirm',
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
<docs>
|
||||
---
|
||||
order: 13
|
||||
title:
|
||||
zh-CN: 自定义渲染对话框
|
||||
en-US: Custom modal content render
|
||||
debugger: true
|
||||
---
|
||||
|
||||
## zh-CN
|
||||
|
||||
自定义渲染对话框, 可通过 `react-draggable` 来实现拖拽。
|
||||
|
||||
## en-US
|
||||
|
||||
Custom modal content render. use `react-draggable` implements draggable.
|
||||
|
||||
</docs>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<a-button type="primary" @click="showModal">Open Draggable Modal</a-button>
|
||||
<a-modal v-model:visible="visible" @ok="handleOk">
|
||||
<template #title>
|
||||
<div
|
||||
class="drag"
|
||||
style="width: 100%; cursor: move"
|
||||
@mouseover="handleMouseover"
|
||||
@mouseout="handleMouseout"
|
||||
@focus="() => {}"
|
||||
@blur="() => {}"
|
||||
>
|
||||
Draggable Modal
|
||||
</div>
|
||||
</template>
|
||||
<p>
|
||||
Just don't learn physics at school and your life will be full of magic and miracles.
|
||||
</p>
|
||||
<br />
|
||||
<p>Day before yesterday I saw a rabbit, and yesterday a deer, and today, you.</p>
|
||||
<template #modalRender="{ originVNode }">
|
||||
<VueDragResize is-active drag-handle=".drag" :is-resizable="false">
|
||||
<component :is="originVNode"></component>
|
||||
</VueDragResize>
|
||||
</template>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from 'vue';
|
||||
import VueDragResize from 'vue-drag-resize';
|
||||
export default defineComponent({
|
||||
components: {
|
||||
VueDragResize,
|
||||
},
|
||||
setup() {
|
||||
const visible = ref<boolean>(false);
|
||||
const draggleRef = ref();
|
||||
const disabled = ref(true);
|
||||
const bounds = ref({ left: 0, top: 0, width: 520, height: 0 });
|
||||
const showModal = () => {
|
||||
visible.value = true;
|
||||
};
|
||||
|
||||
const handleOk = (e: MouseEvent) => {
|
||||
console.log(e);
|
||||
visible.value = false;
|
||||
};
|
||||
|
||||
const onStart = (event, uiData) => {
|
||||
const { clientWidth, clientHeight } = window.document.documentElement;
|
||||
const targetRect = draggleRef.value?.getBoundingClientRect();
|
||||
if (!targetRect) {
|
||||
return;
|
||||
}
|
||||
bounds.value = {
|
||||
left: `${-targetRect.left + uiData.x}px`,
|
||||
right: `${clientWidth - (targetRect.right - uiData.x)}`,
|
||||
top: `${-targetRect.top + uiData.y}`,
|
||||
bottom: `${clientHeight - (targetRect.bottom - uiData.y)}`,
|
||||
};
|
||||
};
|
||||
return {
|
||||
visible,
|
||||
showModal,
|
||||
handleOk,
|
||||
onStart,
|
||||
handleMouseover() {
|
||||
if (disabled.value) {
|
||||
disabled.value = false;
|
||||
}
|
||||
},
|
||||
handleMouseout() {
|
||||
disabled.value = true;
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -1,70 +1,34 @@
|
|||
import type { App, Plugin } from 'vue';
|
||||
import type { ModalFunc, ModalFuncProps } from './Modal';
|
||||
import Modal, { destroyFns } from './Modal';
|
||||
import modalConfirm from './confirm';
|
||||
import InfoCircleOutlined from '@ant-design/icons-vue/InfoCircleOutlined';
|
||||
import CheckCircleOutlined from '@ant-design/icons-vue/CheckCircleOutlined';
|
||||
import CloseCircleOutlined from '@ant-design/icons-vue/CloseCircleOutlined';
|
||||
import ExclamationCircleOutlined from '@ant-design/icons-vue/ExclamationCircleOutlined';
|
||||
import confirm, { withWarn, withInfo, withSuccess, withError, withConfirm } from './confirm';
|
||||
|
||||
export type { IActionButtonProps as ActionButtonProps } from './ActionButton';
|
||||
export type { ActionButtonProps } from './ActionButton';
|
||||
export type { ModalProps, ModalFuncProps } from './Modal';
|
||||
|
||||
const info = function (props: ModalFuncProps) {
|
||||
const config = {
|
||||
type: 'info',
|
||||
icon: () => <InfoCircleOutlined />,
|
||||
okCancel: false,
|
||||
...props,
|
||||
};
|
||||
return modalConfirm(config);
|
||||
function modalWarn(props: ModalFuncProps) {
|
||||
return confirm(withWarn(props));
|
||||
}
|
||||
|
||||
Modal.info = function infoFn(props: ModalFuncProps) {
|
||||
return confirm(withInfo(props));
|
||||
};
|
||||
|
||||
const success = function (props: ModalFuncProps) {
|
||||
const config = {
|
||||
type: 'success',
|
||||
icon: () => <CheckCircleOutlined />,
|
||||
okCancel: false,
|
||||
...props,
|
||||
};
|
||||
return modalConfirm(config);
|
||||
Modal.success = function successFn(props: ModalFuncProps) {
|
||||
return confirm(withSuccess(props));
|
||||
};
|
||||
|
||||
const error = function (props: ModalFuncProps) {
|
||||
const config = {
|
||||
type: 'error',
|
||||
icon: () => <CloseCircleOutlined />,
|
||||
okCancel: false,
|
||||
...props,
|
||||
};
|
||||
return modalConfirm(config);
|
||||
Modal.error = function errorFn(props: ModalFuncProps) {
|
||||
return confirm(withError(props));
|
||||
};
|
||||
|
||||
const warning = function (props: ModalFuncProps) {
|
||||
const config = {
|
||||
type: 'warning',
|
||||
icon: () => <ExclamationCircleOutlined />,
|
||||
okCancel: false,
|
||||
...props,
|
||||
};
|
||||
return modalConfirm(config);
|
||||
};
|
||||
const warn = warning;
|
||||
Modal.warning = modalWarn;
|
||||
|
||||
const confirm = function confirmFn(props: ModalFuncProps) {
|
||||
const config = {
|
||||
type: 'confirm',
|
||||
okCancel: true,
|
||||
...props,
|
||||
};
|
||||
return modalConfirm(config);
|
||||
Modal.warn = modalWarn;
|
||||
|
||||
Modal.confirm = function confirmFn(props: ModalFuncProps) {
|
||||
return confirm(withConfirm(props));
|
||||
};
|
||||
Modal.info = info;
|
||||
Modal.success = success;
|
||||
Modal.error = error;
|
||||
Modal.warning = warning;
|
||||
Modal.warn = warn;
|
||||
Modal.confirm = confirm;
|
||||
|
||||
Modal.destroyAll = function destroyAllFn() {
|
||||
while (destroyFns.length) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import type { CSSProperties, PropType } from 'vue';
|
||||
import { computed, ref, defineComponent } from 'vue';
|
||||
import { computed, ref, defineComponent, nextTick } from 'vue';
|
||||
import type { MouseEventHandler } from '../_util/EventInterface';
|
||||
import Transition, { getTransitionProps } from '../_util/transition';
|
||||
import dialogPropTypes from './IDialogPropTypes';
|
||||
|
@ -12,6 +12,7 @@ export type ContentRef = {
|
|||
};
|
||||
export default defineComponent({
|
||||
name: 'Content',
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
...dialogPropTypes(),
|
||||
motionName: String,
|
||||
|
@ -20,7 +21,7 @@ export default defineComponent({
|
|||
onMousedown: Function as PropType<MouseEventHandler>,
|
||||
onMouseup: Function as PropType<MouseEventHandler>,
|
||||
},
|
||||
setup(props, { expose, slots }) {
|
||||
setup(props, { expose, slots, attrs }) {
|
||||
const sentinelStartRef = ref<HTMLDivElement>();
|
||||
const sentinelEndRef = ref<HTMLDivElement>();
|
||||
const dialogRef = ref<HTMLDivElement>();
|
||||
|
@ -54,13 +55,16 @@ export default defineComponent({
|
|||
});
|
||||
|
||||
const onPrepare = () => {
|
||||
const elementOffset = offset(dialogRef.value);
|
||||
|
||||
transformOrigin.value = props.mousePosition
|
||||
? `${props.mousePosition.x - elementOffset.left}px ${
|
||||
props.mousePosition.y - elementOffset.top
|
||||
}px`
|
||||
: '';
|
||||
nextTick(() => {
|
||||
if (dialogRef.value) {
|
||||
const elementOffset = offset(dialogRef.value);
|
||||
transformOrigin.value = props.mousePosition
|
||||
? `${props.mousePosition.x - elementOffset.left}px ${
|
||||
props.mousePosition.y - elementOffset.top
|
||||
}px`
|
||||
: '';
|
||||
}
|
||||
});
|
||||
};
|
||||
const onVisibleChanged = (visible: boolean) => {
|
||||
props.onVisibleChanged(visible);
|
||||
|
@ -122,24 +126,24 @@ export default defineComponent({
|
|||
return (
|
||||
<Transition
|
||||
{...transitionProps}
|
||||
onEnter={onPrepare}
|
||||
// onBeforeEnter={onPrepare}
|
||||
onBeforeEnter={onPrepare}
|
||||
onAfterEnter={() => onVisibleChanged(true)}
|
||||
onAfterLeave={() => onVisibleChanged(false)}
|
||||
>
|
||||
{visible || !destroyOnClose ? (
|
||||
<div
|
||||
{...attrs}
|
||||
ref={dialogRef}
|
||||
v-show={visible}
|
||||
key="dialog-element"
|
||||
role="document"
|
||||
style={contentStyleRef.value}
|
||||
class={prefixCls}
|
||||
style={{ ...contentStyleRef.value, ...(attrs.style as any) }}
|
||||
class={[prefixCls, attrs.class]}
|
||||
onMousedown={onMousedown}
|
||||
onMouseup={onMouseup}
|
||||
>
|
||||
<div tabindex={0} ref={sentinelStartRef} style={sentinelStyle} aria-hidden="true" />
|
||||
{modalRender ? modalRender(content) : content}
|
||||
{modalRender ? modalRender({ originVNode: content }) : content}
|
||||
<div tabindex={0} ref={sentinelEndRef} style={sentinelStyle} aria-hidden="true" />
|
||||
</div>
|
||||
) : null}
|
||||
|
|
|
@ -51,6 +51,7 @@ export default defineComponent({
|
|||
contentRef.value?.focus();
|
||||
}
|
||||
} else {
|
||||
const preAnimatedVisible = animatedVisible.value;
|
||||
// Clean up scroll bar & focus back
|
||||
animatedVisible.value = false;
|
||||
if (props.mask && lastOutSideActiveElementRef.value && props.focusTriggerAfterClose) {
|
||||
|
@ -63,7 +64,7 @@ export default defineComponent({
|
|||
}
|
||||
|
||||
// Trigger afterClose only when change visible from true to false
|
||||
if (animatedVisible.value) {
|
||||
if (preAnimatedVisible) {
|
||||
props.afterClose?.();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { defineComponent, Transition } from 'vue';
|
||||
import { getTransitionProps } from '../_util/transition';
|
||||
import { defineComponent } from 'vue';
|
||||
import Transition, { getTransitionProps } from '../_util/transition';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Mask',
|
||||
|
|
|
@ -221,6 +221,7 @@
|
|||
"vue": "^3.1.0",
|
||||
"vue-antd-md-loader": "^1.2.1-beta.1",
|
||||
"vue-clipboard2": "0.3.3",
|
||||
"vue-drag-resize": "^2.0.3",
|
||||
"vue-draggable-resizable": "^2.1.0",
|
||||
"vue-eslint-parser": "^8.0.0",
|
||||
"vue-i18n": "^9.1.7",
|
||||
|
|
Loading…
Reference in New Issue