2020-07-28 00:32:04 +08:00
|
|
|
import isServer from './isServer'
|
2020-09-19 20:44:07 +08:00
|
|
|
import { camelize, isObject } from './util'
|
2021-09-17 15:27:31 +08:00
|
|
|
import type { CSSProperties } from 'vue'
|
2020-07-28 00:32:04 +08:00
|
|
|
|
2021-08-24 13:36:48 +08:00
|
|
|
import type { Nullable } from './types'
|
2021-08-04 18:28:08 +08:00
|
|
|
|
2020-07-28 00:32:04 +08:00
|
|
|
/* istanbul ignore next */
|
2021-10-19 13:26:30 +08:00
|
|
|
const trimArr = function (s: string | SVGAnimatedString) {
|
|
|
|
if (typeof s !== 'string') {
|
|
|
|
if (s.baseVal) {
|
|
|
|
return s.baseVal.split(' ').map((item) => item.trim())
|
|
|
|
} else {
|
|
|
|
return []
|
|
|
|
}
|
|
|
|
}
|
2021-10-18 16:33:34 +08:00
|
|
|
return (s || '').split(' ').map((item) => item.trim())
|
2020-07-28 00:32:04 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/* istanbul ignore next */
|
2021-09-04 19:29:28 +08:00
|
|
|
export const on = function (
|
2020-08-04 19:03:20 +08:00
|
|
|
element: HTMLElement | Document | Window,
|
|
|
|
event: string,
|
|
|
|
handler: EventListenerOrEventListenerObject,
|
2021-09-04 19:29:28 +08:00
|
|
|
useCapture = false
|
2020-08-04 19:03:20 +08:00
|
|
|
): void {
|
|
|
|
if (element && event && handler) {
|
2021-09-01 17:38:54 +08:00
|
|
|
element?.addEventListener(event, handler, useCapture)
|
2020-07-28 00:32:04 +08:00
|
|
|
}
|
2020-08-04 19:03:20 +08:00
|
|
|
}
|
2020-07-28 00:32:04 +08:00
|
|
|
|
|
|
|
/* istanbul ignore next */
|
2021-09-04 19:29:28 +08:00
|
|
|
export const off = function (
|
2020-08-04 19:03:20 +08:00
|
|
|
element: HTMLElement | Document | Window,
|
|
|
|
event: string,
|
|
|
|
handler: EventListenerOrEventListenerObject,
|
2021-09-04 19:29:28 +08:00
|
|
|
useCapture = false
|
2020-08-04 19:03:20 +08:00
|
|
|
): void {
|
|
|
|
if (element && event && handler) {
|
2021-09-01 17:38:54 +08:00
|
|
|
element?.removeEventListener(event, handler, useCapture)
|
2020-07-28 00:32:04 +08:00
|
|
|
}
|
2020-08-04 19:03:20 +08:00
|
|
|
}
|
2020-07-28 00:32:04 +08:00
|
|
|
|
|
|
|
/* istanbul ignore next */
|
2021-09-04 19:29:28 +08:00
|
|
|
export const once = function (
|
2020-07-28 00:32:04 +08:00
|
|
|
el: HTMLElement,
|
|
|
|
event: string,
|
2021-09-04 19:29:28 +08:00
|
|
|
fn: EventListener
|
2020-07-28 20:31:26 +08:00
|
|
|
): void {
|
2021-10-18 16:33:34 +08:00
|
|
|
const listener = function (this: any, ...args: any) {
|
2020-07-28 00:32:04 +08:00
|
|
|
if (fn) {
|
2020-07-28 20:31:26 +08:00
|
|
|
fn.apply(this, args)
|
2020-07-28 00:32:04 +08:00
|
|
|
}
|
|
|
|
off(el, event, listener)
|
|
|
|
}
|
|
|
|
on(el, event, listener)
|
|
|
|
}
|
|
|
|
|
|
|
|
/* istanbul ignore next */
|
2020-07-28 20:31:26 +08:00
|
|
|
export function hasClass(el: HTMLElement, cls: string): boolean {
|
2020-07-28 00:32:04 +08:00
|
|
|
if (!el || !cls) return false
|
|
|
|
if (cls.indexOf(' ') !== -1)
|
|
|
|
throw new Error('className should not contain space.')
|
|
|
|
if (el.classList) {
|
|
|
|
return el.classList.contains(cls)
|
|
|
|
} else {
|
2021-09-17 09:18:24 +08:00
|
|
|
return ` ${el.className} `.indexOf(` ${cls} `) > -1
|
2020-07-28 00:32:04 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* istanbul ignore next */
|
2020-07-28 20:31:26 +08:00
|
|
|
export function addClass(el: HTMLElement, cls: string): void {
|
2020-07-28 00:32:04 +08:00
|
|
|
if (!el) return
|
2021-10-18 16:33:34 +08:00
|
|
|
const curClass = trimArr(el.className)
|
|
|
|
const classes = (cls || '')
|
|
|
|
.split(' ')
|
|
|
|
.filter((item) => !curClass.includes(item) && !!item.trim())
|
2020-07-28 00:32:04 +08:00
|
|
|
|
2021-10-18 16:33:34 +08:00
|
|
|
if (el.classList) {
|
|
|
|
el.classList.add(...classes)
|
|
|
|
} else {
|
2021-10-19 13:26:30 +08:00
|
|
|
let className = el.className
|
|
|
|
if (typeof className === 'string') {
|
|
|
|
className += ` ${classes.join(' ')}`
|
|
|
|
} else {
|
|
|
|
className = `${(className as SVGAnimatedString).baseVal} ${classes.join(
|
|
|
|
' '
|
|
|
|
)}`
|
|
|
|
}
|
|
|
|
el.setAttribute('class', className)
|
2020-07-28 00:32:04 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* istanbul ignore next */
|
2020-07-28 20:31:26 +08:00
|
|
|
export function removeClass(el: HTMLElement, cls: string): void {
|
2020-07-28 00:32:04 +08:00
|
|
|
if (!el || !cls) return
|
2021-10-18 16:33:34 +08:00
|
|
|
const classes = trimArr(cls)
|
2021-10-19 13:26:30 +08:00
|
|
|
let curClass = el.className as string | SVGAnimatedString
|
|
|
|
if (typeof curClass === 'string') {
|
|
|
|
curClass = ` ${curClass} `
|
|
|
|
} else {
|
|
|
|
curClass = ` ${curClass.baseVal} `
|
|
|
|
}
|
2020-07-28 00:32:04 +08:00
|
|
|
|
2021-10-18 16:33:34 +08:00
|
|
|
if (el.classList) {
|
|
|
|
el.classList.remove(...classes)
|
|
|
|
return
|
2020-07-28 00:32:04 +08:00
|
|
|
}
|
2021-10-18 16:33:34 +08:00
|
|
|
classes.forEach((item) => {
|
2021-10-19 13:26:30 +08:00
|
|
|
curClass = (curClass as string).replace(` ${item} `, ' ')
|
2021-10-18 16:33:34 +08:00
|
|
|
})
|
2021-10-19 13:26:30 +08:00
|
|
|
const className = trimArr(curClass)
|
2021-10-18 16:33:34 +08:00
|
|
|
.filter((item) => !!item)
|
|
|
|
.join(' ')
|
2021-10-19 13:26:30 +08:00
|
|
|
el.setAttribute('class', className)
|
2020-07-28 00:32:04 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/* istanbul ignore next */
|
2021-08-31 09:40:13 +08:00
|
|
|
// Here I want to use the type CSSProperties, but the definition for CSSProperties
|
2020-08-04 19:03:20 +08:00
|
|
|
// has { [index: number]: string } in its type annotation, which does not satisfy the method
|
2020-08-25 22:25:46 +08:00
|
|
|
// camelize(s: string)
|
2020-07-28 00:32:04 +08:00
|
|
|
// Same as the return type
|
2021-09-04 19:29:28 +08:00
|
|
|
export const getStyle = function (
|
2020-07-28 00:32:04 +08:00
|
|
|
element: HTMLElement,
|
2021-09-04 19:29:28 +08:00
|
|
|
styleName: string
|
2020-07-28 00:32:04 +08:00
|
|
|
): string {
|
2021-10-18 16:33:34 +08:00
|
|
|
if (isServer) return ''
|
|
|
|
if (!element || !styleName) return ''
|
2020-08-25 22:25:46 +08:00
|
|
|
styleName = camelize(styleName)
|
2020-07-28 00:32:04 +08:00
|
|
|
if (styleName === 'float') {
|
|
|
|
styleName = 'cssFloat'
|
|
|
|
}
|
|
|
|
try {
|
2020-08-27 21:22:32 +08:00
|
|
|
const style = element.style[styleName]
|
|
|
|
if (style) return style
|
2021-10-18 16:33:34 +08:00
|
|
|
const computed = document.defaultView?.getComputedStyle(element, '')
|
2020-08-27 21:22:32 +08:00
|
|
|
return computed ? computed[styleName] : ''
|
2020-07-28 00:32:04 +08:00
|
|
|
} catch (e) {
|
|
|
|
return element.style[styleName]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* istanbul ignore next */
|
|
|
|
export function setStyle(
|
|
|
|
element: HTMLElement,
|
2021-08-31 09:40:13 +08:00
|
|
|
styleName: CSSProperties | string,
|
2021-09-04 19:29:28 +08:00
|
|
|
value?: string
|
2020-07-28 20:31:26 +08:00
|
|
|
): void {
|
2020-07-28 00:32:04 +08:00
|
|
|
if (!element || !styleName) return
|
|
|
|
|
2020-09-19 20:44:07 +08:00
|
|
|
if (isObject(styleName)) {
|
2021-09-04 19:29:28 +08:00
|
|
|
Object.keys(styleName).forEach((prop) => {
|
2020-09-19 20:44:07 +08:00
|
|
|
setStyle(element, prop, styleName[prop])
|
|
|
|
})
|
2020-07-28 00:32:04 +08:00
|
|
|
} else {
|
2020-08-25 22:25:46 +08:00
|
|
|
styleName = camelize(styleName)
|
2020-07-28 00:32:04 +08:00
|
|
|
element.style[styleName] = value
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-04 19:29:28 +08:00
|
|
|
export function removeStyle(
|
|
|
|
element: HTMLElement,
|
|
|
|
style: CSSProperties | string
|
|
|
|
) {
|
2020-09-19 20:44:07 +08:00
|
|
|
if (!element || !style) return
|
|
|
|
|
|
|
|
if (isObject(style)) {
|
2021-09-04 19:29:28 +08:00
|
|
|
Object.keys(style).forEach((prop) => {
|
2020-09-19 20:44:07 +08:00
|
|
|
setStyle(element, prop, '')
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
setStyle(element, style, '')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-04 19:03:20 +08:00
|
|
|
export const isScroll = (
|
|
|
|
el: HTMLElement,
|
2021-09-04 19:29:28 +08:00
|
|
|
isVertical?: Nullable<boolean>
|
2021-10-18 16:33:34 +08:00
|
|
|
): RegExpMatchArray | null => {
|
|
|
|
if (isServer) return null
|
2020-08-27 21:22:32 +08:00
|
|
|
const determinedDirection = isVertical === null || isVertical === undefined
|
2020-07-28 00:32:04 +08:00
|
|
|
const overflow = determinedDirection
|
2020-08-27 21:22:32 +08:00
|
|
|
? getStyle(el, 'overflow')
|
|
|
|
: isVertical
|
2021-09-04 19:29:28 +08:00
|
|
|
? getStyle(el, 'overflow-y')
|
|
|
|
: getStyle(el, 'overflow-x')
|
2020-07-28 00:32:04 +08:00
|
|
|
|
2021-07-05 09:16:00 +08:00
|
|
|
return overflow.match(/(scroll|auto|overlay)/)
|
2020-07-28 00:32:04 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
export const getScrollContainer = (
|
|
|
|
el: HTMLElement,
|
2021-09-04 19:29:28 +08:00
|
|
|
isVertical?: Nullable<boolean>
|
2021-10-18 16:33:34 +08:00
|
|
|
): Window | HTMLElement | undefined => {
|
2020-07-28 00:32:04 +08:00
|
|
|
if (isServer) return
|
2020-09-07 16:48:11 +08:00
|
|
|
|
2020-07-28 00:32:04 +08:00
|
|
|
let parent: HTMLElement = el
|
|
|
|
while (parent) {
|
|
|
|
if ([window, document, document.documentElement].includes(parent)) {
|
|
|
|
return window
|
|
|
|
}
|
|
|
|
if (isScroll(parent, isVertical)) {
|
|
|
|
return parent
|
|
|
|
}
|
|
|
|
parent = parent.parentNode as HTMLElement
|
|
|
|
}
|
|
|
|
return parent
|
|
|
|
}
|
|
|
|
|
2020-08-04 19:03:20 +08:00
|
|
|
export const isInContainer = (
|
2021-10-06 20:17:18 +08:00
|
|
|
el: Element | undefined,
|
|
|
|
container: Element | Window | undefined
|
2020-08-04 19:03:20 +08:00
|
|
|
): boolean => {
|
2020-07-28 00:32:04 +08:00
|
|
|
if (isServer || !el || !container) return false
|
|
|
|
|
|
|
|
const elRect = el.getBoundingClientRect()
|
|
|
|
|
2021-10-06 20:17:18 +08:00
|
|
|
let containerRect: Pick<DOMRect, 'top' | 'bottom' | 'left' | 'right'>
|
|
|
|
if (container instanceof Element) {
|
|
|
|
containerRect = container.getBoundingClientRect()
|
|
|
|
} else {
|
2020-07-28 00:32:04 +08:00
|
|
|
containerRect = {
|
|
|
|
top: 0,
|
|
|
|
right: window.innerWidth,
|
|
|
|
bottom: window.innerHeight,
|
|
|
|
left: 0,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return (
|
|
|
|
elRect.top < containerRect.bottom &&
|
|
|
|
elRect.bottom > containerRect.top &&
|
|
|
|
elRect.right > containerRect.left &&
|
|
|
|
elRect.left < containerRect.right
|
|
|
|
)
|
|
|
|
}
|
2020-09-07 16:48:11 +08:00
|
|
|
|
|
|
|
export const getOffsetTop = (el: HTMLElement) => {
|
|
|
|
let offset = 0
|
|
|
|
let parent = el
|
|
|
|
|
|
|
|
while (parent) {
|
|
|
|
offset += parent.offsetTop
|
|
|
|
parent = parent.offsetParent as HTMLElement
|
|
|
|
}
|
|
|
|
|
|
|
|
return offset
|
|
|
|
}
|
|
|
|
|
2021-09-04 19:29:28 +08:00
|
|
|
export const getOffsetTopDistance = (
|
|
|
|
el: HTMLElement,
|
|
|
|
containerEl: HTMLElement
|
|
|
|
) => {
|
2020-09-07 16:48:11 +08:00
|
|
|
return Math.abs(getOffsetTop(el) - getOffsetTop(containerEl))
|
|
|
|
}
|
2020-10-30 23:26:33 +08:00
|
|
|
|
2020-09-14 10:03:33 +08:00
|
|
|
export const stop = (e: Event) => e.stopPropagation()
|