diff --git a/components/button/buttonTypes.ts b/components/button/buttonTypes.ts index a87eb1191..a7a359ba1 100644 --- a/components/button/buttonTypes.ts +++ b/components/button/buttonTypes.ts @@ -1,3 +1,4 @@ +import { ExtractPropTypes } from 'vue'; import { tuple } from '../_util/type'; import PropTypes, { withUndefined } from '../_util/vue-types'; @@ -10,7 +11,7 @@ export type ButtonSize = typeof ButtonSizes[number]; const ButtonHTMLTypes = tuple('submit', 'button', 'reset'); export type ButtonHTMLType = typeof ButtonHTMLTypes[number]; -export default () => ({ +const buttonProps = () => ({ prefixCls: PropTypes.string, type: PropTypes.oneOf(ButtonTypes), htmlType: PropTypes.oneOf(ButtonHTMLTypes).def('button'), @@ -26,3 +27,7 @@ export default () => ({ title: PropTypes.string, onClick: PropTypes.func, }); + +export type ButtonProps = ExtractPropTypes>>; + +export default buttonProps; diff --git a/components/locale-provider/LocaleReceiver.tsx b/components/locale-provider/LocaleReceiver.tsx index b48bd13c8..4c9bae778 100644 --- a/components/locale-provider/LocaleReceiver.tsx +++ b/components/locale-provider/LocaleReceiver.tsx @@ -26,7 +26,7 @@ export default defineComponent({ }, children: { type: Function as PropType< - (locale: object, localeCode?: string, fullLocale?: object) => VNodeTypes + (locale: any, localeCode?: string, fullLocale?: object) => VNodeTypes >, }, }, diff --git a/components/modal/ActionButton.jsx b/components/modal/ActionButton.tsx similarity index 89% rename from components/modal/ActionButton.jsx rename to components/modal/ActionButton.tsx index 860f4e8df..beb2929e6 100644 --- a/components/modal/ActionButton.jsx +++ b/components/modal/ActionButton.tsx @@ -1,9 +1,11 @@ +import { defineComponent, ExtractPropTypes } from 'vue'; import PropTypes from '../_util/vue-types'; import Button from '../button'; import BaseMixin from '../_util/BaseMixin'; import buttonTypes from '../button/buttonTypes'; import { getSlot, findDOMNode } from '../_util/props-util'; const ButtonType = buttonTypes().type; + const ActionButtonProps = { type: ButtonType, actionFn: PropTypes.func, @@ -12,7 +14,9 @@ const ActionButtonProps = { buttonProps: PropTypes.object, }; -export default { +export type IActionButtonProps = ExtractPropTypes; + +export default defineComponent({ mixins: [BaseMixin], props: ActionButtonProps, data() { @@ -20,6 +24,11 @@ export default { loading: false, }; }, + setup() { + return { + timeoutId: undefined, + }; + }, mounted() { if (this.autofocus) { this.timeoutId = setTimeout(() => findDOMNode(this).focus()); @@ -74,4 +83,4 @@ export default { }; return ; }, -}; +}); diff --git a/components/modal/ConfirmDialog.jsx b/components/modal/ConfirmDialog.tsx similarity index 59% rename from components/modal/ConfirmDialog.jsx rename to components/modal/ConfirmDialog.tsx index b51efb2b4..461520893 100644 --- a/components/modal/ConfirmDialog.jsx +++ b/components/modal/ConfirmDialog.tsx @@ -1,9 +1,16 @@ import classNames from '../_util/classNames'; -import Dialog from './Modal'; +import Dialog, { ModalFuncProps } from './Modal'; import ActionButton from './ActionButton'; import { getConfirmLocale } from './locale'; +import { FunctionalComponent } from 'vue'; -const ConfirmDialog = (_, { attrs }) => { +interface ConfirmDialogProps extends ModalFuncProps { + afterClose?: () => void; + close: (...args: any[]) => void; + autoFocusButton?: null | 'ok' | 'cancel'; +} + +const ConfirmDialog: FunctionalComponent = props => { const { icon, onCancel, @@ -18,30 +25,30 @@ const ConfirmDialog = (_, { attrs }) => { maskStyle, okButtonProps, cancelButtonProps, - closable = false, - } = attrs; - const okType = attrs.okType || 'primary'; - const prefixCls = attrs.prefixCls || 'ant-modal'; + // closable = false, + } = props; + const okType = props.okType || 'primary'; + const prefixCls = props.prefixCls || 'ant-modal'; const contentPrefixCls = `${prefixCls}-confirm`; // 默认为 true,保持向下兼容 - const okCancel = 'okCancel' in attrs ? attrs.okCancel : true; - const width = attrs.width || 416; - const style = attrs.style || {}; - const mask = attrs.mask === undefined ? true : attrs.mask; + const okCancel = 'okCancel' in props ? props.okCancel : true; + const width = props.width || 416; + const style = props.style || {}; + const mask = props.mask === undefined ? true : props.mask; // 默认为 false,保持旧版默认行为 - const maskClosable = attrs.maskClosable === undefined ? false : attrs.maskClosable; + const maskClosable = props.maskClosable === undefined ? false : props.maskClosable; const runtimeLocale = getConfirmLocale(); - const okText = attrs.okText || (okCancel ? runtimeLocale.okText : runtimeLocale.justOkText); - const cancelText = attrs.cancelText || runtimeLocale.cancelText; - const autoFocusButton = attrs.autoFocusButton === null ? false : attrs.autoFocusButton || 'ok'; - const transitionName = attrs.transitionName || 'zoom'; - const maskTransitionName = attrs.maskTransitionName || 'fade'; + const okText = props.okText || (okCancel ? runtimeLocale.okText : runtimeLocale.justOkText); + const cancelText = props.cancelText || runtimeLocale.cancelText; + const autoFocusButton = props.autoFocusButton === null ? false : props.autoFocusButton || 'ok'; + const transitionName = props.transitionName || 'zoom'; + const maskTransitionName = props.maskTransitionName || 'fade'; const classString = classNames( contentPrefixCls, - `${contentPrefixCls}-${attrs.type}`, - `${prefixCls}-${attrs.type}`, - attrs.class, + `${contentPrefixCls}-${props.type}`, + `${prefixCls}-${props.type}`, + props.class, ); const cancelButton = okCancel && ( @@ -62,7 +69,6 @@ const ConfirmDialog = (_, { attrs }) => { wrapClassName={classNames({ [`${contentPrefixCls}-centered`]: !!centered })} onCancel={e => close({ triggerCancel: true }, e)} visible={visible} - closable={closable} title="" transitionName={transitionName} footer="" @@ -80,13 +86,11 @@ const ConfirmDialog = (_, { attrs }) => { >
- {typeof icon === 'function' ? icon() : icon} - {attrs.title === undefined ? null : ( - {attrs.title} + {icon} + {props.title === undefined ? null : ( + {props.title} )} -
- {typeof attrs.content === 'function' ? attrs.content() : attrs.content} -
+
{props.content}
{cancelButton} @@ -104,5 +108,7 @@ const ConfirmDialog = (_, { attrs }) => { ); }; + ConfirmDialog.inheritAttrs = false; + export default ConfirmDialog; diff --git a/components/modal/Modal.jsx b/components/modal/Modal.tsx similarity index 50% rename from components/modal/Modal.jsx rename to components/modal/Modal.tsx index 27b907f80..097b05bb4 100644 --- a/components/modal/Modal.jsx +++ b/components/modal/Modal.tsx @@ -1,4 +1,11 @@ -import { inject } from 'vue'; +import { + defineComponent, + ExtractPropTypes, + inject, + VNodeTypes, + CSSProperties, + PropType, +} from 'vue'; import classNames from '../_util/classNames'; import Dialog from '../vc-dialog'; import PropTypes from '../_util/vue-types'; @@ -6,15 +13,18 @@ import addEventListener from '../vc-util/Dom/addEventListener'; import { getConfirmLocale } from './locale'; import CloseOutlined from '@ant-design/icons-vue/CloseOutlined'; import Button from '../button'; -import buttonTypes from '../button/buttonTypes'; -const ButtonType = buttonTypes().type; +import buttonTypes, { ButtonType, ButtonProps } from '../button/buttonTypes'; import LocaleReceiver from '../locale-provider/LocaleReceiver'; -import { initDefaultProps, getComponent, getSlot } from '../_util/props-util'; +import { getComponent, getSlot } from '../_util/props-util'; +import initDefaultProps from '../_util/props-util/initDefaultProps'; import { defaultConfigProvider } from '../config-provider'; -let mousePosition = null; +const ButtonProps = buttonTypes(); +const ButtonType = ButtonProps.type; + +let mousePosition: { x: number; y: number } | null = null; // ref: https://github.com/ant-design/ant-design/issues/15795 -const getClickPosition = e => { +const getClickPosition = (e: MouseEvent) => { mousePosition = { x: e.pageX, y: e.pageY, @@ -31,68 +41,121 @@ if (typeof window !== 'undefined' && window.document && window.document.document } function noop() {} -const modalProps = (defaultProps = {}) => { - const props = { - prefixCls: PropTypes.string, - /** 对话框是否可见*/ - visible: PropTypes.looseBool, - /** 确定按钮 loading*/ - confirmLoading: PropTypes.looseBool, - /** 标题*/ - title: PropTypes.any, - /** 是否显示右上角的关闭按钮*/ - closable: PropTypes.looseBool, - closeIcon: PropTypes.any, - /** 点击确定回调*/ - // onOk: (e: React.MouseEvent) => void, - /** 点击模态框右上角叉、取消按钮、Props.maskClosable 值为 true 时的遮罩层或键盘按下 Esc 时的回调*/ - // onCancel: (e: React.MouseEvent) => void, - afterClose: PropTypes.func.def(noop), - /** 垂直居中 */ - centered: PropTypes.looseBool, - /** 宽度*/ - width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - /** 底部内容*/ - footer: PropTypes.any, - /** 确认按钮文字*/ - okText: PropTypes.any, - /** 确认按钮类型*/ - okType: ButtonType, - /** 取消按钮文字*/ - cancelText: PropTypes.any, - icon: PropTypes.any, - /** 点击蒙层是否允许关闭*/ - maskClosable: PropTypes.looseBool, - /** 强制渲染 Modal*/ - forceRender: PropTypes.looseBool, - okButtonProps: PropTypes.object, - cancelButtonProps: PropTypes.object, - destroyOnClose: PropTypes.looseBool, - wrapClassName: PropTypes.string, - maskTransitionName: PropTypes.string, - transitionName: PropTypes.string, - getContainer: PropTypes.func, - zIndex: PropTypes.number, - bodyStyle: PropTypes.object, - maskStyle: PropTypes.object, - mask: PropTypes.looseBool, - keyboard: PropTypes.looseBool, - wrapProps: PropTypes.object, - focusTriggerAfterClose: PropTypes.looseBool, - }; - return initDefaultProps(props, defaultProps); + +const modalProps = { + prefixCls: PropTypes.string, + /** 对话框是否可见*/ + visible: PropTypes.looseBool, + /** 确定按钮 loading*/ + confirmLoading: PropTypes.looseBool, + /** 标题*/ + title: PropTypes.any, + /** 是否显示右上角的关闭按钮*/ + closable: PropTypes.looseBool, + 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]), + /** 底部内容*/ + footer: PropTypes.any, + /** 确认按钮文字*/ + okText: PropTypes.any, + /** 确认按钮类型*/ + okType: ButtonType, + /** 取消按钮文字*/ + cancelText: PropTypes.any, + icon: PropTypes.any, + /** 点击蒙层是否允许关闭*/ + maskClosable: PropTypes.looseBool, + /** 强制渲染 Modal*/ + forceRender: PropTypes.looseBool, + okButtonProps: PropTypes.shape(ButtonProps), + cancelButtonProps: PropTypes.shape(ButtonProps), + destroyOnClose: PropTypes.looseBool, + wrapClassName: PropTypes.string, + maskTransitionName: PropTypes.string, + transitionName: PropTypes.string, + getContainer: PropTypes.func, + zIndex: PropTypes.number, + bodyStyle: PropTypes.style, + maskStyle: PropTypes.style, + mask: PropTypes.looseBool, + keyboard: PropTypes.looseBool, + wrapProps: PropTypes.object, + focusTriggerAfterClose: PropTypes.looseBool, }; +export type ModalProps = ExtractPropTypes; + +export interface ModalFuncProps { + prefixCls?: string; + class?: string; + visible?: boolean; + title?: VNodeTypes; + content?: VNodeTypes; + // TODO: find out exact types + onOk?: (...args: any[]) => any; + onCancel?: (...args: any[]) => any; + okButtonProps?: ButtonProps; + cancelButtonProps?: ButtonProps; + centered?: boolean; + width?: string | number; + okText?: VNodeTypes; + okType?: ButtonType; + cancelText?: VNodeTypes; + icon?: VNodeTypes; + /* Deprecated */ + iconType?: string; + mask?: boolean; + maskClosable?: boolean; + zIndex?: number; + okCancel?: boolean; + style?: CSSProperties | string; + maskStyle?: CSSProperties; + type?: string; + keyboard?: boolean; + getContainer?: getContainerFunc; + autoFocusButton?: null | 'ok' | 'cancel'; + transitionName?: string; + maskTransitionName?: string; +} + +type getContainerFunc = () => HTMLElement; + +export type ModalFunc = ( + props: ModalFuncProps, +) => { + destroy: () => void; + update: (newConfig: ModalFuncProps) => void; +}; + +export interface ModalLocale { + okText: string; + cancelText: string; + justOkText: string; +} + export const destroyFns = []; -export default { +export default defineComponent({ name: 'AModal', inheritAttrs: false, model: { prop: 'visible', event: 'change', }, - props: modalProps({ + emits: ['update:visible', 'cancel', 'change', 'ok'], + props: initDefaultProps(modalProps, { width: 520, transitionName: 'zoom', maskTransitionName: 'fade', @@ -115,23 +178,17 @@ export default { configProvider: inject('configProvider', defaultConfigProvider), }; }, - // static info: ModalFunc; - // static success: ModalFunc; - // static error: ModalFunc; - // static warn: ModalFunc; - // static warning: ModalFunc; - // static confirm: ModalFunc; methods: { - handleCancel(e) { + handleCancel(e: MouseEvent) { this.$emit('update:visible', false); this.$emit('cancel', e); this.$emit('change', false); }, - handleOk(e) { + handleOk(e: MouseEvent) { this.$emit('ok', e); }, - renderFooter(locale) { + renderFooter(locale: ModalLocale) { const { okType, confirmLoading } = this; const cancelBtnProps = { onClick: this.handleCancel, ...(this.cancelButtonProps || {}) }; const okBtnProps = { @@ -194,4 +251,4 @@ export default { }; return {children}; }, -}; +}); diff --git a/components/modal/confirm.jsx b/components/modal/confirm.tsx similarity index 83% rename from components/modal/confirm.jsx rename to components/modal/confirm.tsx index 1d191be49..7b7e9055c 100644 --- a/components/modal/confirm.jsx +++ b/components/modal/confirm.tsx @@ -1,17 +1,17 @@ import { createApp } from 'vue'; import ConfirmDialog from './ConfirmDialog'; -import { destroyFns } from './Modal'; +import { destroyFns, ModalFuncProps } from './Modal'; import Omit from 'omit.js'; -export default function confirm(config) { +export default function confirm(config: ModalFuncProps) { const div = document.createElement('div'); document.body.appendChild(div); let currentConfig = { ...Omit(config, ['parentContext']), close, visible: true }; let confirmDialogInstance = null; let confirmDialogProps = {}; - function close(...args) { + function close(this: typeof close, ...args: any[]) { currentConfig = { ...currentConfig, visible: false, @@ -19,7 +19,7 @@ export default function confirm(config) { }; update(currentConfig); } - function update(newConfig) { + function update(newConfig: ModalFuncProps) { currentConfig = { ...currentConfig, ...newConfig, @@ -27,7 +27,7 @@ export default function confirm(config) { confirmDialogInstance && Object.assign(confirmDialogInstance, { confirmDialogProps: currentConfig }); } - function destroy(...args) { + function destroy(...args: any[]) { if (confirmDialogInstance && div.parentNode) { confirmDialogInstance.vIf = false; // hack destroy confirmDialogInstance = null; @@ -46,10 +46,10 @@ export default function confirm(config) { } } - function render(props) { + function render(props: ModalFuncProps) { confirmDialogProps = props; return createApp({ - parent: config.parentContext, + parent: (config as any).parentContext, data() { return { confirmDialogProps, vIf: true }; }, diff --git a/components/modal/index.jsx b/components/modal/index.tsx similarity index 74% rename from components/modal/index.jsx rename to components/modal/index.tsx index d113f63b5..eef26df6b 100644 --- a/components/modal/index.jsx +++ b/components/modal/index.tsx @@ -1,14 +1,15 @@ -import Modal, { destroyFns } from './Modal'; +import { App } from 'vue'; +import Modal, { destroyFns, ModalFuncProps } 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'; -// export { ActionButtonProps } from './ActionButton' -// export { ModalProps, ModalFuncProps } from './Modal' +export { IActionButtonProps as ActionButtonProps } from './ActionButton'; +export { ModalProps, ModalFuncProps } from './Modal'; -const info = function(props) { +const info = function(props: ModalFuncProps) { const config = { type: 'info', icon: , @@ -18,7 +19,7 @@ const info = function(props) { return modalConfirm(config); }; -const success = function(props) { +const success = function(props: ModalFuncProps) { const config = { type: 'success', icon: , @@ -28,7 +29,7 @@ const success = function(props) { return modalConfirm(config); }; -const error = function(props) { +const error = function(props: ModalFuncProps) { const config = { type: 'error', icon: , @@ -38,7 +39,7 @@ const error = function(props) { return modalConfirm(config); }; -const warning = function(props) { +const warning = function(props: ModalFuncProps) { const config = { type: 'warning', icon: , @@ -49,7 +50,7 @@ const warning = function(props) { }; const warn = warning; -const confirm = function confirmFn(props) { +const confirm = function confirmFn(props: ModalFuncProps) { const config = { type: 'confirm', okCancel: true, @@ -74,7 +75,7 @@ Modal.destroyAll = function destroyAllFn() { }; /* istanbul ignore next */ -Modal.install = function(app) { +Modal.install = function(app: App) { app.component(Modal.name, Modal); return app; }; diff --git a/components/modal/locale.ts b/components/modal/locale.ts index da0ef17d1..77c3d497b 100644 --- a/components/modal/locale.ts +++ b/components/modal/locale.ts @@ -10,7 +10,7 @@ let runtimeLocale = { ...defaultLocale.Modal, }; -export function changeConfirmLocale(newLocale?: any) { +export function changeConfirmLocale(newLocale?: ModalLocale) { if (newLocale) { runtimeLocale = { ...runtimeLocale,