@whitesev/pops
Version:
弹窗库,包含了alert、confirm、prompt、drawer、folder、loading、iframe、panel、tooltip、searchSuggestion、rightClickMenu组件
641 lines (637 loc) • 20.4 kB
text/typescript
import type { PopsAlertConfig } from "../components/alert/types";
import type { PopsConfirmConfig } from "../components/confirm/types";
import type { PopsDrawerConfig } from "../components/drawer/types";
import type { PopsFolderConfig } from "../components/folder/types";
import type { PopsIframeConfig } from "../components/iframe/types";
import type { PopsLoadingConfig } from "../components/loading/types";
import type { PopsPanelConfig } from "../components/panel/types";
import type { PopsPromptConfig } from "../components/prompt/types/index";
import type { EventEmiter } from "../event/EventEmiter";
import { PopsAnimation } from "../PopsAnimation";
import { PopsCore } from "../PopsCore";
import { PopsInstData } from "../PopsInst";
import type { PopsGeneralConfig } from "../types/components";
import type { PopsEventConfig, PopsHandlerEventConfig } from "../types/event";
import type { CustomEventMap } from "../types/EventEmitter";
import type { PopsInstGeneralConfig } from "../types/inst";
import type { PopsInstStoreType, PopsSupportAnimConfigType, PopsSupportOnlyConfig, PopsType } from "../types/main";
import { popsDOMUtils } from "../utils/PopsDOMUtils";
import { PopsInstanceUtils } from "../utils/PopsInstanceUtils";
import { popsUtils } from "../utils/PopsUtils";
import { PopsInstHandler } from "./PopsInstHandler";
export const PopsHandler = {
/**
* 创建shadow
*/
handlerShadow(config: Pick<PopsGeneralConfig, "useShadowRoot" | "stopKeyDownEventPropagation">) {
const $shadowContainer = popsDOMUtils.createElement("div", {
className: "pops-shadow-container",
});
let $shadowRoot: ShadowRoot | HTMLElement;
if (config.useShadowRoot) {
$shadowRoot = $shadowContainer.attachShadow({ mode: "open" });
} else {
$shadowRoot = $shadowContainer;
}
// 添加键盘监听
// rightClickMenu
// searchSuggestion
// tooltip
// 以上都不需要添加该事件监听
if (config.stopKeyDownEventPropagation) {
popsDOMUtils.on(
$shadowRoot,
"keydown",
[
'input[type="text"]',
'input[type="password"]',
'input[type="number"]',
'input[type="email"]',
'input[type="url"]',
'input[type="search"]',
"input:not([type])",
"textarea",
],
(evt) => {
evt.stopImmediatePropagation();
evt.stopPropagation();
},
{ capture: true, overrideTarget: false }
);
}
return {
$shadowContainer,
$shadowRoot,
};
},
/**
* 处理初始化
* @param $styleParent style元素的父元素
* @param css 添加进ShadowRoot的CSS
*/
handleInit(
$styleParent?: ShadowRoot | HTMLElement,
css?:
| string
| string[]
| {
name?: string;
css: string;
}[]
) {
PopsAnimation.init();
if (!arguments.length) {
return;
}
if ($styleParent == null) {
return;
}
if (css == null) {
return;
}
if (typeof css === "string") {
if (css.trim() === "") {
return;
}
css = [
{
css: css,
},
];
} else {
css = css.map((item) => {
if (typeof item === "string") {
return {
css: item,
};
} else {
return item;
}
});
}
for (const cssItem of css) {
const $css = popsDOMUtils.createElement(
"style",
{},
{
"data-type": "PopsHandler.handleInit",
}
);
$css.textContent = cssItem.css;
if (typeof cssItem.name === "string") {
$css.setAttribute("data-name", cssItem.name);
}
$styleParent.appendChild($css);
}
},
/**
* 处理遮罩层
*
* + 设置遮罩层的点击事件
* @param config 传递的配置
*/
handleMask(
config = {} as {
type: "alert" | "confirm" | "prompt" | "loading" | "iframe" | "drawer" | "folder" | "panel";
guid: string;
config:
| Required<PopsAlertConfig>
| Required<PopsLoadingConfig>
| Required<PopsIframeConfig>
| Required<PopsDrawerConfig>
| Required<PopsPanelConfig>
| Required<PopsFolderConfig>;
animElement: HTMLElement;
maskHTML: string;
}
) {
const result = {
maskElement: popsDOMUtils.parseTextToDOM<HTMLDivElement>(config.maskHTML),
};
let isMaskClick = false;
/**
* 点击其它区域的事件
* @param event
*/
const clickEvent = (event: MouseEvent | PointerEvent) => {
popsDOMUtils.preventEvent(event);
// 获取该类型实例存储列表
const targetInst = PopsInstData[config.type];
const continueExec = () => {
if (config.config.mask!.clickEvent!.toClose) {
// 关闭
return PopsInstHandler.close(config.config, config.type, targetInst, config.guid, config.animElement);
} else if (config.config.mask!.clickEvent!.toHide) {
// 隐藏
return PopsInstHandler.hide(
config.config,
config.type,
targetInst,
config.guid,
config.animElement,
result.maskElement
);
}
};
if (typeof config.config.mask.clickCallBack === "function") {
config.config.mask.clickCallBack(continueExec, config.config);
} else {
continueExec();
}
return false;
};
// 判断是否启用了遮罩层点击动作
if (config.config.mask.clickEvent!.toClose || config.config.mask.clickEvent!.toHide) {
// 判断按下的元素是否是pops-anim
popsDOMUtils.on(config.animElement, "pointerup", (event) => {
const $click = event.composedPath()[0] as HTMLElement;
isMaskClick = PopsInstanceUtils.isAnimNode($click);
});
// 如果有动画层,在动画层上监听点击事件
popsDOMUtils.on<MouseEvent | PointerEvent>(config.animElement, "click", (event) => {
const $click = event.composedPath()[0] as HTMLElement;
if (isMaskClick && PopsInstanceUtils.isAnimNode($click)) {
return clickEvent(event);
}
});
// 在遮罩层监听点击事件
// 如果有动画层,那么该点击事件触发不了
popsDOMUtils.on<MouseEvent | PointerEvent>(result.maskElement, "click", (event) => {
isMaskClick = true;
clickEvent(event);
});
}
return result;
},
/**
* 处理获取元素
* @param animElement
* @param type
*/
handleQueryElement(animElement: HTMLDivElement, type: PopsSupportAnimConfigType) {
return {
/**
* 主元素
*/
$pops: animElement.querySelector<HTMLDivElement>(".pops[type-value")!,
/**
* 确认按钮
*/
$btnOk: animElement.querySelector<HTMLDivElement>(`.pops-${type}-btn-ok`)!,
/**
* 取消按钮
*/
$btnCancel: animElement.querySelector<HTMLDivElement>(`.pops-${type}-btn-cancel`)!,
/**
* 其它按钮
*/
$btnOther: animElement.querySelector<HTMLDivElement>(`.pops-${type}-btn-other`)!,
/**
* 标题元素
*/
$title: animElement.querySelector<HTMLDivElement>(`.pops-${type}-title`)!,
/**
* 输入框元素
*/
$input: animElement.querySelector<HTMLTextAreaElement>(`.pops-${type}-content textarea[pops]`)
? animElement.querySelector<HTMLTextAreaElement>(`.pops-${type}-content textarea[pops]`)!
: animElement.querySelector<HTMLInputElement>(`.pops-${type}-content input[pops]`)!,
/**
* 顶部按钮控制层元素
*/
$headerControls: animElement.querySelector<HTMLDivElement>(".pops-header-controls")!,
/**
* iframe元素
*/
$iframe: animElement.querySelector<HTMLIFrameElement>("iframe[pops]")!,
/**
* 加载中元素
*/
$loading: animElement.querySelector<HTMLDivElement>(".pops-loading")!,
/**
* 内容元素
*/
$content: animElement.querySelector<HTMLDivElement>(`.pops-${type}-content`)!,
/**
* panel的右侧容器元素
*/
$panelRightSectionWrapper: animElement.querySelector<HTMLDivElement>(`.pops-${type}-section-wrapper`)!,
/**
* panel侧边栏容器元素
*/
$panelLeftAside: animElement.querySelector<HTMLDivElement>(`.pops-${type}-content aside.pops-${type}-aside`)!,
/**
* panel主要区域容器元素
*/
$panelContentSectionContainer: animElement.querySelector<HTMLDivElement>(
`.pops-${type}-content section.pops-${type}-container`
)!,
/**
* panel底部区域
*/
$panelBottomWrapper: animElement.querySelector<HTMLElement>(`.pops-${type}-bottom-wrapper`)!,
/**
* panel底部区域容器
*/
$panelBottomContainer: animElement.querySelector<HTMLElement>(`.pops-${type}-bottom-container`)!,
/**
* panel底部区域左侧容器
*/
$panelBottomLeftContainer: animElement.querySelector<HTMLElement>(`.pops-${type}-bottom-left-container`)!,
/**
* panel底部区域右侧容器
*/
$panelBottomRightContainer: animElement.querySelector<HTMLElement>(`.pops-${type}-bottom-right-container`)!,
/**
* 内容加载中元素
*/
$contentLoading: animElement.querySelector<HTMLDivElement>(`.pops-${type}-content-global-loading`)!,
/**
* 顶部缩小按钮
*/
$headerBtnMin: animElement.querySelector<HTMLDivElement>(".pops-header-control[data-type='min']")!,
/**
* 顶部放大按钮
*/
$headerBtnMax: animElement.querySelector<HTMLDivElement>(".pops-header-control[data-type='max']")!,
/**
* 顶部恢复原样按钮
*/
$headerBtnMise: animElement.querySelector<HTMLDivElement>(".pops-header-control[data-type='mise']")!,
/**
* 顶部关闭按钮
*/
$headerBtnClose: animElement.querySelector<HTMLDivElement>(".pops-header-control[data-type='close']")!,
/**
* 文件夹列表元素
*/
$folderList: animElement.querySelector<HTMLDivElement>(".pops-folder-list")!,
/**
* 文件夹列表顶部元素
*/
$folderHeaderNav: animElement.querySelector<HTMLDivElement>(
".pops-folder-list .pops-folder-list-table__header-div"
)!,
/**
* 文件夹列表行元素
*/
$folderHeaderRow: animElement.querySelector<HTMLTableRowElement>(
".pops-folder-list .pops-folder-list-table__header-div .pops-folder-list-table__header-row"
)!,
/**
* 文件夹列表tbody元素
*/
$folderTbody: animElement.querySelector<HTMLTableElement>(
".pops-folder-list .pops-folder-list-table__body-div tbody"
)!,
/**
* 文件夹列表primary元素
*/
$folderHeaderBreadcrumbPrimary: animElement.querySelector<HTMLDivElement>(
".pops-folder-list .pops-folder-file-list-breadcrumb-primary"
)!,
/**
* 文件夹排序按钮-文件名
*/
$folderSortFileName: animElement.querySelector<HTMLDivElement>(
'.pops-folder-list-table__sort[data-sort="fileName"]'
)!,
/**
* 文件夹排序按钮-修改时间
*/
$folderSortLatestTime: animElement.querySelector<HTMLDivElement>(
'.pops-folder-list-table__sort[data-sort="latestTime"]'
)!,
/**
* 文件夹排序按钮-文件大小
*/
$folderSortFileSize: animElement.querySelector<HTMLDivElement>(
'.pops-folder-list-table__sort[data-sort="fileSize"]'
)!,
};
},
/**
* 获取事件配置
* @param guid
* @param $shadowContainer
* @param $shadowRoot
* @param type 当前弹窗类型
* @param $anim 动画层
* @param $pops 主元素
* @param $mask 遮罩层
* @param config 当前配置
*/
handleEventConfig<E extends EventEmiter<CustomEventMap> = EventEmiter<CustomEventMap>>(
config:
| PopsAlertConfig
| PopsDrawerConfig
| PopsPromptConfig
| PopsConfirmConfig
| PopsIframeConfig
| PopsLoadingConfig
| PopsPanelConfig
| PopsFolderConfig,
guid: string,
$shadowContainer: HTMLDivElement,
$shadowRoot: ShadowRoot | HTMLElement,
type: PopsInstStoreType,
$anim: HTMLDivElement,
$pops: HTMLDivElement,
emitter: E,
$mask?: HTMLDivElement
): PopsEventConfig<E> {
return {
$shadowContainer: $shadowContainer,
$shadowRoot: $shadowRoot,
$el: $anim,
$anim: $anim,
$pops: $pops,
$mask: $mask,
mode: type,
guid: guid,
emitter: emitter,
close() {
return PopsInstHandler.close(config, type, PopsInstData[type], guid, $anim);
},
hide() {
return PopsInstHandler.hide(config, type, PopsInstData[type], guid, $anim, $mask);
},
show($parent?: HTMLElement | Document | ShadowRoot) {
if ($parent) {
$parent.appendChild(PopsInstData[type][0].$shadowRoot);
}
return PopsInstHandler.show(config, type, PopsInstData[type], guid, $anim, $mask);
},
};
},
/**
* 获取loading的事件配置
* @param guid
* @param type 当前弹窗类型
* @param $anim 动画层
* @param $pops 主元素
* @param $mask 遮罩层
* @param config 当前配置
*/
handleLoadingEventConfig<E extends EventEmiter<CustomEventMap> = EventEmiter<CustomEventMap>>(
config:
| PopsAlertConfig
| PopsDrawerConfig
| PopsPromptConfig
| PopsConfirmConfig
| PopsIframeConfig
| PopsLoadingConfig
| PopsPanelConfig
| PopsFolderConfig,
guid: string,
type: "loading",
$anim: HTMLDivElement,
$pops: HTMLDivElement,
emitter: E,
$mask?: HTMLDivElement
): Omit<PopsEventConfig<E>, "$shadowContainer" | "$shadowRoot"> {
return {
$el: $anim,
$anim: $anim,
$pops: $pops,
$mask: $mask,
mode: type,
guid: guid,
emitter,
close() {
return PopsInstHandler.close(config, type, PopsInstData[type], guid, $anim);
},
hide() {
return PopsInstHandler.hide(config, type, PopsInstData[type], guid, $anim, $mask);
},
show() {
return PopsInstHandler.show(config, type, PopsInstData[type], guid, $anim, $mask);
},
};
},
/**
* 处理返回的配置,针对popsHandler.handleEventConfig
* @param config 配置
*/
handleResultConfig<T>(config: T): Omit<T, "type" | "function"> {
const resultDetails = Object.assign({}, config);
popsUtils.delete(resultDetails, "type");
popsUtils.delete(resultDetails, "function");
return resultDetails;
},
/**
* 处理点击事件
* @param type 当前按钮类型
* @param $btn 按钮元素
* @param eventConfig 事件配置,由popsHandler.handleEventConfig创建的
* @param callback 点击回调
*/
handleClickEvent<E extends EventEmiter<CustomEventMap> = EventEmiter<CustomEventMap>>(
type: PopsHandlerEventConfig<E>["type"],
$btn: HTMLElement,
eventConfig: PopsEventConfig<E>,
callback?: (details: PopsHandlerEventConfig<E>, event: PointerEvent | MouseEvent) => void
) {
if (typeof callback !== "function") return;
return popsDOMUtils.on<PointerEvent | MouseEvent>(
$btn,
"click",
(event) => {
const extraParam = {
type: type,
};
callback(Object.assign(eventConfig, extraParam), event);
},
{
capture: true,
}
);
},
/**
* 全局监听键盘事件
* @param keyName 键名|键值
* @param otherKeyList 组合按键,数组类型,包含ctrl、shift、alt和meta(win键或mac的cmd键)
* @param callback 回调函数
*/
handleKeyboardEvent(keyName: string | number, otherKeyList: string[] = [], callback: (event: KeyboardEvent) => void) {
const keyboardEvent = function (event: KeyboardEvent) {
const _keyName = event.code || event.key;
const _keyValue = event.charCode || event.keyCode || event.which;
if (otherKeyList.includes("ctrl") && !event.ctrlKey) {
return;
}
if (otherKeyList.includes("alt") && !event.altKey) {
return;
}
if (otherKeyList.includes("meta") && !event.metaKey) {
return;
}
if (otherKeyList.includes("shift") && !event.shiftKey) {
return;
}
if (typeof keyName === "string" && keyName === _keyName) {
callback && callback(event);
} else if (typeof keyName === "number" && keyName === _keyValue) {
callback && callback(event);
}
};
const listener = popsDOMUtils.on(PopsCore.globalThis, "keydown", keyboardEvent, {
capture: true,
});
return listener;
},
/**
* 处理prompt的点击事件
* @param type 触发事件类型
* @param inputElement 输入框
* @param $btn 按钮元素
* @param eventConfig 事件配置,由popsHandler.handleEventConfig创建的
* @param callback 点击回调
*/
handlePromptClickEvent<E extends EventEmiter<CustomEventMap> = EventEmiter<CustomEventMap>>(
type: PopsHandlerEventConfig<E>["type"],
inputElement: HTMLInputElement | HTMLTextAreaElement,
$btn: HTMLElement,
eventConfig: PopsEventConfig<E>,
callback: (
details: PopsEventConfig<E> & {
type: any;
text: string;
},
event: MouseEvent | PointerEvent
) => void
) {
popsDOMUtils.on<MouseEvent | PointerEvent>(
$btn,
"click",
(event) => {
// 额外参数
const extraParam = {
type: type,
text: inputElement.value,
};
callback(Object.assign(eventConfig, extraParam), event);
},
{
capture: true,
}
);
},
/**
* 获取数值
* @param target
*/
getTargerOrFunctionValue<T>(target: T | (() => T)): T {
if (typeof target === "function") {
const result = (target as () => T)();
return result;
} else {
return target;
}
},
/**
* 处理config.only
* @param type 当前弹窗类型
* @param config 配置
*/
handleOnly<T extends Required<PopsSupportOnlyConfig[keyof PopsSupportOnlyConfig]>>(type: PopsType, config: T): T {
if (config.only) {
// .loading
// .rightClickMenu
// .tooltip
// 单独处理
if (type === "loading" || type === "tooltip" || type === "rightClickMenu") {
const inst = PopsInstData[type as keyof typeof PopsInstData];
if (inst) {
PopsInstHandler.removeInstance([inst], "", true);
}
} else {
PopsInstHandler.removeInstance(
[
PopsInstData.alert,
PopsInstData.confirm,
PopsInstData.drawer,
PopsInstData.folder,
PopsInstData.iframe,
PopsInstData.panel,
PopsInstData.prompt,
],
"",
true
);
}
}
if (type !== "rightClickMenu") {
// rightClickMenu在配置处理时就处理了一次,这里不需要重复处理
config = this.handleZIndex(config);
}
return config;
},
/**
* 处理z-index
* @param config 配置
*/
handleZIndex<T extends Required<PopsSupportOnlyConfig[keyof PopsSupportOnlyConfig]>>(config: T): T {
// 对配置进行处理
// 选择配置的z-index和已有的pops实例的最大z-index值
const originZIndex = config.zIndex;
const handler = () => {
let deviation = 100;
deviation += PopsHandler.getTargerOrFunctionValue(originZIndex) ?? 0;
let maxZIndex = deviation;
const pointZIndex = popsUtils.getMaxZIndexNodeInfoFromPoint(deviation)[0]?.zIndex ?? 0;
maxZIndex = Math.max(maxZIndex, pointZIndex);
return maxZIndex === deviation ? maxZIndex : maxZIndex + deviation;
};
config.zIndex = handler;
return config;
},
/**
* 处理把已创建的元素保存到内部环境中
* @param type 当前弹窗类型
* @param value
*/
handlePush(type: PopsInstStoreType, value: PopsInstGeneralConfig) {
PopsInstData[type].push(value);
},
};