@whitesev/pops
Version:
弹窗库,包含了alert、confirm、prompt、drawer、folder、loading、iframe、panel、tooltip、searchSuggestion、rightClickMenu组件
639 lines (631 loc) • 19.1 kB
text/typescript
import doubletap from "@any-touch/doubletap";
import AnyTouch from "any-touch";
import { EventEmiter } from "../event/EventEmiter";
import { PopsCore } from "../PopsCore";
import type { PopsUtilsOwnObject } from "../types/main";
class PopsUtils {
/**
* 超时时间
*/
sleep(timeout: number) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(true);
}, timeout);
});
}
/**
* 判断是否是window,例如window、self、globalThis
* @param target
*/
isWin(target: any) {
if (typeof target !== "object") {
return false;
}
if (target instanceof Node) {
return false;
}
if (target === globalThis) {
return true;
}
if (target === window) {
return true;
}
if (target === self) {
return true;
}
if (target === PopsCore.globalThis) {
return true;
}
if (target === PopsCore.window) {
return true;
}
if (target === PopsCore.self) {
return true;
}
if (typeof unsafeWindow !== "undefined" && target === unsafeWindow) {
return true;
}
if (target?.Math?.toString() !== "[object Math]") {
return false;
}
return true;
}
/**
* 判断对象是否是元素
* @param target
* @returns
* + true 是元素
* + false 不是元素
* @example
* Utils.isDOM(document.querySelector("a"))
* > true
*/
isDOM(target: any): boolean;
isDOM(target: any): boolean {
return target instanceof Node;
}
/**
* 判断是否是元素列表
* @param $ele
*/
isNodeList($ele: any): $ele is any[] | NodeList {
return Array.isArray($ele) || $ele instanceof NodeList;
}
/**
* 删除对象上的属性
* @param target
* @param propName
*/
delete(target: any, propName: any) {
if (typeof Reflect === "object" && typeof Reflect.deleteProperty === "function") {
Reflect.deleteProperty(target, propName);
} else {
delete target[propName];
}
}
/**
* JSON数据从源端替换到目标端中,如果目标端存在该数据则替换,不添加,返回结果为目标端替换完毕的结果
* @param target 目标数据
* @param source 源数据
* @param isAdd 是否可以追加键,默认false
* @example
* Utils.assign({"1":1,"2":{"3":3}}, {"2":{"3":4}});
* >
* {
"1": 1,
"2": {
"3": 4
}
}
*/
assign<T1, T2 extends object, T3 extends boolean>(target: T1, source: T2, isAdd?: T3): T3 extends true ? T1 & T2 : T1;
assign(target = {}, source = {}, isAdd = false) {
const UtilsContext = this;
if (Array.isArray(source)) {
const canTraverse = source.filter((item) => {
return typeof item === "object";
});
if (!canTraverse.length) {
return source;
}
}
if (source == null) {
return target;
}
if (target == null) {
target = {};
}
// 当前遍历的目标对象
let iteratorTarget;
if (isAdd) {
// 追加并覆盖是以source为准
iteratorTarget = source;
} else {
// 覆盖以target为准
iteratorTarget = target;
}
for (const keyName in iteratorTarget) {
if (!isAdd && !(keyName in source)) {
// 仅替换 但是源端没有此键
continue;
}
const targetValue = Reflect.get(target, keyName);
const sourceValue = Reflect.get(source, keyName);
if (
typeof sourceValue === "object" &&
sourceValue != null &&
keyName in target &&
!UtilsContext.isDOM(sourceValue) &&
!(sourceValue instanceof EventEmiter)
) {
// 源端的值是object类型,且不是元素节点
// 如果是数组,那此数组中有值,清空旧的数组再赋值
let childObjectValue;
if (Array.isArray(sourceValue)) {
if (Array.isArray(targetValue)) {
targetValue.length = 0;
}
childObjectValue = sourceValue;
} else {
childObjectValue = UtilsContext.assign(targetValue, sourceValue, isAdd);
}
Reflect.set(target, keyName, childObjectValue);
} else {
// 直接赋值
Reflect.set(target, keyName, sourceValue);
}
}
return target;
}
/**
* 生成uuid
*/
getRandomGUID() {
if (typeof PopsCore.globalThis?.crypto?.randomUUID === "function") {
return PopsCore.globalThis.crypto.randomUUID();
} else {
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (charStr) {
const randomValue = (Math.random() * 16) | 0,
randomCharValue = charStr === "x" ? randomValue : (randomValue & 0x3) | 0x8;
return randomCharValue.toString(16);
});
}
}
/**
* 判断元素/页面中是否包含该元素
* @param target 需要判断的元素
* @param context 默认为body
*/
contains(target: any): boolean;
contains(context: any, target?: any): boolean;
contains(...args: any[]): boolean {
const [context, target] = args;
if (args.length === 1) {
// 只判断该页面是否存在该元素
return this.contains(PopsCore.document.body || PopsCore.document.documentElement, args[0]);
} else {
if (target == null) {
return false;
}
if (typeof target[Symbol.iterator] === "function") {
// 可遍历的数组
let flag = true;
for (const targetNode of target as any) {
if (!context.contains(targetNode)) {
flag = false;
break;
}
}
return flag;
} else {
return context.contains(target);
}
}
}
/**
* 获取格式化后的时间
* @param text (可选)需要格式化的字符串或者时间戳,默认:new Date()
* @param formatType (可选)格式化成的显示类型,默认:yyyy-MM-dd HH:mm:ss
* + yyyy 年
* + MM 月
* + dd 天
* + HH 时 (24小时制)
* + hh 时 (12小时制)
* + mm 分
* + ss 秒
* @returns {string} 返回格式化后的时间
* @example
* Utils.formatTime("2022-08-21 23:59:00","HH:mm:ss");
* > '23:59:00'
* @example
* Utils.formatTime(1899187424988,"HH:mm:ss");
* > '15:10:13'
* @example
* Utils.formatTime()
* > '2023-1-1 00:00:00'
**/
formatTime(text?: string | number | Date, formatType?: string): string;
/**
* 获取格式化后的时间
* @param text (可选)需要格式化的字符串或者时间戳,默认:new Date()
* @param formatType (可选)格式化成的显示类型,默认:yyyy-MM-dd HH:mm:ss
* + yyyy 年
* + MM 月
* + dd 天
* + HH 时 (24小时制)
* + hh 时 (12小时制)
* + mm 分
* + ss 秒
* @returns {string} 返回格式化后的时间
* @example
* Utils.formatTime("2022-08-21 23:59:00","HH:mm:ss");
* > '23:59:00'
* @example
* Utils.formatTime(1899187424988,"HH:mm:ss");
* > '15:10:13'
* @example
* Utils.formatTime()
* > '2023-1-1 00:00:00'
**/
formatTime(
text?: string | number | Date,
formatType?:
| "yyyy-MM-dd HH:mm:ss"
| "yyyy/MM/dd HH:mm:ss"
| "yyyy_MM_dd_HH_mm_ss"
| "yyyy年MM月dd日 HH时mm分ss秒"
| "yyyy年MM月dd日 hh:mm:ss"
| "yyyy年MM月dd日 HH:mm:ss"
| "yyyy-MM-dd"
| "yyyyMMdd"
| "HH:mm:ss"
): string;
formatTime(text = new Date(), formatType = "yyyy-MM-dd HH:mm:ss") {
const time = text == null ? new Date() : new Date(text);
/**
* 校验时间补0
* @param timeNum
* @returns
*/
function checkTime(timeNum: number) {
if (timeNum < 10) return "0" + timeNum;
return timeNum;
}
/**
* 时间制修改 24小时制转12小时制
* @param hourNum 小时
* @returns
*/
function timeSystemChange(hourNum: number) {
return hourNum > 12 ? hourNum - 12 : hourNum;
}
const timeRegexp = {
yyyy: time.getFullYear(),
// 年
MM: checkTime(time.getMonth() + 1),
// 月
dd: checkTime(time.getDate()),
// 日
HH: checkTime(time.getHours()),
// 时 (24小时制)
hh: checkTime(timeSystemChange(time.getHours())),
// 时 (12小时制)
mm: checkTime(time.getMinutes()),
// 分
ss: checkTime(time.getSeconds()),
// 秒
};
Object.keys(timeRegexp).forEach(function (key) {
const replaecRegexp = new RegExp(key, "g");
formatType = formatType.replace(replaecRegexp, (timeRegexp as any)[key]);
});
return formatType;
}
/**
* 格式化byte为KB、MB、GB、TB、PB、EB、ZB、YB、BB、NB、DB
* @param byteSize 字节
* @param addType (可选)是否添加单位
* + true (默认) 添加单位
* + false 不添加单位
* @returns
* + {string} 当addType为true时,且保留小数点末尾2位
* + {number} 当addType为false时,且保留小数点末尾2位
* @example
* Utils.formatByteToSize("812304");
* > '793.27KB'
* @example
* Utils.formatByteToSize("812304",false);
* > 793.27
**/
formatByteToSize<T extends boolean>(byteSize: number | string, addType?: T): T extends true ? string : number;
formatByteToSize(byteSize: number | string, addType = true) {
byteSize = parseInt(byteSize.toString());
if (isNaN(byteSize)) {
throw new TypeError("Utils.formatByteToSize 参数 byteSize 格式不正确");
}
let result = 0;
let resultType = "KB";
const sizeData: PopsUtilsOwnObject<number> = {};
sizeData.B = 1;
sizeData.KB = 1024;
sizeData.MB = sizeData.KB * sizeData.KB;
sizeData.GB = sizeData.MB * sizeData.KB;
sizeData.TB = sizeData.GB * sizeData.KB;
sizeData.PB = sizeData.TB * sizeData.KB;
sizeData.EB = sizeData.PB * sizeData.KB;
sizeData.ZB = sizeData.EB * sizeData.KB;
sizeData.YB = sizeData.ZB * sizeData.KB;
sizeData.BB = sizeData.YB * sizeData.KB;
sizeData.NB = sizeData.BB * sizeData.KB;
sizeData.DB = sizeData.NB * sizeData.KB;
for (const key in sizeData) {
result = byteSize / (sizeData as any)[key];
resultType = key;
if (sizeData.KB >= result) {
break;
}
}
new Date();
result = result.toFixed(2) as any;
result = addType ? result + resultType.toString() : (parseFloat(result.toString()) as any);
return result;
}
/**
* https://github.com/any86/any-touch/blob/master/README.CN.md
*/
AnyTouch = () => {
return AnyTouch;
};
/**
* `any-touch`的`doubletap`事件插件
*/
AnyTouchDoubleTapPlugin = () => {
return doubletap;
};
/**
* 通过navigator.userAgent判断是否是手机访问
* @param userAgent
*/
isPhone(userAgent = PopsCore.globalThis.navigator.userAgent): boolean {
return Boolean(/(iPhone|iPad|iPod|iOS|Android)/i.test(userAgent));
}
/**
* 自动使用 Worker 执行 setTimeout
*/
setTimeout(callback: (...args: any[]) => any, timeout: number = 0) {
return PopsCore.setTimeout(callback, timeout);
}
/**
* 配合 .setTimeout 使用
*/
clearTimeout(timeId: number | undefined) {
return PopsCore.clearTimeout(timeId);
}
/**
* 自动使用 Worker 执行 setInterval
*/
setInterval(callback: (...args: any[]) => any, timeout: number = 0) {
return PopsCore.setInterval(callback, timeout);
}
/**
* 配合 .setInterval 使用
*/
clearInterval(timeId: number | undefined) {
return PopsCore.clearInterval(timeId);
}
/**
* 覆盖对象中的数组新值
*/
setArray<T>(target: T, key: keyof T, newArr: any[]) {
if (target == null) return;
if (!Array.isArray(newArr)) return;
const arr: any = target[key];
if (Array.isArray(target[key])) {
arr.length = 0;
} else {
(<any>target)[key] = [];
}
(<any>target)[key] = newArr;
}
/**
* 获取页面的坐标中最大的z-index的元素信息
*
* 矩阵坐标计算
* @param $el 仅检测目标元素最大的z-index(自动往上层找)
* @param deviation 将对所有获取到的z-index处理偏移量(增加或减少),默认为10
* @example
* Utils.getMaxZIndexNodeInfoFromPoint(document.querySelector("a"));
* @example
* Utils.getMaxZIndexNodeInfoFromPoint(document.querySelector("a"), 20);
* @example
* Utils.getMaxZIndexNodeInfoFromPoint([document.querySelector("a"), document.querySelector("div")]);
* @example
* Utils.getMaxZIndexNodeInfoFromPoint({x: 500, y: 500});
* @example
* Utils.getMaxZIndexNodeInfoFromPoint({x: 500, y: 500}, 20);
* @example
* Utils.getMaxZIndexNodeInfoFromPoint(() => {x: 500, y: 500}, 20);
*/
getMaxZIndexNodeInfoFromPoint(
$el?: IFunction<IArray<HTMLElement> | IArray<{ x: number; y: number }>>,
deviation?: number
): {
/** 处理了偏移量和阈值比较后的z-index值 */
zIndex: number;
/** 原始z-index值 */
originZIndex: number;
/** 拥有最大z-index的元素 */
node: HTMLElement | null;
/** 目标坐标元素 */
positionNode: HTMLElement;
/** x坐标 */
positionX: number;
/** y坐标 */
positionY: number;
}[];
/**
* 获取页面的坐标中最大的z-index的元素信息
*
* 矩阵坐标计算
* @param deviation 将对所有获取到的z-index处理偏移量(增加或减少)
* @example
* Utils.getMaxZIndexNodeInfoFromPoint(20);
*/
getMaxZIndexNodeInfoFromPoint(deviation: IFunction<number>): {
/** 处理了偏移量和阈值比较后的z-index值 */
zIndex: number;
/** 原始z-index值 */
originZIndex: number;
/** 拥有最大z-index的元素 */
node: HTMLElement | null;
/** 目标坐标元素 */
positionNode: HTMLElement;
/** x坐标 */
positionX: number;
/** y坐标 */
positionY: number;
}[];
getMaxZIndexNodeInfoFromPoint(
$el?: IFunction<IArray<HTMLElement> | number | IArray<{ x: number; y: number }>>,
deviation?: number
): {
/** 处理了偏移量和阈值比较后的z-index值 */
zIndex: number;
/** 原始z-index值 */
originZIndex: number;
/** 拥有最大z-index的元素 */
node: HTMLElement | null;
/** 目标坐标元素 */
positionNode: HTMLElement;
/** x坐标 */
positionX: number;
/** y坐标 */
positionY: number;
}[] {
if (typeof $el === "function") {
$el = $el();
}
if (typeof $el === "number") {
deviation = $el;
$el = void 0;
}
if (typeof deviation !== "number" || Number.isNaN(deviation)) {
deviation = 10;
}
// 最大值 2147483647
// const maxZIndex = Math.pow(2, 31) - 1;
// 比较值 2000000000
const maxZIndexCompare = 2 * Math.pow(10, 9);
/** 坐标偏移 */
const positionDistance = 10;
const defaultCalcPostion: {
x: number;
y: number;
}[] = [];
const maxPostionX = globalThis.innerWidth;
const maxPostionY = globalThis.innerHeight;
const gridXCount = 8;
const gridYCount = 8;
for (let i = 0; i < gridXCount; i++) {
for (let j = 0; j < gridYCount; j++) {
const positionX = globalThis.innerWidth * (i / gridXCount) + positionDistance;
const positionY = globalThis.innerHeight * (j / gridYCount) + positionDistance;
const position: (typeof defaultCalcPostion)[0] = {
x: positionX,
y: positionY,
};
if (position.x > maxPostionX) {
position.x = maxPostionX;
}
if (position.y > maxPostionY) {
position.y = maxPostionY;
}
defaultCalcPostion.push(position);
}
}
const delayHandlerElementPostionList: ({ x: number; y: number } | HTMLElement)[] = defaultCalcPostion;
if ($el) {
delayHandlerElementPostionList.length = 0;
if (Array.isArray($el)) {
delayHandlerElementPostionList.push(...$el);
} else {
delayHandlerElementPostionList.push($el);
}
}
const positionInfoList = delayHandlerElementPostionList
.map((position) => {
let $position: Element | null;
let positionX: number;
let positionY: number;
if (position instanceof HTMLElement) {
$position = position;
const nodeRect = position.getBoundingClientRect();
positionX = nodeRect.x + nodeRect.width / 2;
positionY = nodeRect.y + nodeRect.height / 2;
} else {
$position = document.elementFromPoint(position.x, position.y);
positionX = position.x;
positionY = position.y;
}
const shadowRoot = $position?.shadowRoot;
if (shadowRoot) {
// 处理ShadowRoot
$position = shadowRoot.elementFromPoint(positionX, positionY);
}
if (!($position instanceof HTMLElement)) return;
let $parent: HTMLElement | null = $position;
let zIndex = 0;
let $maxZIndexNode: HTMLElement | null = null;
let rect = {
x: 0,
y: 0,
width: 0,
height: 0,
top: 0,
right: 0,
bottom: 0,
left: 0,
} as Omit<DOMRect, "toJSON">;
while ($parent) {
const nodeStyle = globalThis.getComputedStyle($parent);
const nodeZIndex = parseInt(nodeStyle.zIndex);
if (nodeStyle.position !== "static" && !isNaN(nodeZIndex)) {
if (nodeZIndex > zIndex) {
zIndex = nodeZIndex;
$maxZIndexNode = $parent;
}
}
$parent = $parent.parentElement;
}
if ($maxZIndexNode) {
const maxRect = $maxZIndexNode.getBoundingClientRect();
rect = {
x: maxRect.x,
y: maxRect.y,
width: maxRect.width,
height: maxRect.height,
top: maxRect.top,
right: maxRect.right,
bottom: maxRect.bottom,
left: maxRect.left,
};
}
const calcZIndex = zIndex + deviation;
if (calcZIndex >= maxZIndexCompare) {
// 不要超过最大值
return;
}
return {
/** 计算偏移量后的z-index值 */
zIndex: calcZIndex,
/** 获取到的最大的z-index值 */
originZIndex: zIndex,
/** 拥有最大z-index的元素 */
node: $maxZIndexNode,
/** 目标坐标元素 */
positionNode: $position,
/** 目标x坐标 */
positionX: positionX,
/** 目标y坐标 */
positionY: positionY,
/** node位置信息 */
rect,
};
})
.filter((it) => it != null);
// 降序排序
positionInfoList.sort((a, b) => {
if (a.zIndex < b.zIndex) {
return 1;
} else if (a.zIndex > b.zIndex) {
return -1;
} else {
return 0;
}
});
return positionInfoList;
}
}
const popsUtils = new PopsUtils();
export { popsUtils };