diff --git a/packages/message-box/src/index.vue b/packages/message-box/src/index.vue index 127e4e9978..994b63424a 100644 --- a/packages/message-box/src/index.vue +++ b/packages/message-box/src/index.vue @@ -2,7 +2,7 @@
- {{ state.editorErrorMessage }} + {{ editorErrorMessage }}
- {{ state.cancelButtonText || t('el.messagebox.cancel') }} + {{ cancelButtonText || t('el.messagebox.cancel') }} - {{ state.confirmButtonText || t('el.messagebox.confirm') }} + {{ confirmButtonText || t('el.messagebox.confirm') }}
@@ -116,7 +116,8 @@ import { watch, reactive, ref, - isVNode, + toRefs, + PropType, } from 'vue' import ElButton from '@element-plus/button' import ElInput from '@element-plus/input' @@ -126,11 +127,11 @@ import { useModal, useLockScreen, useRestoreActive, usePreventGlobal } from '@el import { TrapFocus } from '@element-plus/directives' import PopupManager from '@element-plus/utils/popup-manager' import { on, off } from '@element-plus/utils/dom' -import { isString } from '@element-plus/utils/util' import { EVENT_CODE } from '@element-plus/utils/aria' +import { isValidComponentSize } from '@element-plus/utils/validators' -import type { ComponentPublicInstance, PropType, VNode } from 'vue' -import type { Action, MessageBoxState } from './message-box.type' +import type { ComponentPublicInstance } from 'vue' +import type { Action, MessageBoxState, MessageBoxType } from './message-box.type' const TypeMap: Indexable = { success: 'success', @@ -141,25 +142,32 @@ const TypeMap: Indexable = { export default defineComponent({ name: 'ElMessageBox', + directives: { + TrapFocus, + }, components: { ElButton, ElInput, ElOverlay, }, - directives: { - TrapFocus, - }, + inheritAttrs: false, props: { - beforeClose: { - type: Function as PropType<(action: Action, state: MessageBoxState, doClose: () => void) => any>, - default: undefined, + buttonSize: { + type: String as PropType, + validator: isValidComponentSize, }, - callback: Function, - cancelButtonText: { - type: String, + modal: { + type: Boolean, + default: true, + }, + lockScroll: { + type: Boolean, + default: true, + }, + showClose: { + type: Boolean, + default: true, }, - cancelButtonClass: String, - center: Boolean, closeOnClickModal: { type: Boolean, default: true, @@ -172,85 +180,54 @@ export default defineComponent({ type: Boolean, default: true, }, - confirmButtonText: { - type: String, + center: Boolean, + roundButton: { + default: false, + type: Boolean, }, - confirmButtonClass: String, container: { type: String, // default append to body default: 'body', }, - customClass: String, - dangerouslyUseHTMLString: Boolean, - distinguishCancelAndClose: Boolean, - iconClass: String, - inputPattern: { - type: Object as PropType, - default: () => undefined, - validator: (val: unknown) => (val instanceof RegExp || val === 'undefined'), + boxType: { + type: String as PropType, + default: '', }, - inputPlaceholder: { - type: String, - }, - inputType: { - type: String, - default: 'text', - }, - inputValue: { - type: String, - }, - inputValidator: { - type: Function as PropType<(...args: any[]) => boolean | string>, - default: null, - }, - inputErrorMessage: String, - lockScroll: { - type: Boolean, - default: true, - }, - message: { - type: [String, Object] as PropType, - validator: (val: unknown) => { - return isString(val) || isVNode(val) - }, - }, - modalFade: { // implement this feature - type: Boolean, - default: true, - }, - modalClass: String, - modal: { - type: Boolean, - default: true, - }, - roundButton: Boolean, - showCancelButton: Boolean, - showConfirmButton: { - type: Boolean, - default: true, - }, - showClose: { - type: Boolean, - default: true, - }, - type: String, - title: String, - showInput: Boolean, - zIndex: Number, }, emits: ['vanish', 'action'], setup(props, { emit }) { // const popup = usePopup(props, doClose) const visible = ref(false) // s represents state - const state = reactive({ + const state = reactive({ + beforeClose: null, + callback: null, + cancelButtonText: '', + cancelButtonClass: '', + confirmButtonText: '', + confirmButtonClass: '', + customClass: '', + dangerouslyUseHTMLString: false, + distinguishCancelAndClose: false, + iconClass: '', + inputPattern: null, + inputPlaceholder: '', + inputType: 'text', + inputValue: null, + inputValidator: null, + inputErrorMessage: '', + message: null, + modalFade: true, + modalClass: '', + showCancelButton: false, + showConfirmButton: true, + type: '', + title: undefined, + showInput: false, action: '' as Action, - inputValue: props.inputValue, confirmButtonLoading: false, cancelButtonLoading: false, - cancelButtonText: props.cancelButtonText, confirmButtonDisabled: false, - confirmButtonText: props.confirmButtonText, editorErrorMessage: '', // refer to: https://github.com/ElemeFE/element/commit/2999279ae34ef10c373ca795c87b020ed6753eed // seemed ok for now without this state. @@ -258,28 +235,28 @@ export default defineComponent({ validateError: false, zIndex: PopupManager.nextZIndex(), }) - const icon = computed(() => props.iconClass || (props.type && TypeMap[props.type] ? `el-icon-${TypeMap[props.type]}` : '')) - const hasMessage = computed(() => !!props.message) + const icon = computed(() => state.iconClass || (state.type && TypeMap[state.type] ? `el-icon-${TypeMap[state.type]}` : '')) + const hasMessage = computed(() => !!state.message) const inputRef = ref(null) const confirmRef = ref(null) - const confirmButtonClasses = computed(() => `el-button--primary ${props.confirmButtonClass}`) + const confirmButtonClasses = computed(() => `el-button--primary ${state.confirmButtonClass}`) watch(() => state.inputValue, async val => { await nextTick() - if (props.type === 'prompt' && val !== null) { + if (props.boxType === 'prompt' && val !== null) { validate() } }, { immediate: true }) watch(() => visible.value, val => { if (val) { - if (props.type === 'alert' || props.type === 'confirm') { + if (props.boxType === 'alert' || props.boxType === 'confirm') { nextTick().then(() => { confirmRef.value?.$el?.focus?.() }) } state.zIndex = PopupManager.nextZIndex() } - if (props.type !== 'prompt') return + if (props.boxType !== 'prompt') return if (val) { nextTick().then(() => { if (inputRef.value && inputRef.value.$el) { @@ -315,43 +292,43 @@ export default defineComponent({ const handleWrapperClick = () => { if (props.closeOnClickModal) { - handleAction(props.distinguishCancelAndClose ? 'close' : 'cancel') + handleAction(state.distinguishCancelAndClose ? 'close' : 'cancel') } } const handleInputEnter = () => { - if (props.inputType !== 'textarea') { + if (state.inputType !== 'textarea') { return handleAction('confirm') } } const handleAction = (action: Action) => { - if (props.type === 'prompt' && action === 'confirm' && !validate()) { + if (props.boxType === 'prompt' && action === 'confirm' && !validate()) { return } state.action = action - if (props.beforeClose) { - props.beforeClose?.(action, state, doClose) + if (state.beforeClose) { + state.beforeClose?.(action, state, doClose) } else { doClose() } } const validate = () => { - if (props.type === 'prompt') { - const inputPattern = props.inputPattern + if (props.boxType === 'prompt') { + const inputPattern = state.inputPattern if (inputPattern && !inputPattern.test(state.inputValue || '')) { - state.editorErrorMessage = props.inputErrorMessage || t('el.messagebox.error') + state.editorErrorMessage = state.inputErrorMessage || t('el.messagebox.error') state.validateError = true return false } - const inputValidator = props.inputValidator + const inputValidator = state.inputValidator if (typeof inputValidator === 'function') { const validateResult = inputValidator(state.inputValue) if (validateResult === false) { - state.editorErrorMessage = props.inputErrorMessage || t('el.messagebox.error') + state.editorErrorMessage = state.inputErrorMessage || t('el.messagebox.error') state.validateError = true return false } @@ -399,7 +376,7 @@ export default defineComponent({ useRestoreActive(visible) return { - state, + ...toRefs(state), visible, hasMessage, icon, diff --git a/packages/message-box/src/message-box.type.ts b/packages/message-box/src/message-box.type.ts index 66d8566f40..09d45a63c9 100644 --- a/packages/message-box/src/message-box.type.ts +++ b/packages/message-box/src/message-box.type.ts @@ -1,7 +1,8 @@ import type { VNode } from 'vue' export type Action = 'confirm' | 'close' | 'cancel' -export type MessageType = 'success' | 'warning' | 'info' | 'error' +export type MessageType = '' | 'success' | 'warning' | 'info' | 'error' +export type MessageBoxType = '' | 'prompt' | 'alert' | 'confirm' export type MessageBoxData = MessageBoxInputData & Action export interface MessageBoxInputData { value: string @@ -12,28 +13,13 @@ export interface MessageBoxInputValidator { (value: string): boolean | string } -export interface MessageBoxState { - action: Action - cancelButtonLoading: boolean - cancelButtonText: string - confirmButtonLoading: boolean - confirmButtonDisabled: boolean - confirmButtonText: string - editorErrorMessage: string - // isOnComposition: boolean temporary commented - inputValue: string - validateError: boolean - zIndex: number -} - -export declare class ElMessageBoxComponent { +export declare interface MessageBoxState { title: string message: string type: MessageType iconClass: string customClass: string showInput: boolean - showClose: boolean inputValue: string inputPlaceholder: string inputType: string @@ -53,7 +39,16 @@ export declare class ElMessageBoxComponent { cancelButtonClass: string editorErrorMessage: string - close(): any + beforeClose: null | ((action: Action, instance: MessageBoxState, done: () => void) => void) + callback: null | Callback + distinguishCancelAndClose: boolean + modalFade: boolean + modalClass: string + // refer to: https://github.com/ElemeFE/element/commit/2999279ae34ef10c373ca795c87b020ed6753eed + // seemed ok for now without this state. + // isOnComposition: false, // temporary remove + validateError: boolean + zIndex: number } export type Callback = @@ -66,7 +61,7 @@ export interface ElMessageBoxOptions { /** Callback before MessageBox closes, and it will prevent MessageBox from closing */ beforeClose?: ( action: Action, - instance: ElMessageBoxComponent, + instance: MessageBoxState, done: () => void, ) => void @@ -100,6 +95,9 @@ export interface ElMessageBoxOptions { /** Message type, used for icon display */ type?: MessageType + /** Message box type */ + boxType?: MessageBoxType + /** Custom icon's class */ iconClass?: string diff --git a/packages/message-box/src/messageBox.ts b/packages/message-box/src/messageBox.ts index f18d170eda..ea42cb0535 100644 --- a/packages/message-box/src/messageBox.ts +++ b/packages/message-box/src/messageBox.ts @@ -1,4 +1,4 @@ -import { h, render } from 'vue' +import { h, watch, render } from 'vue' import MessageBoxConstructor from './index.vue' import isServer from '@element-plus/utils/isServer' import { isVNode, isString } from '@element-plus/utils/util' @@ -55,7 +55,7 @@ const showMessage = (options: any) => { const currentMsg = messageInstance.get(vm) let resolve: Action | { value: string; action: Action; } if (options.showInput) { - resolve = { value: vm.state.inputValue, action } + resolve = { value: vm.inputValue, action } } else { resolve = action } @@ -81,14 +81,26 @@ const showMessage = (options: any) => { // get component instance like v2. const vm = instance.proxy as ComponentPublicInstance<{ visible: boolean - state: MessageBoxState doClose: () => void - }> + } & MessageBoxState> - if (isVNode(options.message)) { - // Override slots since message is vnode type. - instance.slots.default = () => [options.message] + for (const prop in options) { + if (options.hasOwnProperty(prop) && !vm.$props.hasOwnProperty(prop)) { + vm[prop] = options[prop] + } } + + watch(() => vm.message, (newVal, oldVal) => { + if (isVNode(newVal)) { + // Override slots since message is vnode type. + instance.slots.default = () => [newVal] + } else if(isVNode(oldVal) && !isVNode(newVal)){ + delete instance.slots.default + } + }, { + immediate: true, + }) + // change visibility after everything is settled vm.visible = true return vm @@ -137,11 +149,14 @@ MessageBox.alert = ( { title: title, message: message, - type: 'alert', + type: '', closeOnPressEscape: false, closeOnClickModal: false, }, options, + { + boxType: 'alert', + }, ), ) } @@ -162,10 +177,13 @@ MessageBox.confirm = ( { title: title, message: message, - type: 'confirm', + type: '', showCancelButton: true, }, options, + { + boxType: 'confirm', + }, ), ) } @@ -188,9 +206,12 @@ MessageBox.prompt = ( message: message, showCancelButton: true, showInput: true, - type: 'prompt', + type: '', }, options, + { + boxType: 'prompt', + }, ), ) } diff --git a/website/docs/en-US/message-box.md b/website/docs/en-US/message-box.md index 79b9660295..264c9c4eb7 100644 --- a/website/docs/en-US/message-box.md +++ b/website/docs/en-US/message-box.md @@ -327,3 +327,4 @@ The corresponding methods are: `ElMessageBox`, `ElMessageBox.alert`, `ElMessageB | inputErrorMessage | error message when validation fails | string | — | Illegal input | | center | whether to align the content in center | boolean | — | false | | roundButton | whether to use round button | boolean | — | false | +| buttonSize | custom size of confirm and cancel buttons | string | mini / small / medium / large | small | diff --git a/website/docs/es/message-box.md b/website/docs/es/message-box.md index 5d11571dda..78f092e2a6 100644 --- a/website/docs/es/message-box.md +++ b/website/docs/es/message-box.md @@ -330,3 +330,4 @@ Los métodos correspondientes: `ElMessageBox`, `ElMessageBox.alert`, `ElMessageB | inputErrorMessage | mensaje de error cuando la validación falla | string | — | Illegal input | | center | utilizado para alinear el contenido al centro | boolean | — | false | | roundButton | utilizado para redondear el botón | boolean | — | false | +| buttonSize | custom size of confirm and cancel buttons | string | mini / small / medium / large | small | diff --git a/website/docs/fr-FR/message-box.md b/website/docs/fr-FR/message-box.md index 4ca4fed275..d6908b9f71 100644 --- a/website/docs/fr-FR/message-box.md +++ b/website/docs/fr-FR/message-box.md @@ -329,3 +329,4 @@ Les méthodes correspondantes sont: `ElMessageBox`, `ElMessageBox.alert`, `ElMes | inputErrorMessage | Message d'erreur lorsque la validation échoue. | string | — | Illegal input | | center | Si le contenu doit être centré. | boolean | — | false | | roundButton | Si le bouton doit être rond. | boolean | — | false | +| buttonSize | custom size of confirm and cancel buttons | string | mini / small / medium / large | small | diff --git a/website/docs/jp/message-box.md b/website/docs/jp/message-box.md index 5ced6f3faf..b034961fc0 100644 --- a/website/docs/jp/message-box.md +++ b/website/docs/jp/message-box.md @@ -326,3 +326,4 @@ import { ElMessageBox } from 'element-plus'; | inputErrorMessage | バリデーション失敗時のエラーメッセージ | string | — | Illegal input | | center | コンテンツを中央に配置するかどうか | boolean | — | false | | roundButton | 丸いボタンを使うかどうか | boolean | — | false | +| buttonSize | custom size of confirm and cancel buttons | string | mini / small / medium / large | small | diff --git a/website/docs/zh-CN/message-box.md b/website/docs/zh-CN/message-box.md index d0ca107bbb..f17a3a78d1 100644 --- a/website/docs/zh-CN/message-box.md +++ b/website/docs/zh-CN/message-box.md @@ -325,3 +325,4 @@ import { ElMessageBox } from 'element-plus'; | inputErrorMessage | 校验未通过时的提示文本 | string | — | 输入的数据不合法! | | center | 是否居中布局 | boolean | — | false | | roundButton | 是否使用圆角按钮 | boolean | — | false | +| buttonSize | 自定义确认按钮及取消按钮的大小 | string | mini / small / medium / large | small |