Skip to content

Commit

Permalink
refactor: modal (#5129), close #5096
Browse files Browse the repository at this point in the history
* fix: can not scroll when close dialog #5096

* refactor: modal
  • Loading branch information
tangjinzhou authored Jan 6, 2022
1 parent ab2df12 commit 98755f3
Show file tree
Hide file tree
Showing 37 changed files with 1,171 additions and 2,361 deletions.
20 changes: 0 additions & 20 deletions components/_util/switchScrollingEffect.js

This file was deleted.

42 changes: 42 additions & 0 deletions components/_util/switchScrollingEffect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import getScrollBarSize from './getScrollBarSize';
import setStyle from './setStyle';

function isBodyOverflowing() {
return (
document.body.scrollHeight > (window.innerHeight || document.documentElement.clientHeight) &&
window.innerWidth > document.body.offsetWidth
);
}

let cacheStyle = {};

export default (close?: boolean) => {
if (!isBodyOverflowing() && !close) {
return;
}

// https://github.com/ant-design/ant-design/issues/19729
const scrollingEffectClassName = 'ant-scrolling-effect';
const scrollingEffectClassNameReg = new RegExp(`${scrollingEffectClassName}`, 'g');
const bodyClassName = document.body.className;

if (close) {
if (!scrollingEffectClassNameReg.test(bodyClassName)) return;
setStyle(cacheStyle);
cacheStyle = {};
document.body.className = bodyClassName.replace(scrollingEffectClassNameReg, '').trim();
return;
}

const scrollBarSize = getScrollBarSize();
if (scrollBarSize) {
cacheStyle = setStyle({
position: 'relative',
width: `calc(100% - ${scrollBarSize}px)`,
});
if (!scrollingEffectClassNameReg.test(bodyClassName)) {
const addClassName = `${bodyClassName} ${scrollingEffectClassName}`;
document.body.className = addClassName.trim();
}
}
};
164 changes: 94 additions & 70 deletions components/modal/ActionButton.tsx
Original file line number Diff line number Diff line change
@@ -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,
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);
});

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;
},
);
};
},
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 });
},
);

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 {
closeModal();
returnValueOfOnOk = actionFn();
if (!returnValueOfOnOk) {
close();
return;
}
}
},
},

render() {
const { type, loading, buttonProps } = this;
const props = {
...convertLegacyProps(type),
onClick: this.onClick,
loading,
...buttonProps,
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>;
},
});
48 changes: 32 additions & 16 deletions components/modal/ConfirmDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down Expand Up @@ -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');
Expand All @@ -73,38 +82,42 @@ 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';

const classString = classNames(
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>
Expand All @@ -118,39 +131,42 @@ 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}
keyboard={keyboard}
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>
Expand Down
Loading

0 comments on commit 98755f3

Please sign in to comment.