* Copyright (c) 2022 - present TinyVue Authors.
* Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd.
*
* Use of this source code is governed by an MIT-style license.
*
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
*
*/
import { KEY_CODE } from '../common'
import { addClass, removeClass, on } from '../dom'
import { isServer } from '../globalConfig'
const instances = {} as Record<string, any>
const classes = {
leave: 'v-modal-leave',
enter: 'v-modal-enter',
modal: 'v-modal'
}
export interface IModalStack {
id: string
zIndex: number
modalClass: string
}
const removeStack = (modalStack: IModalStack[], id: string) => {
for (let i = modalStack.length - 1; i >= 0; i--) {
if (modalStack[i].id === id) {
modalStack.splice(i, 1)
break
}
}
}
let getModal: () => HTMLElement
export const PopupManager = {
step: 2,
zIndex: 2000,
globalScroll: false,
modalFade: true,
modalStack: [] as IModalStack[],
modalDom: null as unknown as HTMLElement,
hasModal: false,
popLockClass: 'popup-parent--hidden',
oldBodyBorder: '',
viewportWindow: null,
fixBodyBorder() {
const barWidth = window.innerWidth - document.documentElement.clientWidth
if (barWidth) {
this.oldBodyBorder = document.documentElement.style.borderRight
document.body.style.borderRight = `${barWidth}px solid transparent`
}
},
resetBodyBorder() {
document.body.style.borderRight = this.oldBodyBorder
this.oldBodyBorder = ''
},
deregister: (id: string) => {
if (id) {
instances[id] = null
delete instances[id]
}
},
getInstance: (id: string) => instances[id],
register: (id: string, instance: any) => {
if (id && instance) {
instances[id] = instance
}
},
nextZIndex: () => {
const zIndex = PopupManager.zIndex
PopupManager.zIndex += PopupManager.step
return zIndex
},
openModal(id: string, zIndex: number, dom: HTMLElement | undefined, modalClass: string, modalFade: boolean) {
if (isServer) {
return
}
if (!id || zIndex === undefined) {
return
}
this.modalFade = modalFade
for (let i = 0, len = this.modalStack.length; i < len; i++) {
const modal = this.modalStack[i]
if (modal.id === id) {
return
}
}
const modalDom = getModal()
addClass(modalDom, classes.modal)
if (this.modalFade && !PopupManager.hasModal) {
addClass(modalDom, classes.enter)
}
if (modalClass) {
const classArr = modalClass.trim().split(/\s+/)
classArr.forEach((cls) => addClass(modalDom, cls))
}
setTimeout(() => {
removeClass(modalDom, classes.enter)
}, 200)
if (zIndex) {
modalDom.style.zIndex = zIndex.toString()
}
modalDom.style.display = ''
modalDom.tabIndex = 0
let parentNode
if (dom && dom.parentNode && dom.parentNode.nodeType !== 11) {
parentNode = dom.parentNode
} else {
parentNode = document.body
}
parentNode.appendChild(modalDom)
this.modalStack.push({ id, zIndex, modalClass })
},
doOnModalClick: () => {
const modalStack = PopupManager.modalStack
const topPopup = modalStack[modalStack.length - 1]
if (!topPopup) {
return
}
const instance = PopupManager.getInstance(topPopup.id)
if (instance && instance.closeOnClickModal) {
typeof instance.close === 'function' && instance.close()
}
},
closeModal(id: string) {
const modalStack = this.modalStack
const modalDom = getModal()
if (modalStack.length > 0) {
const topPopup = modalStack[modalStack.length - 1]
if (topPopup.id === id) {
if (topPopup.modalClass) {
const classArr = topPopup.modalClass.trim().split(/\s+/)
classArr.forEach((cls) => removeClass(modalDom, cls))
}
modalStack.pop()
const stackSize = modalStack.length
if (stackSize > 0) {
modalDom.style.zIndex = modalStack[stackSize - 1].zIndex.toString()
}
} else {
removeStack(modalStack, id)
}
}
if (modalStack.length === 0) {
this.modalFade && addClass(modalDom, classes.leave)
removeClass(document.body, this.popLockClass)
this.resetBodyBorder()
setTimeout(() => {
if (modalStack.length === 0) {
if (modalDom.parentNode) {
modalDom.parentNode.removeChild(modalDom)
}
modalDom.style.display = 'none'
PopupManager.modalDom = null as unknown as HTMLElement
}
removeClass(modalDom, classes.leave)
}, 200)
}
}
}
getModal = () => {
if (isServer) {
return null as unknown as HTMLElement
}
let modalDom: HTMLElement = PopupManager.modalDom as any
if (modalDom) {
PopupManager.hasModal = true
} else {
PopupManager.hasModal = false
modalDom = document.createElement('div')
PopupManager.modalDom = modalDom
modalDom.addEventListener(
'touchmove',
(event) => {
event.preventDefault()
event.stopPropagation()
},
{ passive: true }
)
on(modalDom, 'click', () => {
PopupManager.doOnModalClick()
})
}
return modalDom
}
if (!isServer) {
on(window, 'keydown', (event: KeyboardEvent) => {
if (event.keyCode === KEY_CODE.Escape) {
const modalStack = PopupManager.modalStack
if (modalStack.length > 0) {
const topPopup = modalStack[modalStack.length - 1]
if (!topPopup) {
return
}
const topPopupVm = PopupManager.getInstance(topPopup.id)
if (topPopupVm && topPopupVm.closeOnPressEscape) {
if (topPopupVm.handleClose) {
topPopupVm.handleClose('esc')
} else if (topPopupVm.handleAction) {
topPopupVm.handleAction('cancel')
} else {
topPopupVm.close()
}
}
}
}
})
}