method.ts 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. import { createVNode, render } from 'vue'
  2. import {
  3. debugWarn,
  4. isClient,
  5. isElement,
  6. isFunction,
  7. isNumber,
  8. isString,
  9. isVNode,
  10. } from '@kankan-components/utils'
  11. import { messageConfig } from '@kankan-components/components/basic/config-provider'
  12. import MessageConstructor from './message.vue'
  13. import { messageDefaults, messageTypes } from './message'
  14. import { instances } from './instance'
  15. import type { MessageContext } from './instance'
  16. import type { AppContext } from 'vue'
  17. import type {
  18. Message,
  19. MessageFn,
  20. MessageHandler,
  21. MessageOptions,
  22. MessageParams,
  23. MessageParamsNormalized,
  24. messageType,
  25. } from './message'
  26. let seed = 1
  27. // TODO: Since Notify.ts is basically the same like this file. So we could do some encapsulation against them to reduce code duplication.
  28. const normalizeOptions = (params?: MessageParams) => {
  29. const options: MessageOptions =
  30. !params || isString(params) || isVNode(params) || isFunction(params)
  31. ? { message: params }
  32. : params
  33. const normalized = {
  34. ...messageDefaults,
  35. ...options,
  36. }
  37. if (!normalized.appendTo) {
  38. normalized.appendTo = document.body
  39. } else if (isString(normalized.appendTo)) {
  40. let appendTo = document.querySelector<HTMLElement>(normalized.appendTo)
  41. // should fallback to default value with a warning
  42. if (!isElement(appendTo)) {
  43. debugWarn(
  44. 'KkMessage',
  45. 'the appendTo option is not an HTMLElement. Falling back to document.body.'
  46. )
  47. appendTo = document.body
  48. }
  49. normalized.appendTo = appendTo
  50. }
  51. return normalized as MessageParamsNormalized
  52. }
  53. const closeMessage = (instance: MessageContext) => {
  54. const idx = instances.indexOf(instance)
  55. if (idx === -1) return
  56. instances.splice(idx, 1)
  57. const { handler } = instance
  58. handler.close()
  59. }
  60. const createMessage = (
  61. { appendTo, ...options }: MessageParamsNormalized,
  62. context?: AppContext | null
  63. ): MessageContext => {
  64. const id = `message_${seed++}`
  65. const userOnClose = options.onClose
  66. const container = document.createElement('div')
  67. const props = {
  68. ...options,
  69. // now the zIndex will be used inside the message.vue component instead of here.
  70. // zIndex: nextIndex() + options.zIndex
  71. id,
  72. onClose: () => {
  73. userOnClose?.()
  74. closeMessage(instance)
  75. },
  76. // clean message element preventing mem leak
  77. onDestroy: () => {
  78. // since the element is destroy, then the VNode should be collected by GC as well
  79. // we do not want cause any mem leak because we have returned vm as a reference to users
  80. // so that we manually set it to false.
  81. render(null, container)
  82. },
  83. }
  84. const vnode = createVNode(
  85. MessageConstructor,
  86. props,
  87. isFunction(props.message) || isVNode(props.message)
  88. ? {
  89. default: isFunction(props.message)
  90. ? props.message
  91. : () => props.message,
  92. }
  93. : null
  94. )
  95. vnode.appContext = context || message._context
  96. render(vnode, container)
  97. // instances will remove this item when close function gets called. So we do not need to worry about it.
  98. appendTo.appendChild(container.firstElementChild!)
  99. const vm = vnode.component!
  100. const handler: MessageHandler = {
  101. // instead of calling the onClose function directly, setting this value so that we can have the full lifecycle
  102. // for out component, so that all closing steps will not be skipped.
  103. close: () => {
  104. vm.exposed!.visible.value = false
  105. },
  106. }
  107. const instance: MessageContext = {
  108. id,
  109. vnode,
  110. vm,
  111. handler,
  112. props: (vnode.component as any).props,
  113. }
  114. return instance
  115. }
  116. const message: MessageFn &
  117. Partial<Message> & { _context: AppContext | null } = (
  118. options = {},
  119. context
  120. ) => {
  121. if (!isClient) return { close: () => undefined }
  122. if (isNumber(messageConfig.max) && instances.length >= messageConfig.max) {
  123. return { close: () => undefined }
  124. }
  125. const normalized = normalizeOptions(options)
  126. if (normalized.grouping && instances.length) {
  127. const instance = instances.find(
  128. ({ vnode: vm }) => vm.props?.message === normalized.message
  129. )
  130. if (instance) {
  131. instance.props.repeatNum += 1
  132. instance.props.type = normalized.type
  133. return instance.handler
  134. }
  135. }
  136. const instance = createMessage(normalized, context)
  137. instances.push(instance)
  138. return instance.handler
  139. }
  140. messageTypes.forEach((type) => {
  141. message[type] = (options = {}, appContext) => {
  142. const normalized = normalizeOptions(options)
  143. return message({ ...normalized, type }, appContext)
  144. }
  145. })
  146. export function closeAll(type?: messageType): void {
  147. for (const instance of instances) {
  148. if (!type || type === instance.props.type) {
  149. instance.handler.close()
  150. }
  151. }
  152. }
  153. message.closeAll = closeAll
  154. message._context = null
  155. export default message as Message