feat(components): [el-messagebox] add context for message box (#6379)

- Add context for message box
- Update documentation based on changes
This commit is contained in:
JeremyWuuuuu 2022-03-05 19:40:35 +08:00 committed by GitHub
parent 071fca55ae
commit 11d3db586c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 119 additions and 94 deletions

View File

@ -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 <el-tag>> 2.0.4</el-tag>
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:

View File

@ -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
})
})
})

View File

@ -6,6 +6,7 @@ import type { SFCWithInstall } from '@element-plus/utils'
const _MessageBox = MessageBox as SFCWithInstall<typeof MessageBox>
_MessageBox.install = (app: App) => {
_MessageBox._context = app._context
app.config.globalProperties.$msgbox = _MessageBox
app.config.globalProperties.$messageBox = _MessageBox
app.config.globalProperties.$alert = _MessageBox.alert

View File

@ -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<MessageBoxData>) &
((
message: ElMessageBoxOptions['message'],
options?: ElMessageBoxOptions
options?: ElMessageBoxOptions,
appContext?: AppContext | null
) => Promise<MessageBoxData>)
export interface IElMessageBox {
_context: AppContext | null
/** Show a message box */
// (message: string, title?: string, type?: string): Promise<MessageBoxData>
/** Show a message box */
(options: ElMessageBoxOptions): Promise<MessageBoxData>
(
options: ElMessageBoxOptions,
appContext?: AppContext | null
): Promise<MessageBoxData>
/** Show an alert message box */
alert: ElMessageBoxShortcutMethod

View File

@ -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<MessageBoxData>
async function MessageBox(
options: ElMessageBoxOptions,
appContext?: AppContext | null
): Promise<MessageBoxData>
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<ElMessageBoxOptions>
> = {
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