mirror of
https://github.com/element-plus/element-plus.git
synced 2024-11-21 01:02:59 +08:00
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:
parent
071fca55ae
commit
11d3db586c
@ -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:
|
||||
|
@ -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
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user