diff --git a/packages/components/message/__tests__/message-manager.spec.ts b/packages/components/message/__tests__/message-manager.spec.ts
index cc37e88c59..d82cf83356 100644
--- a/packages/components/message/__tests__/message-manager.spec.ts
+++ b/packages/components/message/__tests__/message-manager.spec.ts
@@ -1,7 +1,7 @@
import { nextTick } from 'vue'
import { getStyle } from '@element-plus/utils/dom'
import { rAF } from '@element-plus/test-utils/tick'
-import Message from '../src/message'
+import Message from '../src/message-method'
jest.useFakeTimers()
const selector = '.el-message'
diff --git a/packages/components/message/__tests__/message.spec.ts b/packages/components/message/__tests__/message.spec.ts
index d545e728cf..730c028362 100644
--- a/packages/components/message/__tests__/message.spec.ts
+++ b/packages/components/message/__tests__/message.spec.ts
@@ -1,9 +1,8 @@
import { h, nextTick } from 'vue'
-import * as domExports from '@element-plus/utils/dom'
import makeMount from '@element-plus/test-utils/make-mount'
import { rAF } from '@element-plus/test-utils/tick'
import { EVENT_CODE } from '@element-plus/utils/aria'
-import Message from '../src/index.vue'
+import Message from '../src/message.vue'
import type { ComponentPublicInstance, CSSProperties } from 'vue'
@@ -76,19 +75,6 @@ describe('Message.vue', () => {
})
})
- describe('lifecycle', () => {
- test('should add keydown event lister on mount', () => {
- jest.spyOn(domExports, 'on')
- jest.spyOn(domExports, 'off')
- const wrapper = _mount({
- slots: { default: AXIOM },
- })
- expect(domExports.on).toHaveBeenCalled()
- wrapper.unmount()
- expect(domExports.off).toHaveBeenCalled()
- })
- })
-
describe('Message.type', () => {
test('should be able to render typed messages', () => {
for (const type of ['success', 'warning', 'info', 'error'] as const) {
diff --git a/packages/components/message/index.ts b/packages/components/message/index.ts
index cef8d5ad11..91ac3af3e8 100644
--- a/packages/components/message/index.ts
+++ b/packages/components/message/index.ts
@@ -1,15 +1,8 @@
-import Message from './src/message'
+import { withInstallFunction } from '@element-plus/utils/with-install'
-import type { App } from 'vue'
-import type { SFCWithInstall } from '@element-plus/utils/types'
+import Message from './src/message-method'
-const _Message = Message as SFCWithInstall
+export const ElMessage = withInstallFunction(Message, '$message')
+export default ElMessage
-_Message.install = (app: App) => {
- app.config.globalProperties.$message = _Message
-}
-
-export default _Message
-export const ElMessage = _Message
-
-export * from './src/types'
+export * from './src/message'
diff --git a/packages/components/message/src/message-method.ts b/packages/components/message/src/message-method.ts
new file mode 100644
index 0000000000..74c795869f
--- /dev/null
+++ b/packages/components/message/src/message-method.ts
@@ -0,0 +1,120 @@
+import { createVNode, render } from 'vue'
+import { isVNode } from '@element-plus/utils/util'
+import PopupManager from '@element-plus/utils/popup-manager'
+import isServer from '@element-plus/utils/isServer'
+import MessageConstructor from './message.vue'
+import { messageTypes } from './message'
+
+import type { MessagePartial, MessageQueue, MessageProps } from './message'
+import type { ComponentPublicInstance, VNode } from 'vue'
+
+const instances: MessageQueue = []
+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 message: MessagePartial = function (options = {}) {
+ if (isServer) return { close: () => undefined }
+
+ if (typeof options === 'string' || isVNode(options)) {
+ options = { message: options }
+ }
+
+ let verticalOffset = options.offset || 20
+ instances.forEach(({ vm }) => {
+ verticalOffset += (vm.el?.offsetHeight || 0) + 16
+ })
+ verticalOffset += 16
+
+ const id = `message_${seed++}`
+ const userOnClose = options.onClose
+ const props: Partial = {
+ zIndex: PopupManager.nextZIndex(),
+ offset: verticalOffset,
+ ...options,
+ id,
+ onClose: () => {
+ close(id, userOnClose)
+ },
+ }
+
+ const container = document.createElement('div')
+
+ container.className = `container_${id}`
+
+ const message = props.message
+ const vm = createVNode(
+ MessageConstructor,
+ props,
+ isVNode(props.message) ? { default: () => message } : null
+ )
+
+ // clean message element preventing mem leak
+ vm.props!.onDestroy = () => {
+ render(null, container)
+ // 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(vm, container)
+ // instances will remove this item when close function gets called. So we do not need to worry about it.
+ instances.push({ vm })
+ document.body.appendChild(container.firstElementChild!)
+
+ return {
+ // 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.component!.proxy as ComponentPublicInstance<{ visible: boolean }>
+ ).visible = false),
+ }
+}
+
+messageTypes.forEach((type) => {
+ message[type] = (options = {}) => {
+ if (typeof options === 'string' || isVNode(options)) {
+ options = {
+ message: options,
+ }
+ }
+ return message({
+ ...options,
+ type,
+ })
+ }
+})
+
+export function close(id: string, userOnClose?: (vm: VNode) => void): void {
+ const idx = instances.findIndex(({ vm }) => id === vm.component!.props.id)
+ if (idx === -1) return
+
+ const { vm } = instances[idx]
+ if (!vm) return
+ userOnClose?.(vm)
+
+ const removedHeight = vm.el!.offsetHeight
+ instances.splice(idx, 1)
+
+ // adjust other instances vertical offset
+ const len = instances.length
+ if (len < 1) return
+ for (let i = idx; i < len; i++) {
+ const pos =
+ parseInt(instances[i].vm.el!.style['top'], 10) - removedHeight - 16
+
+ instances[i].vm.component!.props.offset = pos
+ }
+}
+
+export function closeAll(): void {
+ for (let i = instances.length - 1; i >= 0; i--) {
+ const instance = instances[i].vm.component as any
+ instance.ctx.close()
+ }
+}
+
+message.closeAll = closeAll
+
+export default message
diff --git a/packages/components/message/src/message.ts b/packages/components/message/src/message.ts
index ab92f77e05..68db13643e 100644
--- a/packages/components/message/src/message.ts
+++ b/packages/components/message/src/message.ts
@@ -1,137 +1,90 @@
-import { createVNode, render } from 'vue'
-import { isVNode } from '@element-plus/utils/util'
-import PopupManager from '@element-plus/utils/popup-manager'
-import isServer from '@element-plus/utils/isServer'
-import MessageConstructor from './index.vue'
+import { buildProp } from '@element-plus/utils/props'
-import type { ComponentPublicInstance } from 'vue'
-import type {
- IMessage,
- MessageQueue,
- IMessageOptions,
- MessageVM,
- IMessageHandle,
- MessageParams,
-} from './types'
+import type { VNode, ExtractPropTypes } from 'vue'
-const instances: MessageQueue = []
-let seed = 1
+export const messageTypes = ['success', 'info', 'warning', 'error'] as const
-// TODO: Since Notify.ts is basically the same like this file. So we could do some encapsulation against them to
-// reduce code duplication.
-const Message: IMessage = function (
- opts: MessageParams = {} as MessageParams
-): IMessageHandle {
- if (isServer) return
+export const messageProps = {
+ customClass: {
+ type: String,
+ default: '',
+ },
+ center: {
+ type: Boolean,
+ default: false,
+ },
+ dangerouslyUseHTMLString: {
+ type: Boolean,
+ default: false,
+ },
+ duration: {
+ type: Number,
+ default: 3000,
+ },
+ iconClass: {
+ type: String,
+ default: '',
+ },
+ id: {
+ type: String,
+ default: '',
+ },
+ message: buildProp({
+ type: [String, Object],
+ default: '',
+ }),
+ onClose: buildProp<() => void>({
+ type: Function,
+ required: false,
+ }),
+ showClose: {
+ type: Boolean,
+ default: false,
+ },
+ type: buildProp({
+ type: String,
+ values: messageTypes,
+ default: 'info',
+ } as const),
+ offset: {
+ type: Number,
+ default: 20,
+ },
+ zIndex: {
+ type: Number,
+ default: 0,
+ },
+} as const
+export type MessageProps = ExtractPropTypes
- if (typeof opts === 'string') {
- opts = {
- message: opts,
- }
- }
+export const messageEmits = {
+ destroy: () => true,
+}
+export type MessageEmits = typeof messageEmits
- let options: IMessageOptions = opts
+export type MessageOptions = Omit
+export type MessageOptionsTyped = Omit
- let verticalOffset = opts.offset || 20
- instances.forEach(({ vm }) => {
- verticalOffset += (vm.el.offsetHeight || 0) + 16
- })
- verticalOffset += 16
-
- const id = `message_${seed++}`
- const userOnClose = options.onClose
-
- options = {
- ...options,
- onClose: () => {
- close(id, userOnClose)
- },
- offset: verticalOffset,
- id,
- zIndex: PopupManager.nextZIndex(),
- }
-
- const container = document.createElement('div')
-
- container.className = `container_${id}`
-
- const message = options.message
- const vm = createVNode(
- MessageConstructor,
- options,
- isVNode(options.message) ? { default: () => message } : null
- )
-
- // clean message element preventing mem leak
- vm.props.onDestroy = () => {
- render(null, container)
- // 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(vm, container)
- // instances will remove this item when close function gets called. So we do not need to worry about it.
- instances.push({ vm })
- document.body.appendChild(container.firstElementChild)
-
- return {
- // 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.component.proxy as ComponentPublicInstance<{ visible: boolean }>
- ).visible = false),
- }
-} as any
-
-export function close(id: string, userOnClose?: (vm: MessageVM) => void): void {
- const idx = instances.findIndex(({ vm }) => {
- const { id: _id } = vm.component.props
- return id === _id
- })
- if (idx === -1) {
- return
- }
-
- const { vm } = instances[idx]
- if (!vm) return
- userOnClose?.(vm)
-
- const removedHeight = vm.el.offsetHeight
- instances.splice(idx, 1)
-
- // adjust other instances vertical offset
- const len = instances.length
- if (len < 1) return
- for (let i = idx; i < len; i++) {
- const pos =
- parseInt(instances[i].vm.el.style['top'], 10) - removedHeight - 16
-
- instances[i].vm.component.props.offset = pos
- }
+export interface MessageHandle {
+ close: () => void
}
-export function closeAll(): void {
- for (let i = instances.length - 1; i >= 0; i--) {
- const instance = instances[i].vm.component as any
- instance.ctx.close()
- }
+export type MessageParams = Partial | string | VNode
+export type MessageParamsTyped = Partial | string | VNode
+
+export interface MessagePartial {
+ (options?: MessageParams): MessageHandle
+ closeAll(): void
+
+ success?: (options?: MessageParamsTyped) => MessageHandle
+ warning?: (options?: MessageParamsTyped) => MessageHandle
+ info?: (options?: MessageParamsTyped) => MessageHandle
+ error?: (options?: MessageParamsTyped) => MessageHandle
+}
+export type Message = Required
+
+type MessageQueueItem = {
+ vm: VNode
}
-;(['success', 'warning', 'info', 'error'] as const).forEach((type) => {
- Message[type] = (options) => {
- if (typeof options === 'string') {
- options = {
- message: options,
- type,
- }
- } else {
- options.type = type
- }
- return Message(options)
- }
-})
-
-Message.closeAll = closeAll
-export default Message
+export type MessageQueue = MessageQueueItem[]
diff --git a/packages/components/message/src/index.vue b/packages/components/message/src/message.vue
similarity index 51%
rename from packages/components/message/src/index.vue
rename to packages/components/message/src/message.vue
index f74e2f5d08..f2cc2c100e 100644
--- a/packages/components/message/src/index.vue
+++ b/packages/components/message/src/message.vue
@@ -28,7 +28,6 @@
{{ message }}
-