123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241 |
- import { createVNode, render } from 'vue'
- import { isClient } from '@vueuse/core'
- import {
- debugWarn,
- hasOwn,
- isElement,
- isFunction,
- isObject,
- isString,
- isUndefined,
- isVNode,
- } from '@kankan-components/utils'
- import MessageBoxConstructor from './index.vue'
- import type { AppContext, ComponentPublicInstance, VNode } from 'vue'
- import type {
- Action,
- Callback,
- IKkMessageBox,
- KkMessageBoxOptions,
- KkMessageBoxShortcutMethod,
- 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: KkMessageBoxOptions,
- appContext?: AppContext | null
- ): Promise<MessageBoxData>
- function MessageBox(
- options: KkMessageBoxOptions | 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 IKkMessageBox)._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<KkMessageBoxOptions>
- > = {
- alert: { closeOnPressEscape: false, closeOnClickModal: false },
- confirm: { showCancelButton: true },
- prompt: { showCancelButton: true, showInput: true },
- }
- MESSAGE_BOX_VARIANTS.forEach((boxType) => {
- ;(MessageBox as IKkMessageBox)[boxType] = messageBoxFactory(
- boxType
- ) as KkMessageBoxShortcutMethod
- })
- function messageBoxFactory(boxType: (typeof MESSAGE_BOX_VARIANTS)[number]) {
- return (
- message: string | VNode,
- title: string | KkMessageBoxOptions,
- options?: KkMessageBoxOptions,
- appContext?: AppContext | null
- ) => {
- let titleOrOpts = ''
- if (isObject(title)) {
- options = title as KkMessageBoxOptions
- 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 IKkMessageBox)._context = null
- export default MessageBox as IKkMessageBox
|