Skip to content

Commit 98755f3

Browse files
authored
refactor: modal (#5129), close #5096
* fix: can not scroll when close dialog #5096 * refactor: modal
1 parent ab2df12 commit 98755f3

37 files changed

+1171
-2361
lines changed

components/_util/switchScrollingEffect.js

-20
This file was deleted.
+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import getScrollBarSize from './getScrollBarSize';
2+
import setStyle from './setStyle';
3+
4+
function isBodyOverflowing() {
5+
return (
6+
document.body.scrollHeight > (window.innerHeight || document.documentElement.clientHeight) &&
7+
window.innerWidth > document.body.offsetWidth
8+
);
9+
}
10+
11+
let cacheStyle = {};
12+
13+
export default (close?: boolean) => {
14+
if (!isBodyOverflowing() && !close) {
15+
return;
16+
}
17+
18+
// https://github.com/ant-design/ant-design/issues/19729
19+
const scrollingEffectClassName = 'ant-scrolling-effect';
20+
const scrollingEffectClassNameReg = new RegExp(`${scrollingEffectClassName}`, 'g');
21+
const bodyClassName = document.body.className;
22+
23+
if (close) {
24+
if (!scrollingEffectClassNameReg.test(bodyClassName)) return;
25+
setStyle(cacheStyle);
26+
cacheStyle = {};
27+
document.body.className = bodyClassName.replace(scrollingEffectClassNameReg, '').trim();
28+
return;
29+
}
30+
31+
const scrollBarSize = getScrollBarSize();
32+
if (scrollBarSize) {
33+
cacheStyle = setStyle({
34+
position: 'relative',
35+
width: `calc(100% - ${scrollBarSize}px)`,
36+
});
37+
if (!scrollingEffectClassNameReg.test(bodyClassName)) {
38+
const addClassName = `${bodyClassName} ${scrollingEffectClassName}`;
39+
document.body.className = addClassName.trim();
40+
}
41+
}
42+
};

components/modal/ActionButton.tsx

+94-70
Original file line numberDiff line numberDiff line change
@@ -1,89 +1,113 @@
11
import type { ExtractPropTypes, PropType } from 'vue';
2-
import { defineComponent } from 'vue';
3-
import PropTypes from '../_util/vue-types';
2+
import { onMounted, ref, defineComponent, onBeforeUnmount } from 'vue';
43
import Button from '../button';
5-
import BaseMixin from '../_util/BaseMixin';
4+
import type { ButtonProps } from '../button';
65
import type { LegacyButtonType } from '../button/buttonTypes';
76
import { convertLegacyProps } from '../button/buttonTypes';
8-
import { getSlot, findDOMNode } from '../_util/props-util';
97

10-
const ActionButtonProps = {
8+
const actionButtonProps = {
119
type: {
1210
type: String as PropType<LegacyButtonType>,
1311
},
14-
actionFn: PropTypes.func,
15-
closeModal: PropTypes.func,
16-
autofocus: PropTypes.looseBool,
17-
buttonProps: PropTypes.object,
12+
actionFn: Function as PropType<(...args: any[]) => any | PromiseLike<any>>,
13+
close: Function,
14+
autofocus: Boolean,
15+
prefixCls: String,
16+
buttonProps: Object as PropType<ButtonProps>,
17+
emitEvent: Boolean,
18+
quitOnNullishReturnValue: Boolean,
1819
};
1920

20-
export type IActionButtonProps = ExtractPropTypes<typeof ActionButtonProps>;
21+
export type ActionButtonProps = ExtractPropTypes<typeof actionButtonProps>;
22+
23+
function isThenable(thing?: PromiseLike<any>): boolean {
24+
return !!(thing && !!thing.then);
25+
}
2126

2227
export default defineComponent({
23-
mixins: [BaseMixin],
24-
props: ActionButtonProps,
25-
setup() {
26-
return {
27-
timeoutId: undefined,
28-
};
29-
},
30-
data() {
31-
return {
32-
loading: false,
28+
name: 'ActionButton',
29+
props: actionButtonProps,
30+
setup(props, { slots }) {
31+
const clickedRef = ref<boolean>(false);
32+
const buttonRef = ref();
33+
const loading = ref(false);
34+
let timeoutId: any;
35+
onMounted(() => {
36+
if (props.autofocus) {
37+
timeoutId = setTimeout(() => buttonRef.value.$el?.focus());
38+
}
39+
});
40+
onBeforeUnmount(() => {
41+
clearTimeout(timeoutId);
42+
});
43+
44+
const handlePromiseOnOk = (returnValueOfOnOk?: PromiseLike<any>) => {
45+
const { close } = props;
46+
if (!isThenable(returnValueOfOnOk)) {
47+
return;
48+
}
49+
loading.value = true;
50+
returnValueOfOnOk!.then(
51+
(...args: any[]) => {
52+
loading.value = false;
53+
close(...args);
54+
clickedRef.value = false;
55+
},
56+
(e: Error) => {
57+
// Emit error when catch promise reject
58+
// eslint-disable-next-line no-console
59+
console.error(e);
60+
// See: https://github.com/ant-design/ant-design/issues/6183
61+
loading.value = false;
62+
clickedRef.value = false;
63+
},
64+
);
3365
};
34-
},
35-
mounted() {
36-
if (this.autofocus) {
37-
this.timeoutId = setTimeout(() => findDOMNode(this).focus());
38-
}
39-
},
40-
beforeUnmount() {
41-
clearTimeout(this.timeoutId);
42-
},
43-
methods: {
44-
onClick() {
45-
const { actionFn, closeModal } = this;
46-
if (actionFn) {
47-
let ret: any;
48-
if (actionFn.length) {
49-
ret = actionFn(closeModal);
50-
} else {
51-
ret = actionFn();
52-
if (!ret) {
53-
closeModal();
54-
}
55-
}
56-
if (ret && ret.then) {
57-
this.setState({ loading: true });
58-
ret.then(
59-
(...args: any[]) => {
60-
// It's unnecessary to set loading=false, for the Modal will be unmounted after close.
61-
// this.setState({ loading: false });
62-
closeModal(...args);
63-
},
64-
(e: Event) => {
65-
// Emit error when catch promise reject
66-
// eslint-disable-next-line no-console
67-
console.error(e);
68-
// See: https://github.com/ant-design/ant-design/issues/6183
69-
this.setState({ loading: false });
70-
},
71-
);
66+
67+
const onClick = (e: MouseEvent) => {
68+
const { actionFn, close = () => {} } = props;
69+
if (clickedRef.value) {
70+
return;
71+
}
72+
clickedRef.value = true;
73+
if (!actionFn) {
74+
close();
75+
return;
76+
}
77+
let returnValueOfOnOk;
78+
if (props.emitEvent) {
79+
returnValueOfOnOk = actionFn(e);
80+
if (props.quitOnNullishReturnValue && !isThenable(returnValueOfOnOk)) {
81+
clickedRef.value = false;
82+
close(e);
83+
return;
7284
}
85+
} else if (actionFn.length) {
86+
returnValueOfOnOk = actionFn(close);
87+
// https://github.com/ant-design/ant-design/issues/23358
88+
clickedRef.value = false;
7389
} else {
74-
closeModal();
90+
returnValueOfOnOk = actionFn();
91+
if (!returnValueOfOnOk) {
92+
close();
93+
return;
94+
}
7595
}
76-
},
77-
},
78-
79-
render() {
80-
const { type, loading, buttonProps } = this;
81-
const props = {
82-
...convertLegacyProps(type),
83-
onClick: this.onClick,
84-
loading,
85-
...buttonProps,
96+
handlePromiseOnOk(returnValueOfOnOk);
97+
};
98+
return () => {
99+
const { type, prefixCls, buttonProps } = props;
100+
return (
101+
<Button
102+
{...convertLegacyProps(type)}
103+
onClick={onClick}
104+
loading={loading.value}
105+
prefixCls={prefixCls}
106+
{...buttonProps}
107+
ref={buttonRef}
108+
v-slots={slots}
109+
></Button>
110+
);
86111
};
87-
return <Button {...props}>{getSlot(this)}</Button>;
88112
},
89113
});

components/modal/ConfirmDialog.tsx

+32-16
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,17 @@ import Dialog from './Modal';
44
import ActionButton from './ActionButton';
55
import { defineComponent } from 'vue';
66
import { useLocaleReceiver } from '../locale-provider/LocaleReceiver';
7+
import { getTransitionName } from '../_util/transition';
78

89
interface ConfirmDialogProps extends ModalFuncProps {
910
afterClose?: () => void;
1011
close?: (...args: any[]) => void;
1112
autoFocusButton?: null | 'ok' | 'cancel';
13+
rootPrefixCls: string;
14+
iconPrefixCls?: string;
1215
}
1316

14-
function renderSomeContent(_name, someContent) {
17+
function renderSomeContent(someContent: any) {
1518
if (typeof someContent === 'function') {
1619
return someContent();
1720
}
@@ -50,6 +53,12 @@ export default defineComponent<ConfirmDialogProps>({
5053
'type',
5154
'title',
5255
'content',
56+
'direction',
57+
'rootPrefixCls',
58+
'bodyStyle',
59+
'closeIcon',
60+
'modalRender',
61+
'focusTriggerAfterClose',
5362
] as any,
5463
setup(props, { attrs }) {
5564
const [locale] = useLocaleReceiver('Modal');
@@ -73,38 +82,42 @@ export default defineComponent<ConfirmDialogProps>({
7382
width = 416,
7483
mask = true,
7584
maskClosable = false,
76-
maskTransitionName = 'fade',
77-
transitionName = 'zoom',
7885
type,
7986
title,
8087
content,
81-
// closable = false,
88+
direction,
89+
closeIcon,
90+
modalRender,
91+
focusTriggerAfterClose,
92+
rootPrefixCls,
93+
bodyStyle,
8294
} = props;
8395
const okType = props.okType || 'primary';
8496
const prefixCls = props.prefixCls || 'ant-modal';
8597
const contentPrefixCls = `${prefixCls}-confirm`;
8698
const style = attrs.style || {};
8799
const okText =
88-
renderSomeContent('okText', props.okText) ||
100+
renderSomeContent(props.okText) ||
89101
(okCancel ? locale.value.okText : locale.value.justOkText);
90-
const cancelText =
91-
renderSomeContent('cancelText', props.cancelText) || locale.value.cancelText;
102+
const cancelText = renderSomeContent(props.cancelText) || locale.value.cancelText;
92103
const autoFocusButton =
93104
props.autoFocusButton === null ? false : props.autoFocusButton || 'ok';
94105

95106
const classString = classNames(
96107
contentPrefixCls,
97108
`${contentPrefixCls}-${type}`,
98109
`${prefixCls}-${type}`,
110+
{ [`${contentPrefixCls}-rtl`]: direction === 'rtl' },
99111
attrs.class,
100112
);
101113

102114
const cancelButton = okCancel && (
103115
<ActionButton
104116
actionFn={onCancel}
105-
closeModal={close}
117+
close={close}
106118
autofocus={autoFocusButton === 'cancel'}
107119
buttonProps={cancelButtonProps}
120+
prefixCls={`${rootPrefixCls}-btn`}
108121
>
109122
{cancelText}
110123
</ActionButton>
@@ -118,39 +131,42 @@ export default defineComponent<ConfirmDialogProps>({
118131
onCancel={e => close({ triggerCancel: true }, e)}
119132
visible={visible}
120133
title=""
121-
transitionName={transitionName}
122134
footer=""
123-
maskTransitionName={maskTransitionName}
135+
transitionName={getTransitionName(rootPrefixCls, 'zoom', props.transitionName)}
136+
maskTransitionName={getTransitionName(rootPrefixCls, 'fade', props.maskTransitionName)}
124137
mask={mask}
125138
maskClosable={maskClosable}
126139
maskStyle={maskStyle}
127140
style={style}
141+
bodyStyle={bodyStyle}
128142
width={width}
129143
zIndex={zIndex}
130144
afterClose={afterClose}
131145
keyboard={keyboard}
132146
centered={centered}
133147
getContainer={getContainer}
134148
closable={closable}
149+
closeIcon={closeIcon}
150+
modalRender={modalRender}
151+
focusTriggerAfterClose={focusTriggerAfterClose}
135152
>
136153
<div class={`${contentPrefixCls}-body-wrapper`}>
137154
<div class={`${contentPrefixCls}-body`}>
138-
{renderSomeContent('icon', icon)}
155+
{renderSomeContent(icon)}
139156
{title === undefined ? null : (
140-
<span class={`${contentPrefixCls}-title`}>{renderSomeContent('title', title)}</span>
157+
<span class={`${contentPrefixCls}-title`}>{renderSomeContent(title)}</span>
141158
)}
142-
<div class={`${contentPrefixCls}-content`}>
143-
{renderSomeContent('content', content)}
144-
</div>
159+
<div class={`${contentPrefixCls}-content`}>{renderSomeContent(content)}</div>
145160
</div>
146161
<div class={`${contentPrefixCls}-btns`}>
147162
{cancelButton}
148163
<ActionButton
149164
type={okType}
150165
actionFn={onOk}
151-
closeModal={close}
166+
close={close}
152167
autofocus={autoFocusButton === 'ok'}
153168
buttonProps={okButtonProps}
169+
prefixCls={`${rootPrefixCls}-btn`}
154170
>
155171
{okText}
156172
</ActionButton>

0 commit comments

Comments
 (0)