feat(message-box): migrate MessageBox (#300)

* feat(message-box): migrate MessageBox

* refactor(message-box): import sleep from test-utils

* refactor(message-box): optimize props

* refactor(message-box): optimize message-box

* refactor(message-box): optimize message-box

* refactor(message-box): optimize message-box

* refactor(message-box): remove `removeClass` & `addClass`

* refactor(message-box): replace 'HTMLElement' with 'HTMLButtonElement'

* refactor(message-box): review optimize

* refactor(message-box): __test__ optimize

* refactor(message-box): replace addEvent with on and off

* refactor(message-box): remove doc

* feat(message-box): jest add @babel/plugin-proposal-class-properties

Co-authored-by: YuLinXi <yumengyuan@klicen.com>
This commit is contained in:
余林夕 2020-10-19 13:50:22 +08:00 committed by GitHub
parent 73fb527f49
commit c2951875b3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 1266 additions and 1761 deletions

View File

@ -23,7 +23,10 @@ module.exports = {
],
'@babel/preset-typescript',
],
plugins: ['@vue/babel-plugin-jsx'],
plugins: [
'@vue/babel-plugin-jsx',
'@babel/plugin-proposal-class-properties',
],
},
],
},

View File

@ -21,6 +21,7 @@
},
"devDependencies": {
"@babel/core": "^7.11.4",
"@babel/plugin-proposal-class-properties": "^7.12.1",
"@babel/preset-env": "^7.11.5",
"@babel/preset-typescript": "^7.10.4",
"@commitlint/cli": "^9.1.2",

View File

@ -48,6 +48,7 @@ import ElColorPicker from '@element-plus/color-picker'
import ElSelect from '@element-plus/select'
import ElTimeSelect from '@element-plus/time-select'
import ElPagination from '@element-plus/pagination'
import ElMessageBox from '@element-plus/message-box'
import ElInputNumber from '@element-plus/input-number'
import ElPopover from '@element-plus/popover'
@ -100,6 +101,7 @@ export {
ElSelect,
ElTimeSelect,
ElPagination,
ElMessageBox,
ElInputNumber,
ElPopover,
}
@ -145,6 +147,7 @@ const install = (app: App): void => {
ElCalendar(app)
ElInfiniteScroll(app)
ElMessage(app)
ElMessageBox(app)
ElDrawer(app)
ElPopconfirm(app)
ElForm(app)

View File

@ -0,0 +1,189 @@
import MessageBox from '../src/messageBox'
import { sleep } from '@element-plus/test-utils'
const selector = '.el-message-box__wrapper'
describe('MessageBox', () => {
afterEach(() => {
const el = document.querySelector('.el-message-box__wrapper')
if (!el) return
if (el.parentNode) {
el.parentNode.removeChild(el)
}
MessageBox.close()
})
test('create and close', async () => {
MessageBox({
type: 'success',
title: '消息',
message: '这是一段内容',
})
const msgbox: HTMLElement = document.querySelector(selector)
expect(msgbox).toBeDefined()
await sleep()
expect(msgbox.querySelector('.el-message-box__title span').textContent).toEqual('消息')
expect(msgbox.querySelector('.el-message-box__message').querySelector('p').textContent).toEqual('这是一段内容')
MessageBox.close()
await sleep(250)
expect(msgbox.style.display).toEqual('none')
})
test('invoke with strings', () => {
MessageBox('消息', '这是一段内容')
const msgbox = document.querySelector(selector)
expect(msgbox).toBeDefined()
})
test('custom icon', async () => {
MessageBox({
type: 'warning',
iconClass: 'el-icon-question',
message: '这是一段内容',
})
await sleep()
const icon = document.querySelector('.el-message-box__status')
expect(icon.classList.contains('el-icon-question')).toBe(true)
})
test('html string', async () => {
MessageBox({
title: 'html string',
dangerouslyUseHTMLString: true,
message: '<strong>html string</strong>',
})
await sleep()
const message = document.querySelector('.el-message-box__message strong')
expect(message.textContent).toEqual('html string')
})
test('distinguish cancel and close', async () => {
let msgAction = ''
MessageBox({
title: '消息',
message: '这是一段内容',
distinguishCancelAndClose: true,
callback: action => {
msgAction = action
},
})
await sleep()
const btn = document.querySelector('.el-message-box__close') as HTMLButtonElement
btn.click()
await sleep()
expect(msgAction).toEqual('close')
})
test('alert', async () => {
MessageBox.alert('这是一段内容', {
title: '标题名称',
type: 'warning',
})
await sleep()
const vModal: HTMLElement = document.querySelector('.v-modal')
vModal.click()
await sleep(250)
const msgbox: HTMLElement = document.querySelector(selector)
expect(msgbox.style.display).toEqual('')
expect(msgbox.querySelector('.el-icon-warning')).toBeDefined()
})
test('confirm', async () => {
MessageBox.confirm('这是一段内容', {
title: '标题名称',
type: 'warning',
})
await sleep()
const btn = document.querySelector(selector).querySelector('.el-button--primary') as HTMLButtonElement
btn.click()
await sleep(250)
const msgbox: HTMLElement = document.querySelector(selector)
expect(msgbox.style.display).toEqual('none')
})
test('prompt', async () => {
MessageBox.prompt('这是一段内容', {
title: '标题名称',
inputPattern: /test/,
inputErrorMessage: 'validation failed',
})
await sleep(0)
const inputElm = document.querySelector(selector).querySelector('.el-message-box__input')
const haveFocus = inputElm.querySelector('input').isSameNode(document.activeElement)
expect(inputElm).toBeDefined()
expect(haveFocus).toBe(true)
})
test('prompt: focus on textarea', async () => {
MessageBox.prompt('这是一段内容', {
inputType: 'textarea',
title: '标题名称',
})
await sleep()
const textareaElm = document.querySelector(selector).querySelector('textarea')
const haveFocus = textareaElm.isSameNode(document.activeElement)
expect(haveFocus).toBe(true)
})
test('callback', async () => {
let msgAction = ''
MessageBox({
title: '消息',
message: '这是一段内容',
callback: action => {
msgAction = action
},
})
await sleep()
const closeBtn = document.querySelector('.el-message-box__close') as HTMLButtonElement
closeBtn.click()
await sleep()
expect(msgAction).toEqual('cancel')
})
test('beforeClose', async() => {
let msgAction = ''
MessageBox({
title: '消息',
message: '这是一段内容',
beforeClose: (action, instance) => {
instance.close()
},
}, action => {
msgAction = action
})
await sleep();
(document.querySelector('.el-message-box__wrapper .el-button--primary') as HTMLButtonElement).click()
await sleep()
expect(msgAction).toEqual('confirm')
})
describe('promise', () => {
test('resolve',async () => {
let msgAction = ''
MessageBox.confirm('此操作将永久删除该文件, 是否继续?', '提示')
.then(action => {
msgAction = action
})
await sleep()
const btn = document.querySelector('.el-message-box__btns .el-button--primary') as HTMLButtonElement
btn.click()
await sleep()
expect(msgAction).toEqual('confirm')
})
test('reject', async () => {
let msgAction = ''
MessageBox.confirm('此操作将永久删除该文件, 是否继续?', '提示')
.catch(action => {
msgAction = action
})
await sleep()
const btn = document.querySelectorAll('.el-message-box__btns .el-button') as NodeListOf<HTMLButtonElement>
btn[0].click()
await sleep()
expect(msgAction).toEqual('cancel')
})
})
})

View File

@ -0,0 +1,9 @@
import { App } from 'vue'
import MessageBox from './src/messageBox'
export default (app: App): void => {
app.config.globalProperties.$msgbox = MessageBox
app.config.globalProperties.$messageBox = MessageBox
app.config.globalProperties.$alert = MessageBox.alert
app.config.globalProperties.$confirm = MessageBox.confirm
app.config.globalProperties.$prompt = MessageBox.prompt
}

View File

@ -0,0 +1,12 @@
{
"name": "@element-plus/message-box",
"version": "0.0.0",
"main": "dist/index.js",
"license": "MIT",
"peerDependencies": {
"vue": "^3.0.0-rc.7"
},
"devDependencies": {
"@vue/test-utils": "^2.0.0-beta.0"
}
}

View File

@ -0,0 +1,385 @@
<template>
<transition name="msgbox-fade">
<div
v-show="visible"
:aria-label="title || 'dialog'"
class="el-message-box__wrapper"
tabindex="-1"
role="dialog"
aria-modal="true"
@click.self="handleWrapperClick"
>
<div class="el-message-box" :class="[customClass, center && 'el-message-box--center']">
<div v-if="title !== null && title !== undefined" class="el-message-box__header">
<div class="el-message-box__title">
<div
v-if="icon && center"
:class="['el-message-box__status', icon]"
>
</div>
<span>{{ title }}</span>
</div>
<button
v-if="showClose"
type="button"
class="el-message-box__headerbtn"
aria-label="Close"
@click="handleAction(distinguishCancelAndClose ? 'close' : 'cancel')"
@keydown.enter="handleAction(distinguishCancelAndClose ? 'close' : 'cancel')"
>
<i class="el-message-box__close el-icon-close"></i>
</button>
</div>
<div class="el-message-box__content">
<div class="el-message-box__container">
<div
v-if="icon && !center && hasMessage"
:class="['el-message-box__status', icon]"
>
</div>
<div v-if="hasMessage" class="el-message-box__message">
<slot>
<p v-if="!dangerouslyUseHTMLString">{{ message }}</p>
<p v-else v-html="message"></p>
</slot>
</div>
</div>
<div v-show="showInput" class="el-message-box__input">
<el-input
ref="input"
v-model="inputValue"
:type="inputType"
:placeholder="inputPlaceholder"
:class="{ invalid: validateError }"
@keydown.enter="handleInputEnter"
/>
<div class="el-message-box__errormsg" :style="{ visibility: !!editorErrorMessage ? 'visible' : 'hidden' }">{{ editorErrorMessage }}</div>
</div>
</div>
<div class="el-message-box__btns">
<el-button
v-if="showCancelButton"
:loading="cancelButtonLoading"
:class="[ cancelButtonClass ]"
:round="roundButton"
size="small"
@click="handleAction('cancel')"
@keydown.enter="handleAction('cancel')"
>
{{ cancelButtonText || t('el.messagebox.cancel') }}
</el-button>
<el-button
v-show="showConfirmButton"
ref="confirm"
:loading="confirmButtonLoading"
:class="[ confirmButtonClasses ]"
:round="roundButton"
size="small"
@click="handleAction('confirm')"
@keydown.enter="handleAction('confirm')"
>
{{ confirmButtonText || t('el.messagebox.confirm') }}
</el-button>
</div>
</div>
</div>
</transition>
</template>
<script lang='ts'>
import {
defineComponent,
nextTick,
onMounted,
onBeforeUnmount,
computed,
watch,
onBeforeMount,
getCurrentInstance,
reactive,
toRefs,
} from 'vue'
import ElButton from '@element-plus/button/src/button.vue'
import ElInput from '@element-plus/input/src/index.vue'
import { t } from '@element-plus/locale'
import Dialog from '@element-plus/utils/aria-dialog'
import usePopup from '@element-plus/utils/popup/usePopup'
import { on, off } from '@element-plus/utils/dom'
let dialog
const TypeMap: Indexable<string> = {
success: 'success',
info: 'info',
warning: 'warning',
error: 'error',
}
export default defineComponent({
name: 'ElMessageBox',
components: {
ElButton,
ElInput,
},
props: {
openDelay: {
type: Boolean,
default: false,
},
closeDelay: {
type: Boolean,
default: false,
},
zIndex: {
type: Number,
},
modalFade: {
type: Boolean,
default: true,
},
modalClass: {
type: String,
default: '',
},
modalAppendToBody: {
type: Boolean,
default: false,
},
modal: {
type: Boolean,
default: true,
},
lockScroll: {
type: Boolean,
default: true,
},
showClose: {
type: Boolean,
default: true,
},
closeOnClickModal: {
type: Boolean,
default: true,
},
closeOnPressEscape: {
type: Boolean,
default: true,
},
closeOnHashChange: {
type: Boolean,
default: true,
},
center: {
default: false,
type: Boolean,
},
roundButton: {
default: false,
type: Boolean,
},
} ,
setup(props) {
let vm
const popup = usePopup(props, doClose)
const state = reactive<State>({
uid: 1,
title: undefined,
message: '',
type: '',
iconClass: '',
customClass: '',
showInput: false,
inputValue: null,
inputPlaceholder: '',
inputType: 'text',
inputPattern: null,
inputValidator: null,
inputErrorMessage: '',
showConfirmButton: true,
showCancelButton: false,
action: '',
confirmButtonText: '',
cancelButtonText: '',
confirmButtonLoading: false,
cancelButtonLoading: false,
confirmButtonClass: '',
confirmButtonDisabled: false,
cancelButtonClass: '',
editorErrorMessage: null,
callback: null,
dangerouslyUseHTMLString: false,
focusAfterClosed: null,
isOnComposition: false,
distinguishCancelAndClose: false,
type$: '',
visible: false,
validateError: false,
})
const icon = computed(() => state.iconClass || (state.type && TypeMap[state.type] ? `el-icon-${ TypeMap[state.type] }` : ''))
const hasMessage = computed(() => !!state.message)
const confirmButtonClasses = computed(() => `el-button--primary ${ state.confirmButtonClass }`)
watch(() => state.inputValue, val => {
nextTick().then(() => {
if (state.type$ === 'prompt' && val !== null) {
validate()
}
})
}, { immediate: true })
watch(() => state.visible, val => {
popup.state.visible = val
if (val) {
state.uid++
if (state.type$ === 'alert' || state.type$ === 'confirm') {
nextTick().then(() => { vm.refs.confirm.$el.focus() })
}
state.focusAfterClosed = document.activeElement
dialog = new Dialog(vm.vnode.el, state.focusAfterClosed, getFirstFocus())
}
if (state.type$ !== 'prompt') return
if (val) {
nextTick().then(() => {
if (vm.refs.input && vm.refs.input.$el) {
getInputElement().focus()
}
})
} else {
state.editorErrorMessage = ''
state.validateError = false
}
})
onBeforeMount(() => {
vm = getCurrentInstance()
vm.setupInstall = {
state,
doClose,
}
})
onMounted(async () => {
await nextTick()
if (props.closeOnHashChange) {
on(window, 'hashchange', popup.close)
}
})
onBeforeUnmount(() => {
if (props.closeOnHashChange) {
off(window, 'hashchange', popup.close)
}
setTimeout(() => {
dialog.closeDialog()
})
})
function getSafeClose () {
const currentId = state.uid
return () => {
nextTick().then(() => {
if (currentId === state.uid) doClose()
})
}
}
function doClose() {
if (!state.visible) return
state.visible = false
popup.updateClosingFlag(true)
dialog.closeDialog()
if (props.lockScroll) {
setTimeout(popup.restoreBodyStyle, 200)
}
popup.state.opened = false
popup.doAfterClose()
setTimeout(() => {
if (state.action) state.callback(state.action, state)
})
}
const getFirstFocus = () => {
const btn = vm.vnode.el.querySelector('.el-message-box__btns .el-button')
const title = vm.vnode.el.querySelector('.el-message-box__btns .el-message-box__title')
return btn || title
}
const handleWrapperClick = () => {
if (props.closeOnClickModal) {
handleAction(state.distinguishCancelAndClose ? 'close' : 'cancel')
}
}
const handleInputEnter = () => {
if (state.inputType !== 'textarea') {
return handleAction('confirm')
}
}
const handleAction = action => {
if (state.type$ === 'prompt' && action === 'confirm' && !validate()) {
return
}
state.action = action
if (typeof vm.setupInstall.state.beforeClose === 'function') {
vm.setupInstall.state.close = getSafeClose()
vm.setupInstall.state.beforeClose(action, state, popup.close)
} else {
doClose()
}
}
const validate = () => {
if (state.type$ === 'prompt') {
const inputPattern = state.inputPattern
if (inputPattern && !inputPattern.test(state.inputValue || '')) {
state.editorErrorMessage = state.inputErrorMessage || t('el.messagebox.error')
state.validateError = true
return false
}
const inputValidator = state.inputValidator
if (typeof inputValidator === 'function') {
const validateResult = inputValidator(state.inputValue)
if (validateResult === false) {
state.editorErrorMessage = state.inputErrorMessage || t('el.messagebox.error')
state.validateError = true
return false
}
if (typeof validateResult === 'string') {
state.editorErrorMessage = validateResult
state.validateError = true
return false
}
}
}
state.editorErrorMessage = ''
state.validateError = false
return true
}
const getInputElement = () => {
const inputRefs = vm.refs.input.$refs
return inputRefs.input || inputRefs.textarea
}
const handleClose = () => {
handleAction('close')
}
return {
...toRefs(state),
hasMessage,
icon,
confirmButtonClasses,
handleWrapperClick,
handleInputEnter,
handleAction,
handleClose,
t,
doClose,
}
},
})
</script>
<style>
</style>

View File

@ -0,0 +1,161 @@
import { VNode } from 'vue'
export type MessageType = 'success' | 'warning' | 'info' | 'error'
export type MessageBoxCloseAction = 'confirm' | 'cancel' | 'close'
export type MessageBoxData = MessageBoxInputData
export interface MessageBoxInputData {
value: string
action: MessageBoxCloseAction
}
export interface MessageBoxInputValidator {
(value: string): boolean | string
}
export declare class ElMessageBoxComponent {
title: string
message: string
type: MessageType
iconClass: string
customClass: string
showInput: boolean
showClose: boolean
inputValue: string
inputPlaceholder: string
inputType: string
inputPattern: RegExp
inputValidator: MessageBoxInputValidator
inputErrorMessage: string
showConfirmButton: boolean
showCancelButton: boolean
action: MessageBoxCloseAction
dangerouslyUseHTMLString: boolean
confirmButtonText: string
cancelButtonText: string
confirmButtonLoading: boolean
cancelButtonLoading: boolean
confirmButtonClass: string
confirmButtonDisabled: boolean
cancelButtonClass: string
editorErrorMessage: string
close(): any
}
/** Options used in MessageBox */
export interface ElMessageBoxOptions {
/** Title of the MessageBox */
title?: string
/** Content of the MessageBox */
message?: string | VNode
/** Message type, used for icon display */
type?: MessageType
/** Custom icon's class */
iconClass?: string
/** Custom class name for MessageBox */
customClass?: string
/** MessageBox closing callback if you don't prefer Promise */
callback?: (action: MessageBoxCloseAction, instance: ElMessageBoxComponent) => void
/** Callback before MessageBox closes, and it will prevent MessageBox from closing */
beforeClose?: (action: MessageBoxCloseAction, instance: ElMessageBoxComponent, done: (() => void)) => void
/** Whether to lock body scroll when MessageBox prompts */
lockScroll?: boolean
/** Whether to show a cancel button */
showCancelButton?: boolean
/** Whether to show a confirm button */
showConfirmButton?: boolean
/** Whether to show a close button */
showClose?: boolean
/** Text content of cancel button */
cancelButtonText?: string
/** Text content of confirm button */
confirmButtonText?: string
/** Custom class name of cancel button */
cancelButtonClass?: string
/** Custom class name of confirm button */
confirmButtonClass?: string
/** Whether to align the content in center */
center?: boolean
/** Whether message is treated as HTML string */
dangerouslyUseHTMLString?: boolean
/** Whether to use round button */
roundButton?: boolean
/** Whether MessageBox can be closed by clicking the mask */
closeOnClickModal?: boolean
/** Whether MessageBox can be closed by pressing the ESC */
closeOnPressEscape?: boolean
/** Whether to close MessageBox when hash changes */
closeOnHashChange?: boolean
/** Whether to show an input */
showInput?: boolean
/** Placeholder of input */
inputPlaceholder?: string
/** Initial value of input */
inputValue?: string
/** Regexp for the input */
inputPattern?: RegExp
/** Input Type: text, textArea, password or number */
inputType?: string
/** Validation function for the input. Should returns a boolean or string. If a string is returned, it will be assigned to inputErrorMessage */
inputValidator?: MessageBoxInputValidator
/** Error message when validation fails */
inputErrorMessage?: string
/** Whether to distinguish canceling and closing */
distinguishCancelAndClose?: boolean
}
export interface ElMessageBoxShortcutMethod {
(message: string, title: string, options?: ElMessageBoxOptions): Promise<MessageBoxData>
(message: string, options?: ElMessageBoxOptions): Promise<MessageBoxData>
}
export interface ElMessageBox {
/** Show a message box */
(message: string, title?: string, type?: string): Promise<MessageBoxData>
/** Show a message box */
(options: ElMessageBoxOptions): Promise<MessageBoxData>
/** Show an alert message box */
alert: ElMessageBoxShortcutMethod
/** Show a confirm message box */
confirm: ElMessageBoxShortcutMethod
/** Show a prompt message box */
prompt: ElMessageBoxShortcutMethod
/** Set default options of message boxes */
setDefaults (defaults: ElMessageBoxOptions): void
/** Close current message box */
close (): void
}

View File

@ -0,0 +1,230 @@
import { createVNode, render } from 'vue'
import MessageBoxConstructor from './index.vue'
import isServer from '@element-plus/utils/isServer'
import { isVNode } from '../../utils/util'
import { ElMessageBoxOptions } from './message-box'
let currentMsg, instance
// component default props
const PROP_KEYS = [
'lockScroll',
'showClose',
'closeOnClickModal',
'closeOnPressEscape',
'closeOnHashChange',
'center',
'roundButton',
'closeDelay',
'zIndex',
'modal',
'modalFade',
'modalClass',
'modalAppendToBody',
'lockScroll',
]
// component default merge props & data
const defaults = {
title: null,
message: '',
type: '',
iconClass: '',
showInput: false,
showClose: true,
modalFade: true,
lockScroll: true,
closeOnClickModal: true,
closeOnPressEscape: true,
closeOnHashChange: true,
inputValue: null,
inputPlaceholder: '',
inputType: 'text',
inputPattern: null,
inputValidator: null,
inputErrorMessage: '',
showConfirmButton: true,
showCancelButton: false,
confirmButtonPosition: 'right',
confirmButtonHighlight: false,
cancelButtonHighlight: false,
confirmButtonText: '',
cancelButtonText: '',
confirmButtonClass: '',
cancelButtonClass: '',
customClass: '',
beforeClose: null,
dangerouslyUseHTMLString: false,
center: false,
roundButton: false,
distinguishCancelAndClose: false,
}
let msgQueue = []
const defaultCallback = (action, ctx) => {
if (currentMsg) {
const callback = currentMsg.callback
if (typeof callback === 'function') {
if (ctx.showInput) {
callback(ctx.inputValue, action)
} else {
callback(action)
}
}
if (currentMsg.resolve) {
if (action === 'confirm') {
if (ctx.showInput) {
currentMsg.resolve({ value: ctx.inputValue, action })
} else {
currentMsg.resolve(action)
}
} else if (currentMsg.reject && (action === 'cancel' || action === 'close')) {
currentMsg.reject(action)
}
}
}
}
const initInstance = () => {
const container = document.createElement('div')
const vnode = createVNode(MessageBoxConstructor)
render(vnode, container)
instance = vnode.component
}
const showNextMsg = async () => {
if (!instance) {
initInstance()
}
if (instance && instance.setupInstall.state.visible) { return }
if (msgQueue.length > 0) {
const props = {}
const state = {}
currentMsg = msgQueue.shift()
const options = currentMsg.options
Object.keys(options).forEach(key => {
if (PROP_KEYS.includes(key)) {
props[key] = options[key]
} else {
state[key] = options[key]
}
})
// update props to instance/**/
const vmProps = instance.props
for (const prop in props) {
if (props.hasOwnProperty(prop)) {
vmProps[prop] = props[prop]
}
}
const vmState = instance.setupInstall.state
vmState.action = ''
if (options.callback === undefined) {
options.callback = defaultCallback
}
for (const prop in state) {
if (state.hasOwnProperty(prop)) {
vmState[prop] = state[prop]
}
}
if (isVNode(options.message)) {
instance.slots.default = () => [options.message]
}
const oldCb = options.callback
vmState.callback = (action, inst) => {
oldCb(action, inst)
showNextMsg()
}
document.body.appendChild(instance.vnode.el)
vmState.visible = true
}
}
const MessageBox = function(options: ElMessageBoxOptions | string, callback?): Promise<any> {
if (isServer) return
if (typeof options === 'string' || isVNode(options)) {
options = {
message: options,
}
if (typeof callback === 'string') {
options.title = callback
}
} else if (options.callback && !callback) {
callback = options.callback
}
if (typeof Promise !== 'undefined') {
return new Promise((resolve, reject) => {
msgQueue.push({
options: Object.assign({}, defaults, options),
callback: callback,
resolve: resolve,
reject: reject,
})
showNextMsg()
})
} else {
msgQueue.push({
options: Object.assign({}, defaults, options),
callback: callback,
})
showNextMsg()
}
}
MessageBox.alert = (message, title, options?: ElMessageBoxOptions) => {
if (typeof title === 'object') {
options = title
title = ''
} else if (title === undefined) {
title = ''
}
return MessageBox(Object.assign({
title: title,
message: message,
type$: 'alert',
closeOnPressEscape: false,
closeOnClickModal: false,
}, options))
}
MessageBox.confirm = (message, title, options?: ElMessageBoxOptions) => {
if (typeof title === 'object') {
options = title
title = ''
} else if (title === undefined) {
title = ''
}
return MessageBox(Object.assign({
title: title,
message: message,
type$: 'confirm',
showCancelButton: true,
}, options))
}
MessageBox.prompt = (message, title, options?: ElMessageBoxOptions) => {
if (typeof title === 'object') {
options = title
title = ''
} else if (title === undefined) {
title = ''
}
return MessageBox(Object.assign({
title: title,
message: message,
showCancelButton: true,
showInput: true,
type$: 'prompt',
}, options))
}
MessageBox.close = () => {
instance.setupInstall.doClose()
instance.setupInstall.state.visible = false
msgQueue = []
currentMsg = null
}
export default MessageBox

View File

@ -0,0 +1,206 @@
import {
nextTick,
reactive,
onBeforeMount,
onBeforeUnmount,
onMounted,
getCurrentInstance,
watch,
} from 'vue'
import PopupManager from '../popup-manager'
import getScrollBarWidth from '../scrollbar-width'
import { getStyle, addClass, removeClass, hasClass } from '../dom'
import isServer from '../isServer'
let idSeed = 1
let scrollBarWidth
export const usePopup1 = {
watch: {
visible(val) {
if (val) {
if (this._opening) return
if (!this.rendered) {
this.rendered = true
nextTick(() => {
this.open()
}).then(() => {
//
})
} else {
this.open()
}
} else {
this.close()
}
},
},
}
const usePopup = (props, doClose) => {
let _popupId
let _opening = false
let _closing = false
let vm
let _closeTimer = null
let _openTimer = null
const state = reactive<State>({
opened: false,
bodyPaddingRight: null,
computedBodyPaddingRight: 0,
withoutHiddenClass: true,
rendered: false,
visible: false,
})
onMounted(() => {
vm = getCurrentInstance()
})
onBeforeMount(() => {
_popupId = 'popup-' + idSeed++
PopupManager.register(_popupId, vm)
})
onBeforeUnmount(() => {
PopupManager.deregister(_popupId)
PopupManager.closeModal(_popupId)
restoreBodyStyle()
})
const doOpen = merProps => {
if (isServer) return
if (state.opened) return
_opening = true
const dom = vm.vnode.el
const modal = merProps.modal
const zIndex = merProps.zIndex
if (zIndex) {
PopupManager.zIndex = zIndex
}
if (modal) {
if (_closing) {
PopupManager.closeModal(_popupId)
_closing = false
}
PopupManager.openModal(_popupId, PopupManager.nextZIndex(), props.modalAppendToBody ? undefined : dom, merProps.modalClass, merProps.modalFade)
if (merProps.lockScroll) {
state.withoutHiddenClass = !hasClass(document.body, 'el-popup-parent--hidden')
if (state.withoutHiddenClass) {
state.bodyPaddingRight = document.body.style.paddingRight
state.computedBodyPaddingRight = parseInt(getStyle(document.body, 'paddingRight'), 10)
}
scrollBarWidth = getScrollBarWidth()
const bodyHasOverflow = document.documentElement.clientHeight < document.body.scrollHeight
const bodyOverflowY = getStyle(document.body, 'overflowY')
if (scrollBarWidth > 0 && (bodyHasOverflow || bodyOverflowY === 'scroll') && state.withoutHiddenClass) {
document.body.style.paddingRight = state.computedBodyPaddingRight + scrollBarWidth + 'px'
}
addClass(document.body, 'el-popup-parent--hidden')
}
}
if (getComputedStyle(dom).position === 'static') {
dom.style.position = 'absolute'
}
dom.style.zIndex = PopupManager.nextZIndex()
state.opened = true
doAfterOpen()
}
const open = function (options?) {
if (!state.rendered) {
state.rendered = true
}
const _props = Object.assign({}, props || vm.proxy, options)
if (_closeTimer) {
clearTimeout(_closeTimer)
_closeTimer = null
}
clearTimeout(_openTimer)
const openDelay = Number(_props.openDelay)
if (openDelay > 0) {
_openTimer = setTimeout(() => {
_openTimer = null
doOpen(_props)
}, openDelay)
} else {
doOpen(_props)
}
}
const close = () => {
if (_openTimer !== null) {
clearTimeout(_openTimer)
_openTimer = null
}
clearTimeout(_closeTimer)
const closeDelay = Number(props.closeDelay)
if (closeDelay > 0) {
_closeTimer = setTimeout(() => {
_closeTimer = null
doClose()
}, closeDelay)
} else {
doClose()
}
}
const doAfterOpen = () => {
_opening = false
}
const restoreBodyStyle = () => {
if (props.modal && state.withoutHiddenClass) {
document.body.style.paddingRight = state.bodyPaddingRight
removeClass(document.body, 'el-popup-parent--hidden')
}
state.withoutHiddenClass = true
}
const doAfterClose = () => {
PopupManager.closeModal(_popupId)
_closing = false
}
const updateClosingFlag = value => {
_closing = value
}
watch(() => state.visible, async val => {
if (val) {
if (_opening) return
if (!state.rendered) {
state.rendered = true
await nextTick()
open()
} else {
open()
}
} else {
close()
}
})
return {
state,
close,
doAfterClose,
updateClosingFlag,
restoreBodyStyle,
}
}
export default usePopup

1826
yarn.lock

File diff suppressed because it is too large Load Diff