mirror of
https://github.com/element-plus/element-plus.git
synced 2026-03-13 07:51:17 +08:00
242 lines
6.1 KiB
TypeScript
242 lines
6.1 KiB
TypeScript
import { createVNode, render } from 'vue'
|
|
import { isClient } from '@vueuse/core'
|
|
import {
|
|
debugWarn,
|
|
hasOwn,
|
|
isElement,
|
|
isFunction,
|
|
isObject,
|
|
isString,
|
|
isUndefined,
|
|
isVNode,
|
|
} from '@element-plus/utils'
|
|
import MessageBoxConstructor from './index.vue'
|
|
|
|
import type { AppContext, ComponentPublicInstance, VNode } from 'vue'
|
|
import type {
|
|
Action,
|
|
Callback,
|
|
ElMessageBoxOptions,
|
|
ElMessageBoxShortcutMethod,
|
|
IElMessageBox,
|
|
MessageBoxData,
|
|
MessageBoxState,
|
|
} from './message-box.type'
|
|
|
|
// component default merge props & data
|
|
|
|
const messageInstance = new Map<
|
|
ComponentPublicInstance<{ doClose: () => void }>, // marking doClose as function
|
|
{
|
|
options: any
|
|
callback: Callback | undefined
|
|
resolve: (res: any) => void
|
|
reject: (reason?: any) => void
|
|
}
|
|
>()
|
|
|
|
const getAppendToElement = (props: any): HTMLElement => {
|
|
let appendTo: HTMLElement | null = document.body
|
|
if (props.appendTo) {
|
|
if (isString(props.appendTo)) {
|
|
appendTo = document.querySelector<HTMLElement>(props.appendTo)
|
|
}
|
|
if (isElement(props.appendTo)) {
|
|
appendTo = props.appendTo
|
|
}
|
|
|
|
// should fallback to default value with a warning
|
|
if (!isElement(appendTo)) {
|
|
debugWarn(
|
|
'ElMessageBox',
|
|
'the appendTo option is not an HTMLElement. Falling back to document.body.'
|
|
)
|
|
appendTo = document.body
|
|
}
|
|
}
|
|
return appendTo
|
|
}
|
|
|
|
const initInstance = (
|
|
props: any,
|
|
container: HTMLElement,
|
|
appContext: AppContext | null = null
|
|
) => {
|
|
const vnode = createVNode(
|
|
MessageBoxConstructor,
|
|
props,
|
|
isFunction(props.message) || isVNode(props.message)
|
|
? {
|
|
default: isFunction(props.message)
|
|
? props.message
|
|
: () => props.message,
|
|
}
|
|
: null
|
|
)
|
|
vnode.appContext = appContext
|
|
render(vnode, container)
|
|
getAppendToElement(props).appendChild(container.firstElementChild!)
|
|
return vnode.component
|
|
}
|
|
|
|
const genContainer = () => {
|
|
return document.createElement('div')
|
|
}
|
|
|
|
const showMessage = (options: any, appContext?: AppContext | null) => {
|
|
const container = genContainer()
|
|
// Adding destruct method.
|
|
// when transition leaves emitting `vanish` evt. so that we can do the clean job.
|
|
options.onVanish = () => {
|
|
// not sure if this causes mem leak, need proof to verify that.
|
|
// maybe calling out like 1000 msg-box then close them all.
|
|
render(null, container)
|
|
messageInstance.delete(vm) // Remove vm to avoid mem leak.
|
|
// here we were suppose to call document.body.removeChild(container.firstElementChild)
|
|
// but render(null, container) did that job for us. so that we do not call that directly
|
|
}
|
|
|
|
options.onAction = (action: Action) => {
|
|
const currentMsg = messageInstance.get(vm)!
|
|
let resolve: Action | { value: string; action: Action }
|
|
if (options.showInput) {
|
|
resolve = { value: vm.inputValue, action }
|
|
} else {
|
|
resolve = action
|
|
}
|
|
if (options.callback) {
|
|
options.callback(resolve, instance.proxy)
|
|
} else {
|
|
if (action === 'cancel' || action === 'close') {
|
|
if (options.distinguishCancelAndClose && action !== 'cancel') {
|
|
currentMsg.reject('close')
|
|
} else {
|
|
currentMsg.reject('cancel')
|
|
}
|
|
} else {
|
|
currentMsg.resolve(resolve)
|
|
}
|
|
}
|
|
}
|
|
|
|
const instance = initInstance(options, container, appContext)!
|
|
|
|
// This is how we use message box programmably.
|
|
// Maybe consider releasing a template version?
|
|
// get component instance like v2.
|
|
const vm = instance.proxy as ComponentPublicInstance<
|
|
{
|
|
visible: boolean
|
|
doClose: () => void
|
|
} & MessageBoxState
|
|
>
|
|
|
|
for (const prop in options) {
|
|
if (hasOwn(options, prop) && !hasOwn(vm.$props, prop)) {
|
|
vm[prop as keyof ComponentPublicInstance] = options[prop]
|
|
}
|
|
}
|
|
|
|
// change visibility after everything is settled
|
|
vm.visible = true
|
|
return vm
|
|
}
|
|
|
|
async function MessageBox(
|
|
options: ElMessageBoxOptions,
|
|
appContext?: AppContext | null
|
|
): Promise<MessageBoxData>
|
|
function MessageBox(
|
|
options: ElMessageBoxOptions | string | VNode,
|
|
appContext: AppContext | null = null
|
|
): Promise<{ value: string; action: Action } | Action> {
|
|
if (!isClient) return Promise.reject()
|
|
let callback: Callback | undefined
|
|
if (isString(options) || isVNode(options)) {
|
|
options = {
|
|
message: options,
|
|
}
|
|
} else {
|
|
callback = options.callback
|
|
}
|
|
|
|
return new Promise((resolve, reject) => {
|
|
const vm = showMessage(
|
|
options,
|
|
appContext ?? (MessageBox as IElMessageBox)._context
|
|
)
|
|
// collect this vm in order to handle upcoming events.
|
|
messageInstance.set(vm, {
|
|
options,
|
|
callback,
|
|
resolve,
|
|
reject,
|
|
})
|
|
})
|
|
}
|
|
|
|
const MESSAGE_BOX_VARIANTS = ['alert', 'confirm', 'prompt'] as const
|
|
const MESSAGE_BOX_DEFAULT_OPTS: Record<
|
|
typeof MESSAGE_BOX_VARIANTS[number],
|
|
Partial<ElMessageBoxOptions>
|
|
> = {
|
|
alert: { closeOnPressEscape: false, closeOnClickModal: false },
|
|
confirm: { showCancelButton: true },
|
|
prompt: { showCancelButton: true, showInput: true },
|
|
}
|
|
|
|
MESSAGE_BOX_VARIANTS.forEach((boxType) => {
|
|
;(MessageBox as IElMessageBox)[boxType] = messageBoxFactory(
|
|
boxType
|
|
) as ElMessageBoxShortcutMethod
|
|
})
|
|
|
|
function messageBoxFactory(boxType: typeof MESSAGE_BOX_VARIANTS[number]) {
|
|
return (
|
|
message: string | VNode,
|
|
title: string | ElMessageBoxOptions,
|
|
options?: ElMessageBoxOptions,
|
|
appContext?: AppContext | null
|
|
) => {
|
|
let titleOrOpts = ''
|
|
if (isObject(title)) {
|
|
options = title as ElMessageBoxOptions
|
|
titleOrOpts = ''
|
|
} else if (isUndefined(title)) {
|
|
titleOrOpts = ''
|
|
} else {
|
|
titleOrOpts = title as string
|
|
}
|
|
|
|
return MessageBox(
|
|
Object.assign(
|
|
{
|
|
title: titleOrOpts,
|
|
message,
|
|
type: '',
|
|
...MESSAGE_BOX_DEFAULT_OPTS[boxType],
|
|
},
|
|
options,
|
|
{
|
|
boxType,
|
|
}
|
|
),
|
|
appContext
|
|
)
|
|
}
|
|
}
|
|
|
|
MessageBox.close = () => {
|
|
// instance.setupInstall.doClose()
|
|
// instance.setupInstall.state.visible = false
|
|
|
|
messageInstance.forEach((_, vm) => {
|
|
vm.doClose()
|
|
})
|
|
|
|
messageInstance.clear()
|
|
}
|
|
;(MessageBox as IElMessageBox)._context = null
|
|
|
|
export default MessageBox as IElMessageBox
|