From 11d3db586c51fca64b2f417aabe924295861e7a4 Mon Sep 17 00:00:00 2001 From: JeremyWuuuuu <15975785+JeremyWuuuuu@users.noreply.github.com> Date: Sat, 5 Mar 2022 19:40:35 +0800 Subject: [PATCH] feat(components): [el-messagebox] add context for message box (#6379) - Add context for message box - Update documentation based on changes --- docs/en-US/component/message-box.md | 16 ++ .../message-box/__tests__/message-box.spec.ts | 21 ++- packages/components/message-box/index.ts | 1 + .../message-box/src/message-box.type.ts | 15 +- .../components/message-box/src/messageBox.ts | 160 ++++++++---------- 5 files changed, 119 insertions(+), 94 deletions(-) diff --git a/docs/en-US/component/message-box.md b/docs/en-US/component/message-box.md index 14ab917a72..989d317b0c 100644 --- a/docs/en-US/component/message-box.md +++ b/docs/en-US/component/message-box.md @@ -114,6 +114,22 @@ If Element Plus is fully imported, it will add the following global methods for - `$confirm(message, title, options)` or `$confirm(message, options)` - `$prompt(message, title, options)` or `$prompt(message, options)` +## App context inheritance > 2.0.4 + +Now message box accepts a `context` as second (forth if you are using message box variants) parameter of the message constructor which allows you to inject current app's context to message which allows you to inherit all the properties of the app. + +```ts +import { getCurrentInstance } from 'vue' +import { ElMessageBox } from 'element-plus' + +// in your setup method +const { appContext } = getCurrentInstance()! +// You can pass it like: +ElMessageBox({}, appContext) +// or if you are using variants +ElMessageBox.alert('Hello world!', 'Title', {}, appContext) +``` + ## Local import If you prefer importing `MessageBox` on demand: diff --git a/packages/components/message-box/__tests__/message-box.spec.ts b/packages/components/message-box/__tests__/message-box.spec.ts index 89c256cac1..c63ee0a954 100644 --- a/packages/components/message-box/__tests__/message-box.spec.ts +++ b/packages/components/message-box/__tests__/message-box.spec.ts @@ -1,10 +1,13 @@ +import { markRaw } from 'vue' import { mount } from '@vue/test-utils' import { rAF } from '@element-plus/test-utils/tick' import { triggerNativeCompositeClick } from '@element-plus/test-utils/composite-click' -import { QuestionFilled } from '@element-plus/icons-vue' +import { QuestionFilled as QuestionFilledIcon } from '@element-plus/icons-vue' import MessageBox from '../src/messageBox' +import { ElMessageBox } from '..' const selector = '.el-overlay' +const QuestionFilled = markRaw(QuestionFilledIcon) const _mount = (invoker: () => void) => { return mount( @@ -240,4 +243,20 @@ describe('MessageBox', () => { expect(msgAction).toEqual('cancel') }) }) + describe('context inheritance', () => { + it('should globally inherit context correctly', () => { + expect(ElMessageBox._context).toBe(null) + const testContext = { + config: { + globalProperties: {}, + }, + _context: {}, + } + ElMessageBox.install?.(testContext as any) + expect(ElMessageBox._context).not.toBe(null) + expect(ElMessageBox._context).toBe(testContext._context) + // clean up + ElMessageBox._context = null + }) + }) }) diff --git a/packages/components/message-box/index.ts b/packages/components/message-box/index.ts index 6ebed56710..411f3161c6 100644 --- a/packages/components/message-box/index.ts +++ b/packages/components/message-box/index.ts @@ -6,6 +6,7 @@ import type { SFCWithInstall } from '@element-plus/utils' const _MessageBox = MessageBox as SFCWithInstall _MessageBox.install = (app: App) => { + _MessageBox._context = app._context app.config.globalProperties.$msgbox = _MessageBox app.config.globalProperties.$messageBox = _MessageBox app.config.globalProperties.$alert = _MessageBox.alert diff --git a/packages/components/message-box/src/message-box.type.ts b/packages/components/message-box/src/message-box.type.ts index 969c4269c8..6e1ca1e48c 100644 --- a/packages/components/message-box/src/message-box.type.ts +++ b/packages/components/message-box/src/message-box.type.ts @@ -1,4 +1,4 @@ -import type { CSSProperties, VNode, Component } from 'vue' +import type { AppContext, CSSProperties, VNode, Component } from 'vue' import type { ComponentSize } from '@element-plus/constants' type MessageType = '' | 'success' | 'warning' | 'info' | 'error' @@ -169,19 +169,26 @@ export interface ElMessageBoxOptions { export type ElMessageBoxShortcutMethod = (( message: ElMessageBoxOptions['message'], title: ElMessageBoxOptions['title'], - options?: ElMessageBoxOptions + options?: ElMessageBoxOptions, + appContext?: AppContext | null ) => Promise) & (( message: ElMessageBoxOptions['message'], - options?: ElMessageBoxOptions + options?: ElMessageBoxOptions, + appContext?: AppContext | null ) => Promise) export interface IElMessageBox { + _context: AppContext | null + /** Show a message box */ // (message: string, title?: string, type?: string): Promise /** Show a message box */ - (options: ElMessageBoxOptions): Promise + ( + options: ElMessageBoxOptions, + appContext?: AppContext | null + ): Promise /** Show an alert message box */ alert: ElMessageBoxShortcutMethod diff --git a/packages/components/message-box/src/messageBox.ts b/packages/components/message-box/src/messageBox.ts index 893853e770..778a8bd89b 100644 --- a/packages/components/message-box/src/messageBox.ts +++ b/packages/components/message-box/src/messageBox.ts @@ -1,9 +1,15 @@ import { h, watch, render } from 'vue' import { isClient } from '@vueuse/core' -import { isVNode, isString, hasOwn } from '@element-plus/utils' +import { + isVNode, + isString, + hasOwn, + isObject, + isUndefined, +} from '@element-plus/utils' import MessageBoxConstructor from './index.vue' -import type { ComponentPublicInstance, VNode } from 'vue' +import type { AppContext, ComponentPublicInstance, VNode } from 'vue' import type { Action, Callback, @@ -25,10 +31,15 @@ const messageInstance = new Map< } >() -const initInstance = (props: any, container: HTMLElement) => { +const initInstance = ( + props: any, + container: HTMLElement, + appContext: AppContext | null = null +) => { const vnode = h(MessageBoxConstructor, props) + vnode.appContext = appContext render(vnode, container) - document.body.appendChild(container.firstElementChild) + document.body.appendChild(container.firstElementChild!) return vnode.component } @@ -36,7 +47,7 @@ const genContainer = () => { return document.createElement('div') } -const showMessage = (options: any) => { +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. @@ -50,7 +61,7 @@ const showMessage = (options: any) => { } options.onAction = (action: Action) => { - const currentMsg = messageInstance.get(vm) + const currentMsg = messageInstance.get(vm)! let resolve: Action | { value: string; action: Action } if (options.showInput) { resolve = { value: vm.inputValue, action } @@ -72,7 +83,7 @@ const showMessage = (options: any) => { } } - const instance = initInstance(options, container) + const instance = initInstance(options, container, appContext)! // This is how we use message box programmably. // Maybe consider releasing a template version? @@ -110,11 +121,15 @@ const showMessage = (options: any) => { return vm } -async function MessageBox(options: ElMessageBoxOptions): Promise +async function MessageBox( + options: ElMessageBoxOptions, + appContext?: AppContext | null +): Promise function MessageBox( - options: ElMessageBoxOptions | string | VNode + options: ElMessageBoxOptions | string | VNode, + appContext: AppContext | null = null ): Promise<{ value: string; action: Action } | Action> { - if (!isClient) return + if (!isClient) return Promise.reject() let callback if (isString(options) || isVNode(options)) { options = { @@ -125,7 +140,7 @@ function MessageBox( } return new Promise((resolve, reject) => { - const vm = showMessage(options) + const vm = showMessage(options, appContext ?? MessageBox._context) // collect this vm in order to handle upcoming events. messageInstance.set(vm, { options, @@ -136,88 +151,53 @@ function MessageBox( }) } -MessageBox.alert = ( - message: string, - title: string, - options?: ElMessageBoxOptions -) => { - if (typeof title === 'object') { - options = title - title = '' - } else if (title === undefined) { - title = '' - } - - return MessageBox( - Object.assign( - { - title, - message, - type: '', - closeOnPressEscape: false, - closeOnClickModal: false, - }, - options, - { - boxType: 'alert', - } - ) - ) +const MESSAGE_BOX_VARIANTS = ['alert', 'confirm', 'prompt'] as const +const MESSAGE_BOX_DEFAULT_OPTS: Record< + typeof MESSAGE_BOX_VARIANTS[number], + Partial +> = { + alert: { closeOnPressEscape: false, closeOnClickModal: false }, + confirm: { showCancelButton: true }, + prompt: { showCancelButton: true, showInput: true }, } -MessageBox.confirm = ( - message: string, - title: string, - options?: ElMessageBoxOptions -) => { - if (typeof title === 'object') { - options = title - title = '' - } else if (title === undefined) { - title = '' - } - return MessageBox( - Object.assign( - { - title, - message, - type: '', - showCancelButton: true, - }, - options, - { - boxType: 'confirm', - } - ) - ) -} +MESSAGE_BOX_VARIANTS.forEach((boxType) => { + MessageBox[boxType] = messageBoxFactory(boxType) +}) -MessageBox.prompt = ( - message: string, - title: string, - options?: ElMessageBoxOptions -) => { - if (typeof title === 'object') { - options = title - title = '' - } else if (title === undefined) { - title = '' - } - return MessageBox( - Object.assign( - { - title, - message, - showCancelButton: true, - showInput: true, - type: '', - }, - options, - { - boxType: 'prompt', - } +function messageBoxFactory(boxType: typeof MESSAGE_BOX_VARIANTS[number]) { + return ( + message: string, + titleOrOpts: string | ElMessageBoxOptions, + options?: ElMessageBoxOptions, + appContext?: AppContext | null + ) => { + let title: string + if (isObject(titleOrOpts)) { + options = titleOrOpts + title = '' + } else if (isUndefined(titleOrOpts)) { + title = '' + } else { + title = titleOrOpts + } + + return MessageBox( + Object.assign( + { + title, + message, + type: '', + ...MESSAGE_BOX_DEFAULT_OPTS[boxType], + }, + options, + { + boxType, + } + ), + appContext ) - ) + } } MessageBox.close = () => { @@ -231,4 +211,6 @@ MessageBox.close = () => { messageInstance.clear() } +MessageBox._context = null + export default MessageBox as IElMessageBox