Files
element-plus/packages/components/loading/src/service.ts

173 lines
5.2 KiB
TypeScript

// @ts-nocheck
import { nextTick } from 'vue'
import {
addClass,
getStyle,
isClient,
isString,
removeClass,
} from '@element-plus/utils'
import { createLoadingComponent } from './loading'
import type { UseNamespaceReturn, UseZIndexReturn } from '@element-plus/hooks'
import type { LoadingInstance } from './loading'
import type { LoadingOptionsResolved } from '..'
import type { LoadingOptions } from './types'
import type { AppContext, CSSProperties } from 'vue'
let fullscreenInstance: LoadingInstance | undefined = undefined
const Loading = function (
options: LoadingOptions = {},
context?: AppContext | null
): LoadingInstance {
if (!isClient) return undefined as any
const resolved = resolveOptions(options)
if (resolved.fullscreen && fullscreenInstance) {
return fullscreenInstance
}
const instance = createLoadingComponent(
{
...resolved,
closed: () => {
resolved.closed?.()
if (resolved.fullscreen) fullscreenInstance = undefined
},
},
context ?? Loading._context
)
addStyle(resolved, resolved.parent, instance)
addClassList(resolved, resolved.parent, instance)
resolved.parent.vLoadingAddClassList = () =>
addClassList(resolved, resolved.parent, instance)
/**
* add loading-number to parent.
* because if a fullscreen loading is triggered when somewhere
* a v-loading.body was triggered before and it's parent is
* document.body which with a margin , the fullscreen loading's
* destroySelf function will remove 'el-loading-parent--relative',
* and then the position of v-loading.body will be error.
*/
let loadingNumber: string | null =
resolved.parent.getAttribute('loading-number')
if (!loadingNumber) {
loadingNumber = '1'
} else {
loadingNumber = `${Number.parseInt(loadingNumber) + 1}`
}
resolved.parent.setAttribute('loading-number', loadingNumber)
resolved.parent.appendChild(instance.$el)
// after instance render, then modify visible to trigger transition
nextTick(() => (instance.visible.value = resolved.visible))
if (resolved.fullscreen) {
fullscreenInstance = instance
}
return instance
}
const resolveOptions = (options: LoadingOptions): LoadingOptionsResolved => {
let target: HTMLElement
if (isString(options.target)) {
target =
document.querySelector<HTMLElement>(options.target) ?? document.body
} else {
target = options.target || document.body
}
return {
parent: target === document.body || options.body ? document.body : target,
background: options.background || '',
svg: options.svg || '',
svgViewBox: options.svgViewBox || '',
spinner: options.spinner || false,
text: options.text || '',
fullscreen: target === document.body && (options.fullscreen ?? true),
lock: options.lock ?? false,
customClass: options.customClass || '',
visible: options.visible ?? true,
beforeClose: options.beforeClose,
closed: options.closed,
target,
}
}
const addStyle = async (
options: LoadingOptionsResolved,
parent: HTMLElement,
instance: LoadingInstance
) => {
// Compatible with the instance data format of vue@3.2.12 and earlier versions #12351
const { nextZIndex } =
((instance.vm as any).zIndex as UseZIndexReturn) ||
(instance.vm as any)._.exposed.zIndex
const maskStyle: CSSProperties = {}
if (options.fullscreen) {
instance.originalPosition.value = getStyle(document.body, 'position')
instance.originalOverflow.value = getStyle(document.body, 'overflow')
maskStyle.zIndex = nextZIndex()
} else if (options.parent === document.body) {
instance.originalPosition.value = getStyle(document.body, 'position')
/**
* await dom render when visible is true in init,
* because some component's height maybe 0.
* e.g. el-table.
*/
await nextTick()
for (const property of ['top', 'left']) {
const scroll = property === 'top' ? 'scrollTop' : 'scrollLeft'
maskStyle[property] = `${
(options.target as HTMLElement).getBoundingClientRect()[property] +
document.body[scroll] +
document.documentElement[scroll] -
Number.parseInt(getStyle(document.body, `margin-${property}`), 10)
}px`
}
for (const property of ['height', 'width']) {
maskStyle[property] = `${
(options.target as HTMLElement).getBoundingClientRect()[property]
}px`
}
} else {
instance.originalPosition.value = getStyle(parent, 'position')
}
for (const [key, value] of Object.entries(maskStyle)) {
instance.$el.style[key] = value
}
}
const addClassList = (
options: LoadingOptions,
parent: HTMLElement,
instance: LoadingInstance
) => {
// Compatible with the instance data format of vue@3.2.12 and earlier versions #12351
const ns =
((instance.vm as any).ns as UseNamespaceReturn) ||
(instance.vm as any)._.exposed.ns
if (
!['absolute', 'fixed', 'sticky'].includes(instance.originalPosition.value)
) {
addClass(parent, ns.bm('parent', 'relative'))
} else {
removeClass(parent, ns.bm('parent', 'relative'))
}
if (options.fullscreen && options.lock) {
addClass(parent, ns.bm('parent', 'hidden'))
} else {
removeClass(parent, ns.bm('parent', 'hidden'))
}
}
Loading._context = null as AppContext | null
export default Loading