@whitesev/pops
Version:
弹窗库,包含了alert、confirm、prompt、drawer、folder、loading、iframe、panel、tooltip、searchSuggestion、rightClickMenu组件
1,474 lines (1,454 loc) • 95 kB
text/typescript
import { PopsCommonCSSClassName } from "../config/CommonCSSClassName";
import { OriginPrototype, PopsCore } from "../PopsCore";
import type {
ParseHTMLReturnType,
PopsDOMUtils_Event,
PopsDOMUtils_EventType,
PopsDOMUtilsAddEventListenerResult,
PopsDOMUtilsCreateElementAttributesMap,
PopsDOMUtilsCSSProperty,
PopsDOMUtilsCSSPropertyType,
PopsDOMUtilsElementEventType,
PopsDOMUtilsEventListenerOption,
PopsDOMUtilsEventListenerOptionsAttribute,
PopsDOMUtilsTargetElementType,
} from "../types/PopsDOMUtilsEventType";
import { PopsSafeUtils } from "./PopsSafeUtils";
import { popsUtils } from "./PopsUtils";
/* 数据 */
const GlobalData = {
/** .on添加在元素存储的事件 */
domEventSymbol: Symbol("events_" + (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1)),
};
const CommonUtils = {
isWin: popsUtils.isWin.bind(popsUtils),
delete: popsUtils.delete.bind(popsUtils),
isNodeList: popsUtils.isNodeList.bind(popsUtils),
};
class PopsDOMUtilsEvent {
get windowApi() {
return PopsCore.window;
}
/**
* 绑定事件
* @param element 需要绑定的元素|元素数组|window
* @param eventType 需要监听的事件
* @param handler 绑定事件触发的回调函数
* @param option
* + capture 表示事件是否在捕获阶段触发。默认为false,即在冒泡阶段触发
* + once 表示事件是否只触发一次。默认为false
* + passive 表示事件监听器是否不会调用preventDefault()。默认为false
* @example
* // 监听元素a.xx的click事件
* DOMUtils.on(document.querySelector("a.xx"),"click",(event)=>{
* console.log("事件触发",event)
* })
* DOMUtils.on("a.xx","click",(event)=>{
* console.log("事件触发",event)
* })
*/
on<T extends PopsDOMUtils_EventType = PopsDOMUtils_EventType>(
element: PopsDOMUtilsElementEventType,
eventType: T | T[],
handler: <E extends HTMLElement = HTMLElement>(this: E, event: PopsDOMUtils_Event[T]) => void,
option?: PopsDOMUtilsEventListenerOption | boolean
): PopsDOMUtilsAddEventListenerResult;
/**
* 绑定事件
* @param element 需要绑定的元素|元素数组|window
* @param eventType 需要监听的事件
* @param handler 绑定事件触发的回调函数
* @param option
* + capture 表示事件是否在捕获阶段触发。默认为false,即在冒泡阶段触发
* + once 表示事件是否只触发一次。默认为false
* + passive 表示事件监听器是否不会调用preventDefault()。默认为false
* @example
* // 监听元素a.xx的click事件
* DOMUtils.on(document.querySelector("a.xx"),"click",(event)=>{
* console.log("事件触发",event)
* })
* DOMUtils.on("a.xx","click",(event)=>{
* console.log("事件触发",event)
* })
*/
on<T extends Event = Event>(
element: PopsDOMUtilsElementEventType,
eventType: string | string[],
handler: <E extends HTMLElement = HTMLElement>(this: E, event: T) => void,
option?: PopsDOMUtilsEventListenerOption | boolean
): PopsDOMUtilsAddEventListenerResult;
/**
* 绑定事件
* @param element 需要绑定的元素|元素数组|window
* @param eventType 需要监听的事件
* @param selector 子元素选择器
* @param handler 绑定事件触发的回调函数
* @param option
* + capture 表示事件是否在捕获阶段触发。默认为false,即在冒泡阶段触发
* + once 表示事件是否只触发一次。默认为false
* + passive 表示事件监听器是否不会调用preventDefault()。默认为false
* @example
* // 监听元素a.xx的click、tap、hover事件
* DOMUtils.on(document.querySelector("a.xx"),"click tap hover",(event, $selector)=>{
* console.log("事件触发", event, $selector)
* })
* DOMUtils.on("a.xx",["click","tap","hover"],(event, $selector)=>{
* console.log("事件触发", event, $selector)
* })
* @example
* // 监听全局document下的子元素a.xx的click事件
* DOMUtils.on(document,"click tap hover","a.xx",(event, $selector)=>{
* console.log("事件触发", event, $selector)
* })
*/
on<T extends PopsDOMUtils_EventType = PopsDOMUtils_EventType>(
element: PopsDOMUtilsElementEventType,
eventType: T | T[],
selector: string | string[] | undefined | null,
handler: <E extends HTMLElement = HTMLElement>(this: E, event: PopsDOMUtils_Event[T], $selector: E) => void,
option?: PopsDOMUtilsEventListenerOption | boolean
): PopsDOMUtilsAddEventListenerResult;
/**
* 绑定事件
* @param element 需要绑定的元素|元素数组|window
* @param eventType 需要监听的事件
* @param selector 子元素选择器
* @param handler 绑定事件触发的回调函数
* @param option
* + capture 表示事件是否在捕获阶段触发。默认为false,即在冒泡阶段触发
* + once 表示事件是否只触发一次。默认为false
* + passive 表示事件监听器是否不会调用preventDefault()。默认为false
* @example
* // 监听元素a.xx的click、tap、hover事件
* DOMUtils.on(document.querySelector("a.xx"),"click tap hover",(event, $selector)=>{
* console.log("事件触发", event, $selector)
* })
* DOMUtils.on("a.xx",["click","tap","hover"],(event, $selector)=>{
* console.log("事件触发", event, $selector)
* })
* @example
* // 监听全局document下的子元素a.xx的click事件
* DOMUtils.on(document,"click tap hover","a.xx",(event, $selector)=>{
* console.log("事件触发", event, $selector)
* })
*/
on<T extends Event = Event>(
element: PopsDOMUtilsElementEventType,
eventType: string | string[],
selector: string | string[] | undefined | null,
handler: <E extends HTMLElement = HTMLElement>(this: E, event: T, $selector: E) => void,
option?: PopsDOMUtilsEventListenerOption | boolean
): PopsDOMUtilsAddEventListenerResult;
on<T extends Event = Event>(
element: HTMLElement | string | NodeList | HTMLElement[] | Window | Document | Element | null | typeof globalThis,
eventType: PopsDOMUtils_EventType | PopsDOMUtils_EventType[] | string | string[],
selector:
| string
| string[]
| undefined
| (<E extends HTMLElement = HTMLElement>(this: E, event: T, $selector: E) => void)
| null,
callback?:
| (<E extends HTMLElement = HTMLElement>(this: E, event: T, $selector: E) => void)
| PopsDOMUtilsEventListenerOption
| boolean,
option?: PopsDOMUtilsEventListenerOption | boolean
): PopsDOMUtilsAddEventListenerResult {
/**
* 获取option配置
* @param args
* @param startIndex
* @param option
*/
const getOption = function (args: IArguments, startIndex: number, option: PopsDOMUtilsEventListenerOption) {
const currentParam: boolean | PopsDOMUtilsEventListenerOption = args[startIndex];
if (typeof currentParam === "boolean") {
option.capture = currentParam;
if (typeof args[startIndex + 1] === "boolean") {
option.once = args[startIndex + 1];
}
if (typeof args[startIndex + 2] === "boolean") {
option.passive = args[startIndex + 2];
}
} else if (currentParam && typeof currentParam === "object") {
for (const key in option) {
if (Reflect.has(currentParam, key)) {
Reflect.set(option, key, currentParam[key as keyof typeof currentParam]);
}
}
}
return option;
};
const that = this;
// eslint-disable-next-line prefer-rest-params
const args = arguments;
if (typeof element === "string") {
element = that.selectorAll(element);
}
if (element == null) {
return {
off() {},
emit() {},
};
}
let $elList: (Element | Document | Window)[] = [];
if (element instanceof NodeList || Array.isArray(element)) {
$elList = $elList.concat(Array.from(element as Element[]));
} else {
$elList.push(element as Element);
}
// 事件名
let eventTypeList: string[] = [];
if (Array.isArray(eventType)) {
eventTypeList = eventTypeList.concat(eventType.filter((it) => typeof it === "string" && it.toString() !== ""));
} else if (typeof eventType === "string") {
eventTypeList = eventTypeList.concat(eventType.split(" ").filter((it) => it !== ""));
}
// 子元素选择器
let selectorList: string[] = [];
if (Array.isArray(selector)) {
selectorList = selectorList.concat(selector.filter((it) => typeof it === "string" && it.toString() !== ""));
} else if (typeof selector === "string") {
selectorList.push(selector);
}
// 事件回调
let listenerCallBack: (this: Element, event: Event, $selector?: HTMLElement) => void | boolean = callback as (
this: Element,
event: Event,
$selector?: HTMLElement
) => void | boolean;
// 事件配置
let listenerOption: PopsDOMUtilsEventListenerOption = {
capture: false,
once: false,
passive: false,
isComposedPath: false,
overrideTarget: true,
};
if (typeof selector === "function") {
// 这是为没有selector的情况
// 那么它就是callback
listenerCallBack = selector as any;
listenerOption = getOption(args, 3, listenerOption);
} else {
// 这是存在selector的情况
listenerOption = getOption(args, 4, listenerOption);
}
$elList.forEach(($elItem) => {
// window和document共用一个对象
// 这样就能处理子元素选择器无法匹配的问题
const targetIsWindow = CommonUtils.isWin($elItem);
// 遍历事件名设置元素事件
eventTypeList.forEach((eventName) => {
/**
* 如果是option.once,那么删除该监听和元素上的事件和监听
*/
const checkOptionOnceToRemoveEventListener = () => {
if (listenerOption.once) {
this.off($elItem, eventName, selector as any, callback as any, option);
}
};
/**
* 事件回调
* @param event
*/
const handlerCallBack = function (event: Event) {
if (listenerOption.isPreventEvent) {
that.preventEvent(event);
}
let call_this: Element | undefined = void 0;
let call_event: Event | undefined = void 0;
let call_$selector: HTMLElement | undefined = void 0;
let execCallback = false;
if (selectorList.length) {
// 存在子元素选择器
// 这时候的this和target都是子元素选择器的元素
let $target: HTMLElement;
if (listenerOption.isComposedPath) {
// 可能为空
const composedPath = event.composedPath();
if (!composedPath.length && event.target) {
composedPath.push(event.target);
}
$target = composedPath[0] as HTMLElement;
} else {
$target = event.target as HTMLElement;
}
const $parent = targetIsWindow ? that.windowApi.document.documentElement : $elItem;
const findValue = selectorList.find((selectors) => {
// 判断目标元素是否匹配选择器
if (that.matches($target, selectors)) {
// 当前目标可以被selector所匹配到
return true;
}
// 在上层与主元素之间寻找可以被selector所匹配到的
const $closestMatches = that.closest<HTMLElement>($target, selectors);
if ($closestMatches && (<HTMLElement>$parent)?.contains?.($closestMatches)) {
$target = $closestMatches;
return true;
}
return false;
});
if (findValue) {
if (listenerOption.overrideTarget) {
// 这里尝试使用defineProperty修改event的target值
try {
const originTarget = event.target;
OriginPrototype.Object.defineProperties(event, {
target: {
get() {
return $target;
},
},
originTarget: {
get() {
return originTarget;
},
},
});
// oxlint-disable-next-line no-empty
} catch {}
}
execCallback = true;
call_this = $target;
call_event = event;
call_$selector = $target;
}
} else {
execCallback = true;
call_this = $elItem as Element;
call_event = event;
}
if (execCallback) {
const result = listenerCallBack.call(call_this!, call_event!, call_$selector!);
checkOptionOnceToRemoveEventListener();
if (typeof result === "boolean" && !result) {
return false;
}
}
};
// add listener
$elItem.addEventListener(eventName, handlerCallBack, listenerOption);
// 获取对象上的事件
const elementEvents: {
[k: string]: PopsDOMUtilsEventListenerOptionsAttribute[];
} = Reflect.get($elItem, GlobalData.domEventSymbol) || {};
// 初始化对象上的xx事件
elementEvents[eventName] = elementEvents[eventName] || [];
elementEvents[eventName].push({
selector: selectorList,
option: listenerOption,
handlerCallBack: handlerCallBack,
callback: listenerCallBack,
});
// 覆盖事件
Reflect.set($elItem, GlobalData.domEventSymbol, elementEvents);
});
});
return {
/**
* 取消绑定的监听事件
* @param filter (可选)过滤函数,对元素属性上的事件进行过滤出想要删除的事件
*/
off: (
filter?: (
value: PopsDOMUtilsEventListenerOptionsAttribute,
index: number,
array: PopsDOMUtilsEventListenerOptionsAttribute[]
) => boolean
) => {
that.off($elList, eventTypeList, selectorList, listenerCallBack, listenerOption, filter);
},
/**
* 主动触发事件
* @param extraDetails 赋予触发的Event的额外属性,如果是Event类型,那么将自动代替默认new的Event对象
* @param useDispatchToTriggerEvent 是否使用dispatchEvent来触发事件,默认true,如果为false,则直接调用callback,但是这种会让使用了`$selector`的没有值
*/
emit: (extraDetails?: object, useDispatchToTriggerEvent?: boolean) => {
that.emit($elList, eventTypeList, extraDetails, useDispatchToTriggerEvent);
},
};
}
/**
* 取消绑定事件
* @param element 需要取消绑定的元素|元素数组
* @param eventType 需要取消监听的事件
* @param callback 通过DOMUtils.on绑定的事件函数
* @param option
* + capture 如果在添加事件监听器时指定了useCapture为true,则在移除事件监听器时也必须指定为true
* @param filter (可选)过滤函数,对元素属性上的事件进行过滤出想要删除的事件
* @example
* // 取消监听元素a.xx所有的click事件
* DOMUtils.off(document.querySelector("a.xx"),"click")
* DOMUtils.off("a.xx","click")
*/
off<T extends PopsDOMUtils_EventType = PopsDOMUtils_EventType>(
element: PopsDOMUtilsElementEventType,
eventType: T | T[],
callback?: <E extends HTMLElement = HTMLElement>(this: E, event: PopsDOMUtils_Event[T]) => void,
option?: EventListenerOptions | boolean,
filter?: (
value: PopsDOMUtilsEventListenerOptionsAttribute,
index: number,
array: PopsDOMUtilsEventListenerOptionsAttribute[]
) => boolean
): void;
/**
* 取消绑定事件
* @param element 需要取消绑定的元素|元素数组
* @param eventType 需要取消监听的事件
* @param callback 通过DOMUtils.on绑定的事件函数
* @param option
* + capture 如果在添加事件监听器时指定了useCapture为true,则在移除事件监听器时也必须指定为true
* @param filter (可选)过滤函数,对元素属性上的事件进行过滤出想要删除的事件
* @example
* // 取消监听元素a.xx的click事件
* DOMUtils.off(document.querySelector("a.xx"),"click")
* DOMUtils.off("a.xx","click")
*/
off<T extends Event = Event>(
element: PopsDOMUtilsElementEventType,
eventType: string | string[],
callback?: <E extends HTMLElement = HTMLElement>(this: E, event: T) => void,
option?: EventListenerOptions | boolean,
filter?: (
value: PopsDOMUtilsEventListenerOptionsAttribute,
index: number,
array: PopsDOMUtilsEventListenerOptionsAttribute[]
) => boolean
): void;
/**
* 取消绑定事件
* @param element 需要取消绑定的元素|元素数组
* @param eventType 需要取消监听的事件
* @param selector 子元素选择器
* @param callback 通过DOMUtils.on绑定的事件函数
* @param option
* + capture 如果在添加事件监听器时指定了useCapture为true,则在移除事件监听器时也必须指定为true
* @param filter (可选)过滤函数,对元素属性上的事件进行过滤出想要删除的事件
* @example
* // 取消监听元素a.xx的click、tap、hover事件
* DOMUtils.off(document.querySelector("a.xx"),"click tap hover")
* DOMUtils.off("a.xx",["click","tap","hover"])
*/
off<T extends PopsDOMUtils_EventType = PopsDOMUtils_EventType>(
element: PopsDOMUtilsElementEventType,
eventType: T | T[],
selector?: string | string[] | undefined | null,
callback?: <E extends HTMLElement = HTMLElement>(this: E, event: PopsDOMUtils_Event[T], $selector: E) => void,
option?: EventListenerOptions | boolean,
filter?: (
value: PopsDOMUtilsEventListenerOptionsAttribute,
index: number,
array: PopsDOMUtilsEventListenerOptionsAttribute[]
) => boolean
): void;
/**
* 取消绑定事件
* @param element 需要取消绑定的元素|元素数组
* @param eventType 需要取消监听的事件
* @param selector 子元素选择器
* @param callback 通过DOMUtils.on绑定的事件函数
* @param option
* + capture 如果在添加事件监听器时指定了useCapture为true,则在移除事件监听器时也必须指定为true
* @param filter (可选)过滤函数,对元素属性上的事件进行过滤出想要删除的事件
* @example
* // 取消监听元素a.xx的click、tap、hover事件
* DOMUtils.off(document.querySelector("a.xx"),"click tap hover")
* DOMUtils.off("a.xx",["click","tap","hover"])
*/
off<T extends Event = Event>(
element: PopsDOMUtilsElementEventType,
eventType: string | string[],
selector?: string | string[] | undefined | null,
callback?: <E extends HTMLElement = HTMLElement>(this: E, event: T, $selector: E) => void,
option?: EventListenerOptions | boolean,
filter?: (
value: PopsDOMUtilsEventListenerOptionsAttribute,
index: number,
array: PopsDOMUtilsEventListenerOptionsAttribute[]
) => boolean
): void;
off<T extends Event = Event>(
element: HTMLElement | string | NodeList | HTMLElement[] | Window | Document | Element | null | typeof globalThis,
eventType: PopsDOMUtils_EventType | PopsDOMUtils_EventType[] | string | string[],
selector:
| string
| string[]
| undefined
| (<E extends HTMLElement = HTMLElement>(this: E, event: T, $selector: E) => void)
| null,
callback?:
| (<E extends HTMLElement = HTMLElement>(this: E, event: T, $selector: E) => void)
| EventListenerOptions
| boolean,
option?:
| EventListenerOptions
| boolean
| ((
value: PopsDOMUtilsEventListenerOptionsAttribute,
index: number,
array: PopsDOMUtilsEventListenerOptionsAttribute[]
) => boolean),
filter?: (
value: PopsDOMUtilsEventListenerOptionsAttribute,
index: number,
array: PopsDOMUtilsEventListenerOptionsAttribute[]
) => boolean
) {
/**
* 获取option配置
* @param args1
* @param startIndex
* @param option
*/
const getOption = function (args1: IArguments, startIndex: number, option: EventListenerOptions) {
const currentParam: boolean | PopsDOMUtilsEventListenerOption = args1[startIndex];
if (typeof currentParam === "boolean") {
option.capture = currentParam;
} else if (currentParam && typeof currentParam === "object" && "capture" in currentParam) {
option.capture = currentParam.capture;
}
return option;
};
const that = this;
// eslint-disable-next-line prefer-rest-params
const args = arguments;
if (typeof element === "string") {
element = that.selectorAll(element);
}
if (element == null) {
return;
}
let $elList: HTMLElement[] = [];
if (element instanceof NodeList || Array.isArray(element)) {
element = element as HTMLElement[];
$elList = $elList.concat(Array.from(element));
} else {
$elList.push(element as HTMLElement);
}
let eventTypeList: string[] = [];
if (Array.isArray(eventType)) {
eventTypeList = eventTypeList.concat(eventType.filter((it) => typeof it === "string" && it.toString() !== ""));
} else if (typeof eventType === "string") {
eventTypeList = eventTypeList.concat(eventType.split(" ").filter((it) => it !== ""));
}
// 子元素选择器
let selectorList: string[] = [];
if (Array.isArray(selector)) {
selectorList = selectorList.concat(selector.filter((it) => typeof it === "string" && it.toString() !== ""));
} else if (typeof selector === "string") {
selectorList.push(selector);
}
/**
* 事件的回调函数
*/
let listenerCallBack: (this: HTMLElement, event: T, $selector: HTMLElement) => void = callback as (
this: HTMLElement,
event: Event,
$selector?: HTMLElement
) => void | boolean;
/**
* 事件的配置
*/
let listenerOption: EventListenerOptions = {
capture: false,
};
if (typeof selector === "function") {
// 这是为没有selector的情况
// 那么它就是callback
listenerCallBack = selector;
listenerOption = getOption(args, 3, listenerOption);
} else {
// 这是存在selector的情况
listenerOption = getOption(args, 4, listenerOption);
}
if (args.length === 5 && typeof args[4] === "function" && typeof filter !== "function") {
// 目标函数、事件名、回调函数、事件配置、过滤函数
filter = option as (
value: PopsDOMUtilsEventListenerOptionsAttribute,
index: number,
array: PopsDOMUtilsEventListenerOptionsAttribute[]
) => boolean;
}
$elList.forEach(($elItem) => {
// 获取对象上的事件
const elementEvents: {
[key: string]: PopsDOMUtilsEventListenerOptionsAttribute[];
} = Reflect.get($elItem, GlobalData.domEventSymbol) || {};
eventTypeList.forEach((eventName) => {
const handlers = elementEvents[eventName] || [];
// 过滤出需要删除的事件
const handlersFiltered = typeof filter === "function" ? handlers.filter(filter) : handlers;
for (let index = 0; index < handlersFiltered.length; index++) {
const handler = handlersFiltered[index];
// 过滤出的事件再根据下面的条件进行判断处理移除
// 1. callback内存地址必须相同
// 2. selector必须相同
// 3. option.capture必须相同
let flag = true;
if (flag && listenerCallBack && handler.callback !== listenerCallBack) {
flag = false;
}
if (flag && selectorList.length && Array.isArray(handler.selector)) {
if (JSON.stringify(handler.selector) !== JSON.stringify(selectorList)) {
flag = false;
}
}
if (
flag &&
typeof handler.option.capture === "boolean" &&
listenerOption.capture !== handler.option.capture
) {
flag = false;
}
if (flag) {
$elItem.removeEventListener(eventName, handler.handlerCallBack, handler.option);
for (let i = handlers.length - 1; i >= 0; i--) {
if (handlers[i] === handler) {
handlers.splice(i, 1);
}
}
}
}
if (handlers.length === 0) {
// 如果没有任意的handler,那么删除该属性
CommonUtils.delete(elementEvents, eventType);
if (Object.keys(elementEvents).length === 0) {
CommonUtils.delete($elItem, GlobalData.domEventSymbol);
}
}
});
Reflect.set($elItem, GlobalData.domEventSymbol, elementEvents);
});
}
/**
* 取消绑定所有的事件
* @param element 需要取消绑定的元素|元素数组
* @param eventType (可选)需要取消监听的事件,不传入该参数则遍历所有监听的事件
*/
offAll(element: PopsDOMUtilsElementEventType, eventType?: string): void;
/**
* 取消绑定所有的事件
* @param element 需要取消绑定的元素|元素数组
* @param eventType (可选)需要取消监听的事件,不传入该参数则遍历所有监听的事件
*/
offAll(element: PopsDOMUtilsElementEventType, eventType?: PopsDOMUtils_EventType | PopsDOMUtils_EventType[]): void;
/**
* 取消绑定所有的事件
* @param element 需要取消绑定的元素|元素数组
* @param eventType (可选)需要取消监听的事件,不传入该参数则遍历所有监听的事件
*/
offAll(
element: PopsDOMUtilsElementEventType,
eventType?: PopsDOMUtils_EventType | PopsDOMUtils_EventType[] | string
) {
const that = this;
if (typeof element === "string") {
element = that.selectorAll(element);
}
if (element == null) {
return;
}
let $elList: (Element | Document | Window | typeof globalThis | Node | ChildNode | EventTarget)[] = [];
if (element instanceof NodeList || Array.isArray(element)) {
$elList = $elList.concat(Array.from(element as HTMLElement[]));
} else {
$elList.push(element);
}
let eventTypeList: string[] = [];
if (Array.isArray(eventType)) {
eventTypeList = eventTypeList.concat(eventType as string[]);
} else if (typeof eventType === "string") {
eventTypeList = eventTypeList.concat(eventType.split(" "));
}
$elList.forEach(($elItem) => {
const symbolList = [...new Set([...Object.getOwnPropertySymbols($elItem), GlobalData.domEventSymbol])];
symbolList.forEach((__symbol__) => {
if (!__symbol__.toString().startsWith("Symbol(events_")) {
return;
}
const elementEvents: {
[key: string]: PopsDOMUtilsEventListenerOptionsAttribute[];
} = Reflect.get($elItem, __symbol__) || {};
const iterEventNameList = eventTypeList.length ? eventTypeList : Object.keys(elementEvents);
iterEventNameList.forEach((eventName) => {
const handlers: PopsDOMUtilsEventListenerOptionsAttribute[] = elementEvents[eventName];
if (!handlers) {
return;
}
for (const handler of handlers) {
$elItem.removeEventListener(eventName, handler.handlerCallBack, {
capture: handler.option.capture,
});
}
const events = Reflect.get($elItem, __symbol__);
CommonUtils.delete(events, eventName);
if (Object.keys(events).length === 0) {
CommonUtils.delete($elItem, __symbol__);
}
});
});
});
}
/**
* 等待文档加载完成后执行指定的函数
* @param callback 需要执行的函数
* @example
* DOMUtils.onReady(function(){
* console.log("文档加载完毕")
* })
*/
onReady<T extends (...args: any[]) => any>(callback: T) {
const that = this;
if (typeof callback !== "function") {
return;
}
/**
* 检测文档是否加载完毕
*/
function checkDOMReadyState() {
try {
if (
document.readyState === "complete" ||
(document.readyState !== "loading" && !(document.documentElement as any).doScroll)
) {
return true;
} else {
return false;
}
} catch {
return false;
}
}
/**
* 成功加载完毕后触发的回调函数
*/
function completed() {
removeDomReadyListener();
callback();
}
const targetList = [
{
target: PopsCore.document,
eventType: "DOMContentLoaded",
callback: completed,
},
{
target: PopsCore.window,
eventType: "load",
callback: completed,
},
];
/**
* 添加监听
*/
function addDomReadyListener() {
for (let index = 0; index < targetList.length; index++) {
const item = targetList[index];
that.on(item.target, item.eventType, item.callback);
}
}
/**
* 移除监听
*/
function removeDomReadyListener() {
for (let index = 0; index < targetList.length; index++) {
const item = targetList[index];
that.off(item.target, item.eventType, item.callback);
}
}
if (checkDOMReadyState()) {
// 检查document状态
popsUtils.setTimeout(callback, 0);
} else {
// 添加监听
addDomReadyListener();
}
}
/**
* 主动触发事件
* @param element 需要触发的元素|元素数组|window
* @param eventType 需要触发的事件
* @param useDispatchToTriggerEvent 是否使用dispatchEvent来触发事件,默认true,如果为false,则直接调用通过.on监听的callback,但是这种会让使用了$selector的没有值
* @example
* // 触发元素a.xx的click事件
* DOMUtils.emit(document.querySelector("a.xx"),"click")
* DOMUtils.emit("a.xx","click")
* // 触发元素a.xx的click、tap、hover事件
* DOMUtils.emit(document.querySelector("a.xx"),"click tap hover")
* DOMUtils.emit("a.xx",["click","tap","hover"])
*/
emit(
element: PopsDOMUtilsTargetElementType | Element | DocumentFragment | any[] | typeof globalThis | Window | Document,
eventType: string | string[],
useDispatchToTriggerEvent?: boolean
): void;
/**
* 主动触发事件
* @param element 需要触发的元素|元素数组|window
* @param eventType 需要触发的事件
* @param extraDetails 赋予触发的Event的额外属性,如果是Event类型,那么将自动代替默认new的Event对象
* @param useDispatchToTriggerEvent 是否使用dispatchEvent来触发事件,默认true,如果为false,则直接调用通过.on监听的callback(),但是这种只有一个入参,如果使用$selector则没有值
* @example
* // 触发元素a.xx的click事件
* DOMUtils.emit(document.querySelector("a.xx"),"click")
* DOMUtils.emit("a.xx","click")
* // 触发元素a.xx的click、tap、hover事件
* DOMUtils.emit(document.querySelector("a.xx"),"click tap hover")
* DOMUtils.emit("a.xx",["click","tap","hover"])
*/
emit(
element: PopsDOMUtilsTargetElementType | Element | DocumentFragment | any[] | typeof globalThis | Window | Document,
eventType: string | string[],
extraDetails?: object,
useDispatchToTriggerEvent?: boolean
): void;
/**
* 主动触发事件
* @param element 需要触发的元素|元素数组|window
* @param eventType 需要触发的事件
* @param useDispatchToTriggerEvent 是否使用dispatchEvent来触发事件,默认true,如果为false,则直接调用通过.on监听的callback(),但是这种只有一个入参,如果使用$selector则没有值
* @example
* // 触发元素a.xx的click事件
* DOMUtils.emit(document.querySelector("a.xx"),"click")
* DOMUtils.emit("a.xx","click")
* // 触发元素a.xx的click、tap、hover事件
* DOMUtils.emit(document.querySelector("a.xx"),"click tap hover")
* DOMUtils.emit("a.xx",["click","tap","hover"])
*/
emit(
element: Element | string | NodeList | any[] | Window | Document,
eventType: PopsDOMUtils_EventType | PopsDOMUtils_EventType[],
useDispatchToTriggerEvent?: boolean
): void;
/**
* 主动触发事件
* @param element 需要触发的元素|元素数组|window
* @param event 触发的事件
* @param extraDetails (可选)赋予触发的Event的额外属性
* @param useDispatchToTriggerEvent (可选)是否使用dispatchEvent来触发事件,默认true,如果为false,则直接调用通过.on监听的callback(),但是这种只有一个入参,如果使用$selector则没有值
* @example
* DOMUtils.emit("a.xx", new Event("click"))
* @example
* DOMUtils.emit("a.xx", new Event("click"), {
* disableHook: true
* })
* @example
* DOMUtils.emit("a.xx", new Event("click"), {
* disableHook: true
* },false)
*/
emit(
element: Element | string | NodeList | any[] | Window | Document,
event: Event,
extraDetails?: object,
useDispatchToTriggerEvent?: boolean
): void;
/**
* 主动触发事件
* @param element 需要触发的元素|元素数组|window
* @param eventType 需要触发的事件
* @param extraDetails 赋予触发的Event的额外属性,如果是Event类型,那么将自动代替默认new的Event对象
* @param useDispatchToTriggerEvent 是否使用dispatchEvent来触发事件,默认true,如果为false,则直接调用通过.on监听的callback(),但是这种只有一个入参,如果使用$selector则没有值
* @example
* // 触发元素a.xx的click事件
* DOMUtils.emit(document.querySelector("a.xx"),"click")
* DOMUtils.emit("a.xx","click")
* // 触发元素a.xx的click、tap、hover事件
* DOMUtils.emit(document.querySelector("a.xx"),"click tap hover")
* DOMUtils.emit("a.xx",["click","tap","hover"])
*/
emit(
element: Element | string | NodeList | any[] | Window | Document,
eventType: PopsDOMUtils_EventType | PopsDOMUtils_EventType[] | string | string[] | Event,
extraDetails?: object | boolean,
useDispatchToTriggerEvent: boolean = true
) {
const that = this;
if (typeof element === "string") {
element = that.selectorAll(element);
}
if (element == null) {
return;
}
let $elList: (Document | Window | Element)[] = [];
if (element instanceof NodeList || Array.isArray(element)) {
$elList = $elList.concat(Array.from(element));
} else {
$elList.push(element);
}
/**
* 主动添加属性
*/
const addExtraProp = (event: Event, obj: any) => {
if (event instanceof Event && typeof obj === "object" && obj != null && !Array.isArray(obj)) {
const detailKeys = Object.keys(obj);
detailKeys.forEach((keyName) => {
const value = Reflect.get(obj, keyName);
// 在event上添加属性
Reflect.set(event, keyName, value);
});
}
};
let eventTypeList: string[] = [];
/**
* 主动传递的事件
*/
let __event__: Event | null = null;
if (Array.isArray(eventType)) {
eventTypeList = eventType.filter((it) => typeof it === "string" && it.trim() !== "");
} else if (typeof eventType === "string") {
eventTypeList = eventType.split(" ");
} else if (eventType instanceof Event) {
__event__ = eventType;
addExtraProp(__event__, extraDetails);
}
$elList.forEach(($elItem) => {
/* 获取对象上的事件 */
const elementEvents: {
[key: string]: PopsDOMUtilsEventListenerOptionsAttribute[];
} = Reflect.get($elItem, GlobalData.domEventSymbol) || {};
/**
* 触发事件
*/
const dispatchEvent = (event: Event, eventTypeItem: string) => {
if (useDispatchToTriggerEvent == false && eventTypeItem in elementEvents) {
// 直接调用.on监听的事件
elementEvents[eventTypeItem].forEach((eventsItem) => {
eventsItem.handlerCallBack(event);
});
} else {
$elItem.dispatchEvent(event);
}
};
if (__event__) {
// 使用主动传递的事件直接触发
const event = __event__;
const eventTypeItem = event.type;
dispatchEvent(event, eventTypeItem);
} else {
eventTypeList.forEach((eventTypeItem) => {
// 构造事件
const event = new Event(eventTypeItem);
addExtraProp(event, extraDetails);
dispatchEvent(event, eventTypeItem);
});
}
});
}
/**
* 当按键松开时触发事件
* keydown - > keypress - > keyup
* @param element 当前元素
* @param handler 事件处理函数
* @param option 配置
* @example
* // 监听a.xx元素的按键松开
* DOMUtils.keyup(document.querySelector("a.xx"),()=>{
* console.log("按键松开");
* })
* DOMUtils.keyup("a.xx",()=>{
* console.log("按键松开");
* })
*/
onKeyup(
element: PopsDOMUtilsTargetElementType | Element | DocumentFragment | Window | Node | typeof globalThis,
handler: (this: HTMLElement, event: PopsDOMUtils_Event["keyup"]) => void,
option?: boolean | PopsDOMUtilsEventListenerOption
) {
const that = this;
if (element == null) {
return;
}
if (typeof element === "string") {
element = that.selectorAll(element);
}
if (CommonUtils.isNodeList(element)) {
// 设置
const listenerList: (PopsDOMUtilsAddEventListenerResult | undefined)[] = [];
element.forEach(($ele) => {
const listener = that.onKeyup($ele as HTMLElement, handler, option);
listenerList.push(listener);
});
return {
off() {
listenerList.forEach((listener) => {
if (!listener) {
return;
}
listener.off();
});
},
} as PopsDOMUtilsAddEventListenerResult;
}
return that.on(element, "keyup", null, handler, option);
}
/**
* 当按键按下时触发事件
* keydown - > keypress(已弃用) - > keyup
* @param element 目标
* @param handler 事件处理函数
* @param option 配置
* @example
* // 监听a.xx元素的按键按下
* DOMUtils.keydown(document.querySelector("a.xx"),()=>{
* console.log("按键按下");
* })
* DOMUtils.keydown("a.xx",()=>{
* console.log("按键按下");
* })
*/
onKeydown(
element: PopsDOMUtilsTargetElementType | Element | DocumentFragment | Window | Node | typeof globalThis,
handler: (this: HTMLElement, event: PopsDOMUtils_Event["keydown"]) => void,
option?: boolean | PopsDOMUtilsEventListenerOption
) {
const that = this;
if (element == null) {
return;
}
if (typeof element === "string") {
element = that.selectorAll(element);
}
if (CommonUtils.isNodeList(element)) {
// 设置
const listenerList: (PopsDOMUtilsAddEventListenerResult | undefined)[] = [];
element.forEach(($ele) => {
const listener = that.onKeydown($ele as HTMLElement, handler, option);
listenerList.push(listener);
});
return {
off() {
listenerList.forEach((listener) => {
if (!listener) {
return;
}
listener.off();
});
},
} as PopsDOMUtilsAddEventListenerResult;
}
return that.on(element, "keydown", null, handler, option);
}
/**
* 阻止事件传递
*
* + `.preventDefault()`: 阻止事件的默认行为发生。例如,当点击一个链接时,浏览器会默认打开链接的URL,或者在输入框内输入文字
* + `.stopPropagation()`: 停止事件的传播,阻止它继续向更上层的元素冒泡,事件将不会再传播给其他的元素
* + `.stopImmediatePropagation()`: 阻止事件传播,并且还能阻止元素上的其他事件处理程序被触发
* @param event 要阻止传递的事件
* @example
* DOMUtils.preventEvent(event);
*/
preventEvent(event: Event): false;
/**
* 阻止事件传递
* @param event 要阻止传递的事件
* @param onlyStopPropagation (可选)是否仅阻止事件的传播,默认false,不调用`.preventDefault()`
* @example
* DOMUtils.preventEvent(event, true);
*/
preventEvent<T extends boolean>(event: Event, onlyStopPropagation: T): T extends true ? void : false;
/**
* 通过监听事件来主动阻止事件的传递
* @param $el 要进行处理的元素
* @param eventNameList 要阻止的事件名|列表
* @param option (可选)配置项
* @example
* DOMUtils.preventEvent(document.querySelector("a"), "click")
* @example
* DOMUtils.preventEvent(document.querySelector("a"), "click", undefined, {
* capture: true,
* })
* @example
* DOMUtils.preventEvent(document, "click", "a.xxx", {
* capture: true,
* onlyStopPropagation: true,
* })
*/
preventEvent(
$el: Element | Document | ShadowRoot,
eventNameList: string | string[],
option?: {
/** (可选)是否捕获,默认false */
capture?: boolean;
/** (可选)是否仅阻止事件的传播,默认false,不调用`.preventDefault()` */
onlyStopPropagation?: boolean;
}
): {
/** 移除监听事件 */
off(): void;
};
/**
* 通过监听事件来主动阻止事件的传递
* @param $el 要进行处理的元素
* @param eventNameList 要阻止的事件名|列表
* @param selector 子元素选择器
* @param option (可选)配置项
* @example
* DOMUtils.preventEvent(document.querySelector("a"), "click")
* @example
* DOMUtils.preventEvent(document.querySelector("a"), "click", undefined, {
* capture: true,
* })
* @example
* DOMUtils.preventEvent(document, "click", "a.xxx", {
* capture: true,
* onlyStopPropagation: true,
* })
*/
preventEvent(
$el: Element | Document | ShadowRoot,
eventNameList: string | string[],
selector: string | string[] | null | undefined,
option?: {
/** (可选)是否捕获,默认false */
capture?: boolean;
/** (可选)是否仅阻止事件的传播,默认false,不调用`.preventDefault()` */
onlyStopPropagation?: boolean;
}
): {
/** 移除监听事件 */
off(): void;
};
preventEvent(...args: any[]): boolean | void | { off(): void } {
/**
* 阻止事件的默认行为发生,并阻止事件传播
*/
const stopEvent = (event: Event, onlyStopPropagation?: boolean) => {
// 停止事件的传播,阻止它继续向更上层的元素冒泡,事件将不会再传播给其他的元素
event?.stopPropagation();
// 阻止事件传播,并且还能阻止元素上的其他事件处理程序被触发
event?.stopImmediatePropagation();
if (typeof onlyStopPropagation === "boolean" && onlyStopPropagation) {
return;
}
// 阻止事件的默认行为发生。例如,当点击一个链接时,浏览器会默认打开链接的URL,或者在输入框内输入文字
event?.preventDefault();
return false;
};
if (args[0] instanceof Event) {
// 直接阻止事件
const onlyStopPropagation: boolean = args[1];
return stopEvent(args[0], onlyStopPropagation);
} else {
const $el: Element | Document | ShadowRoot = args[0];
let eventNameList: string | string[] = args[1];
let selector: string | string[] | null | undefined = void 0;
let capture = false;
let onlyStopPropagation = false;
// 添加对应的事件来阻止触发
if (typeof eventNameList === "string") {
eventNameList = [eventNameList];
}
let option:
| {
capture?: boolean;
onlyStopPropagation?: boolean;
}
| undefined = void 0;
if (args.length === 2) {
// ignore
} else if (typeof args[2] === "string" || Array.isArray(args[2])) {
// selector
selector = args[2];
if (typeof args[3] === "object" && args[3] != null) {
option = args[3];
}
} else if (typeof args[2] === "object" && args[2] != null && !Array.isArray(args[2])) {
// option
option = args[2];
} else {
throw new TypeError("Invalid argument");
}
if (option) {
capture = Boolean(option.capture);
onlyStopPropagation = Boolean(option.onlyStopPropagation);
}
const listener = this.on(
$el,
eventNameList,
selector,
(evt) => {
return stopEvent(evt, onlyStopPropagation);
},
{ capture: capture }
);
return listener;
}
}
/**
* 选择器,可使用以下的额外语法
*
* + :contains([text]) 作用: 找到包含指定文本内容的指定元素
* + :empty 作用:找到既没有文本内容也没有子元素的指定元素
* + :regexp([text]) 作用: 找到符合正则表达式的内容的指定元素
* @param selector 选择器
* @example
* DOMUtils.selector("div:contains('测试')")
* > div.xxx
* @example
* DOMUtils.selector("div:empty")
* > div.xxx
* @example
* DOMUtils.selector("div:regexp('^xxxx$')")
* > div.xxx
*/
selector<K extends keyof HTMLElementTagNameMap>(selector: K): HTMLElementTagNameMap[K] | undefined;
selector<E extends Element = Element>(selector: string): E | undefined;
selector<E extends Element = Element>(selector: string) {
return this.selectorAll<E>(selector)[0];
}
/**
* 选择器,可使用以下的额外语法
*
* + :contains([text]) 作用: 找到包含指定文本内容的指定元素
* + :empty 作用:找到既没有文本内容也没有子元素的指定元素
* + :regexp([text]) 作用: 找到符合正则表达式的内容的指定元素
* @param selector 选择器
* @example
* DOMUtils.selectorAll("div:contains('测试')")
* > [div.xxx]
* @example
* DOMUtils.selectorAll("div:empty")
* > [div.xxx]
* @example
* DOMUtils.selectorAll("div:regexp('^xxxx$')")
* > [div.xxx]
* @example
* DOMUtils.selectorAll("div:regexp(/^xxx/ig)")
* > [div.xxx]
*/
selectorAll<K extends keyof HTMLElementTagNameMap>(selector: K): HTMLElementTagNameMap[K][];
selectorAll<E extends Element = Element>(selector: string): E[];
selectorAll<E extends Element = Element>(selector: string) {
selector = selector.trim();
if (selector.match(/[^\s]{1}:empty$/gi)) {
// empty 语法
selector = selector.replace(/:empty$/gi, "");
return Array.from(PopsCore.document.querySelectorAll<E>(selector)).filter(($ele) => {
return $ele?.innerHTML?.trim() === "";
});
} else if (selector.match(/[^\s]{1}:contains\("(.*)"\)$/i) || selector.match(/[^\s]{1}:contains\('(.*)'\)$/i)) {
// contains 语法
const textMatch = selector.match(/:contains\(("|')(.*)("|')\)$/i);
const text = textMatch![2];
selector = selector.replace(/:contains\(("|')(.*)("|')\)$/gi, "");
return Array.from(PopsCore.document.querySelectorAll<E>(selector)).filter(($ele) => {
return ($ele?.textContent || (<any>$ele)?.innerText)?.includes(text);
});
} else if (selector.match(/[^\s]{1}:regexp\("(.*)"\)$/i) || selector.match(/[^\s]{1}:regexp\('(.*)'\)$/i)) {
// regexp 语法
const textMatch = selector.match(/:regexp\(("|')(.*)("|')\)$/i);
let pattern = textMatch![2];
const flagMatch = pattern.match(/("|'),[\s]*("|')([igm]{0,3})$/i);
let flags = "";
if (flagMatch) {
pattern = pattern.replace(/("|'),[\s]*("|')([igm]{0,3})$/gi, "");
flags = flagMatch[3];
}
const regexp = new RegExp(pattern, flags);
selector = selector.replace(/:regexp\(("|')(.*)("|')\)$/gi, "");
return Array.from(PopsCore.document.querySelectorAll<E>(selector)).filter(($ele) => {
return Boolean(($ele?.textContent || (<any>$ele)?.innerText)?.match(regexp));
});
} else {
// 普通语法
return Array.from(PopsCore.document.querySelectorAll<E>(selector));
}
}
/**
* 匹配元素,可使用以下的额外语法
*
* + :contains([text]) 作用: 找到包含指定文本内容的指定元素
* + :empty 作用:找到既没有文本内容也没有子元素的指定元素
* + :regexp([text]) 作用: 找到符合正则表达式的内容的指定元素
* @param $el 元素
* @param selector 选择器
* @example
* DOMUtils.matches("div:contains('测试')")
* > true
* @example
* DOMUtils.matches("div:empty")
* > true
* @example
* DOMUtils.matches("div:regexp('^xxxx$')")
* > true
* @example
* DOMUtils.matches("div:regexp(/^xxx/ig)")
* > false
*/
matches($el: HTMLElement | Element | null | undefined, selector: string): boolean {
selector = selector.trim();
if ($el == null) {
return false;
}
if (selector.match(/[^\s]{1}:empty$/gi)) {
// empty 语法
selector = selector.replace(/:empty$/gi, "");
return $el.matches(selector) && $el?.innerHTML?.trim() === "";
} else if (selector.match(/[^\s]{1}:contains\("(.*)"\)$/i) || selector.match(/[^\s]{1}:contains\('(.*)'\)$/i)) {
// contains 语法
const textMatch = selector.match(/:contains\(("|')(.*)("|')\)$/i);
const text = textMatch![2];
selector = selector.replace(/:contains\(("|')(.*)("|')\)$/gi, "");
let content = $el?.textContent || (<any>$el)?.innerText;
if (typeof content !== "string") {
content = "";
}
return $el.matches(selector) && content?.includes(text);
} else if (selector.match(/[^\s]{1}:regexp\("(.*)"\)$/i) || selector.match(/[^\s]{1}:regexp\('(.*)'\)$/i)) {
// regexp 语法
const textMatch = selector.match(/:regexp\(("|')(.*)("|')\)$/i);
let pattern = textMatch![2];
const flagMatch = pattern.match(/("|'),[\s]*("|')([igm]{0,3})$/i);
let flags = "";
if (flagMatch) {
pattern = pattern.replace(/("|'),[\s]*("|')([igm]{0,3})$/gi, "");
flags = flagMatch[3];
}
const regexp = new RegExp(pattern, flags);
selector = selector.replace(/:regexp\(("|')(.*)("|')\)$/gi, "");
let content = $el?.textContent || (<any>$el)?.innerText;
if (typeof content !== "string") {
content = "";
}
return $el.matches(selector) && Boolean(content?.match(regexp));
} else {
// 普通语法
return $el.matches(selector);
}
}
/**
* 根据选择器获取上层元素,可使用以下的额外语法
*
* + :contains([text]) 作用: 找到包含指定文本内容的指定元素
* + :empty 作用:找到既没有文本内容也没有子元素的指定元素
* + :regexp([text]) 作用: 找到符合正则表达式的内容的指定元素
* @param $el 元素
* @param selector 选择器
* @example
* DOMUtils.closest("div:contains('测试')")
* > div.xxx
* @example
* DOMUtils.closest("div:empty")
* > div.xxx
* @example
* DOMUtils.closest("div:regexp('^xxxx$')")
* > div.xxxx
* @example
* DOMUtils.closest("div:regexp(/^xxx/ig)")
* > null
*/
closest<K extends keyof HTMLElementTagNameMap>(
$el: HTMLElement | Element,
selector: string
): HTMLElementTagNameMap[K] | null;
closest<E extends Element = Element>($el: HTMLElement | Element, selector: string): E | null;
closest<E extends Element = Element>($el: HTMLElement | Element, selector: string): E | null {
selector = selector.trim();
if (selector.match(/[^\s]{1}:empty$/gi)) {
// empty 语法
selector = selector.replace(/:empty$/gi, "");
const $closest = $el?.closest<E>(selector);
if ($closest && $closest?.innerHTML?.trim() === "") {
return $closest;
}
return null;
} else if (selector.match(/[^\s]{1}:contains\("(.*)"\)$/i) || selector.match(/[^\s]{1}:contains\('(.*)'\)$/i)) {
// contains 语法
const textMatch = selector.match(/:contains\(("|')(.*)("|')\)$/i);
const text = textMatch![2];
selector = selector.replace(/:contains\(("|')(.*)("|')\)$/gi, "");
const $closest = $el?.closest<E>(selector);
if ($closest) {
const content = $el?.textContent || (<any>$el)?.innerText;
if (typeof content === "string" && content.includes(text)) {
return $closest;
}
}
return null;
} else if (selector.match(/[^\s]{1}:regexp\("(.*)"\)$/i) || selector.match(/[^\s]{1}:regexp\('(.*)'\)$/i)) {
// regexp 语法
const textMatch = selector.match(/:regexp\(("|')(.*)("|')\)$/i);
let pattern = textMatch![2];
const flagMatch = pattern.match(/("|'),[\s]*("|')([igm]{0,3})$/i);
let flags = "";
if (flagMatch) {
pattern = pattern.replace(/("|'),[\s]*("|')([igm]{0,3})$/gi, "");
flags = flagMatch[3];
}
const regexp = new RegExp(pattern, flags);
selector = selector.replace(/:regexp\(("|')(.*)("|')\)$/gi, "");
const $closest = $el?.closest<E>(selector);
if ($closest) {
const content = $el?.textContent || (<any>$el)?.innerText;
if (typeof content === "string" && content.match(regexp)) {
return $closest;
}
}
return null;
} else {
// 普通语法
const $closest = $el?.closest<E>(selector);
return $closest;
}
}
/**
* 监input、textarea的输入框值改变的事件
*/
onInput(
$el: HTMLInputElement | HTMLTextAreaElement,
callback: (evt: InputEvent) => void | Promise<void>,
option?: PopsDOMUtilsEventListenerOption | boolean
) {
/**
* 是否正在输入中
*/
let isComposite = false;
const __callback = async (event: InputEvent) => {
if (isComposite) return;
await callback(event);
};
const __composition_start_callback = () => {
isComposite = true;
};
const __composition_end_callback = () => {
isComposite = false;
this.emit($el, "input", {
isComposite,
});
};
const inputListener = this.on($el, "input", __callback, option);
const compositionStartListener = this.on($el, "compositionstart", __composition_start_callback, option);
const compositionEndListener = this.on($el, "compositionend", __composition_end_callback, option);
return {
off: () => {
inputListener.off();
compositionStartListener.off();
compositionEndListener.off();
},
};
}
}
class PopsDOMUtils extends PopsDOMUtilsEvent {
/** 获取 animationend 在各个浏览器的兼容名 */
getAnimationEndNameList() {
return ["webkitAnimationEnd", "mozAnimationEnd", "MSAnimationEnd", "oanimationend", "animationend"];
}
/** 获取 transitionend 在各个浏览器的兼容名 */
getTransitionEndNameList() {
return ["webkitTransitionEnd", "mozTransitionEnd", "MSTransitionEnd", "otransitionend", "transitionend"];
}
/**
* 实现jQuery中的$().offset();
* @param element
* @param calcScroll 计算滚动距离
*/
offset(element: HTMLElement, calcScroll: boolean = true) {
const rect = element.getBoundingClientRect();
const win = element.ownerDocument.defaultView;
const resultRect = new DOMRect(
calcScroll ? parseFloat((rect.left + (win?.pageXOffset || 0)).toString()) : rect.left,
calcScroll ? pars