123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185 |
- import { createVNode, render } from 'vue'
- import {
- debugWarn,
- isClient,
- isElement,
- isFunction,
- isNumber,
- isString,
- isVNode,
- } from '@kankan-components/utils'
- import { messageConfig } from '@kankan-components/components/basic/config-provider'
- import MessageConstructor from './message.vue'
- import { messageDefaults, messageTypes } from './message'
- import { instances } from './instance'
- import type { MessageContext } from './instance'
- import type { AppContext } from 'vue'
- import type {
- Message,
- MessageFn,
- MessageHandler,
- MessageOptions,
- MessageParams,
- MessageParamsNormalized,
- messageType,
- } from './message'
- let seed = 1
- // TODO: Since Notify.ts is basically the same like this file. So we could do some encapsulation against them to reduce code duplication.
- const normalizeOptions = (params?: MessageParams) => {
- const options: MessageOptions =
- !params || isString(params) || isVNode(params) || isFunction(params)
- ? { message: params }
- : params
- const normalized = {
- ...messageDefaults,
- ...options,
- }
- if (!normalized.appendTo) {
- normalized.appendTo = document.body
- } else if (isString(normalized.appendTo)) {
- let appendTo = document.querySelector<HTMLElement>(normalized.appendTo)
- // should fallback to default value with a warning
- if (!isElement(appendTo)) {
- debugWarn(
- 'KkMessage',
- 'the appendTo option is not an HTMLElement. Falling back to document.body.'
- )
- appendTo = document.body
- }
- normalized.appendTo = appendTo
- }
- return normalized as MessageParamsNormalized
- }
- const closeMessage = (instance: MessageContext) => {
- const idx = instances.indexOf(instance)
- if (idx === -1) return
- instances.splice(idx, 1)
- const { handler } = instance
- handler.close()
- }
- const createMessage = (
- { appendTo, ...options }: MessageParamsNormalized,
- context?: AppContext | null
- ): MessageContext => {
- const id = `message_${seed++}`
- const userOnClose = options.onClose
- const container = document.createElement('div')
- const props = {
- ...options,
- // now the zIndex will be used inside the message.vue component instead of here.
- // zIndex: nextIndex() + options.zIndex
- id,
- onClose: () => {
- userOnClose?.()
- closeMessage(instance)
- },
- // clean message element preventing mem leak
- onDestroy: () => {
- // since the element is destroy, then the VNode should be collected by GC as well
- // we do not want cause any mem leak because we have returned vm as a reference to users
- // so that we manually set it to false.
- render(null, container)
- },
- }
- const vnode = createVNode(
- MessageConstructor,
- props,
- isFunction(props.message) || isVNode(props.message)
- ? {
- default: isFunction(props.message)
- ? props.message
- : () => props.message,
- }
- : null
- )
- vnode.appContext = context || message._context
- render(vnode, container)
- // instances will remove this item when close function gets called. So we do not need to worry about it.
- appendTo.appendChild(container.firstElementChild!)
- const vm = vnode.component!
- const handler: MessageHandler = {
- // instead of calling the onClose function directly, setting this value so that we can have the full lifecycle
- // for out component, so that all closing steps will not be skipped.
- close: () => {
- vm.exposed!.visible.value = false
- },
- }
- const instance: MessageContext = {
- id,
- vnode,
- vm,
- handler,
- props: (vnode.component as any).props,
- }
- return instance
- }
- const message: MessageFn &
- Partial<Message> & { _context: AppContext | null } = (
- options = {},
- context
- ) => {
- if (!isClient) return { close: () => undefined }
- if (isNumber(messageConfig.max) && instances.length >= messageConfig.max) {
- return { close: () => undefined }
- }
- const normalized = normalizeOptions(options)
- if (normalized.grouping && instances.length) {
- const instance = instances.find(
- ({ vnode: vm }) => vm.props?.message === normalized.message
- )
- if (instance) {
- instance.props.repeatNum += 1
- instance.props.type = normalized.type
- return instance.handler
- }
- }
- const instance = createMessage(normalized, context)
- instances.push(instance)
- return instance.handler
- }
- messageTypes.forEach((type) => {
- message[type] = (options = {}, appContext) => {
- const normalized = normalizeOptions(options)
- return message({ ...normalized, type }, appContext)
- }
- })
- export function closeAll(type?: messageType): void {
- for (const instance of instances) {
- if (!type || type === instance.props.type) {
- instance.handler.close()
- }
- }
- }
- message.closeAll = closeAll
- message._context = null
- export default message as Message
|