@whitesev/pops
Version:
弹窗库,包含了alert、confirm、prompt、drawer、folder、loading、iframe、panel、tooltip、searchSuggestion、rightClickMenu组件
1,458 lines (1,450 loc) • 152 kB
text/typescript
import { PopsCommonCSSClassName } from "../../config/CommonCSSClassName";
import type { EventEmiter } from "../../event/EventEmiter";
import { PopsAnimation } from "../../PopsAnimation";
import { PopsCSS } from "../../PopsCSS";
import { PopsIcon } from "../../PopsIcon";
import { popsDOMUtils } from "../../utils/PopsDOMUtils";
import { PopsMathFloatUtils } from "../../utils/PopsMathUtils";
import { PopsSafeUtils } from "../../utils/PopsSafeUtils";
import { popsUtils } from "../../utils/PopsUtils";
import { PopsAlert } from "../alert";
import type { PopsAlertConfig } from "../alert/types";
import { PopsTooltip } from "../tooltip";
import type {
PopsPanelBottomContentConfig,
PopsPanelConfig,
PopsPanelContentConfig,
PopsPanelEventType,
PopsPanelViewConfig,
} from "./types";
import type { PopsPanelButtonConfig } from "./types/components-button";
import type { PopsPanelGeneralConfig, PopsPanelRightAsideContainerConfig } from "./types/components-common";
import type { PopsPanelContainerConfig } from "./types/components-container";
import type { PopsPanelDeepViewConfig } from "./types/components-deepMenu";
import type { PopsPanelInputConfig, PopsPanelInputType } from "./types/components-input";
import type { PopsPanelOwnConfig } from "./types/components-own";
import type { PopsPanelSelectConfig, PopsPanelSelectDataOption } from "./types/components-select";
import type {
PopsPanelSelectMultipleConfig,
PopsPanelSelectMultipleDataOption,
} from "./types/components-selectMultiple";
import type { PopsPanelSliderConfig } from "./types/components-slider";
import type { PopsPanelSwitchConfig } from "./types/components-switch";
import type { PopsPanelTextAreaConfig } from "./types/components-textarea";
/**
* 处理组件(把组件配置转为组件元素)
*/
export const PanelHandlerComponents = () => {
return {
/**
* 左侧上方的的ul容器
*/
asideULElement: null as any as HTMLUListElement,
/**
* 左侧下方的ul容器
*/
asideBottomULElement: null as any as HTMLUListElement,
/**
* 右侧主内容的顶部文字ul容器
*/
sectionContainerHeaderULElement: null as any as HTMLUListElement,
/**
* 右侧主内容的ul容器
*/
sectionContainerULElement: null as any as HTMLUListElement,
/**
* 元素
*/
$el: {
/** pops主元素 */
$pops: null as any as HTMLElement,
/** 内容 */
$content: null as any as HTMLElement,
/** section元素的包裹容器 */
$panelRightSectionWrapper: null as any as HTMLElement,
/** 左侧容器 */
$panelLeftAside: null as any as HTMLElement,
/** 右侧容器 */
$panelContentSectionContainer: null as any as HTMLElement,
$panelBottomWrapper: null as any as HTMLElement,
$panelBottomContainer: null as any as HTMLElement,
$panelBottomLeftContainer: null as any as HTMLElement,
$panelBottomRightContainer: null as any as HTMLElement,
},
$data: {
nodeStoreConfigKey: "data-view-config",
},
$config: {} as Required<PopsPanelConfig>,
emitter: null as any as EventEmiter<PopsPanelEventType>,
/**
* 初始化
* @param data
*/
init(data: {
config: Required<PopsPanelConfig>;
$el: {
$pops: HTMLElement;
$content: HTMLElement;
$panelRightSectionWrapper: HTMLElement;
$panelLeftAside: HTMLElement;
$panelContentSectionContainer: HTMLElement;
$panelBottomWrapper: HTMLElement;
$panelBottomContainer: HTMLElement;
$panelBottomLeftContainer: HTMLElement;
$panelBottomRightContainer: HTMLElement;
};
emitter: EventEmiter<PopsPanelEventType>;
}) {
const PopsType = "panel";
this.$el = {
...data.$el,
};
this.$config = data.config;
this.emitter = data.emitter;
this.asideULElement = this.$el.$panelLeftAside.querySelector<HTMLUListElement>(
`ul.pops-${PopsType}-aside-top-container`
)!;
this.asideBottomULElement = this.$el.$panelLeftAside.querySelector<HTMLUListElement>(
`ul.pops-${PopsType}-aside-bottom-container`
)!;
this.sectionContainerHeaderULElement = this.$el.$panelContentSectionContainer.querySelector<HTMLUListElement>(
`ul.pops-${PopsType}-container-header-ul`
)!;
this.sectionContainerULElement = this.$el.$panelContentSectionContainer.querySelector<HTMLUListElement>(
`ul.pops-${PopsType}-container-main-ul`
)!;
/**
* 默认点击的左侧容器项
*/
let $defaultAsideItem: HTMLLIElement | null = null;
/** 是否滚动到默认位置(第一个项) */
let isScrollToDefaultView = false;
// 初始化内容配置
data.config.content.forEach((asideItemConfig) => {
const $asideLiElement = this.createAsideItem(asideItemConfig);
this.onAsideItemClick($asideLiElement, asideItemConfig);
// 是否处于底部
const isBottom =
typeof asideItemConfig.isBottom === "function" ? asideItemConfig.isBottom() : asideItemConfig.isBottom;
if (isBottom) {
this.asideBottomULElement.appendChild($asideLiElement);
} else {
this.asideULElement.appendChild($asideLiElement);
}
if ($defaultAsideItem == null) {
let flag = false;
if (typeof asideItemConfig.isDefault === "function") {
flag = Boolean(asideItemConfig.isDefault());
} else {
flag = Boolean(asideItemConfig.isDefault);
}
if (flag) {
$defaultAsideItem = $asideLiElement;
isScrollToDefaultView = Boolean(asideItemConfig.scrollToDefaultView);
}
}
if (typeof asideItemConfig.afterRender === "function") {
// 执行渲染完毕的回调
asideItemConfig.afterRender({
asideConfig: asideItemConfig,
$asideLiElement: $asideLiElement,
});
}
});
// 初始化底部内容配置
(data.config?.bottomContentConfig || []).forEach((bottomItemConfig) => {
const $bottomLiElement = this.createBottomItem(bottomItemConfig);
this.onBottomItemClick($bottomLiElement, bottomItemConfig);
if (bottomItemConfig.position === "left" || bottomItemConfig.position == null) {
this.$el.$panelBottomLeftContainer.appendChild($bottomLiElement);
} else if (bottomItemConfig.position === "right") {
this.$el.$panelBottomRightContainer.appendChild($bottomLiElement);
} else {
throw new Error("pops.panel:bottomContentConfig.position参数错误");
}
if (typeof bottomItemConfig.afterRender === "function") {
// 执行渲染完毕的回调
bottomItemConfig.afterRender({
$bottomWrapper: this.$el.$panelBottomWrapper,
$bottomContainer: this.$el.$panelBottomContainer,
$bottomLeftContainer: this.$el.$panelBottomLeftContainer,
$bottomRightContainer: this.$el.$panelBottomRightContainer,
});
}
});
// 点击左侧列表
if ($defaultAsideItem == null && this.asideULElement.children.length) {
// 默认第一个
$defaultAsideItem = <HTMLLIElement>this.asideULElement.children[0];
}
if ($defaultAsideItem) {
// 点击选择的那一项
$defaultAsideItem.click();
if (isScrollToDefaultView) {
$defaultAsideItem?.scrollIntoView();
}
} else {
console.error("pops.panel:左侧容器没有项");
}
},
/**
* 清空container容器的元素
*/
clearContainer() {
Reflect.deleteProperty(this.$el.$panelContentSectionContainer, this.$data.nodeStoreConfigKey);
PopsSafeUtils.setSafeHTML(this.sectionContainerHeaderULElement, "");
PopsSafeUtils.setSafeHTML(this.sectionContainerULElement, "");
this.clearDeepMenuContainer();
},
/**
* 清空deepMenu的容器元素
*/
clearDeepMenuContainer() {
this.$el.$panelRightSectionWrapper
?.querySelectorAll("section.pops-panel-deepMenu-container")
.forEach(($el) => popsDOMUtils.remove($el));
},
/**
* 清空左侧容器已访问记录
*/
clearAsideItemIsVisited() {
this.$el.$panelLeftAside.querySelectorAll<HTMLDivElement>(".pops-is-visited").forEach(($el) => {
popsDOMUtils.removeClassName($el, "pops-is-visited");
});
},
/**
* 设置左侧容器已访问记录
* @param $el
*/
setAsideItemIsVisited($el: HTMLElement) {
popsDOMUtils.addClassName($el, "pops-is-visited");
},
/**
* 为元素添加自定义属性
* @param $el 元素
* @param attributes 属性
*/
setElementAttributes($el: HTMLElement, attributes?: any) {
if (attributes == null) {
return;
}
if (Array.isArray(attributes)) {
attributes.forEach((attrObject) => {
this.setElementAttributes($el, attrObject);
});
} else {
Object.keys(attributes).forEach((attributeName) => {
$el.setAttribute(attributeName, attributes[attributeName]);
});
}
},
/**
* 为元素设置(自定义)属性
* @param $el 元素
* @param props 属性
*/
setElementProps($el: HTMLElement, props?: any) {
if (props == null) return;
if (typeof props !== "object") return;
const propsKeys = Object.keys(props);
propsKeys.forEach((propName) => {
const value = props[propName];
if (propName === "innerHTML") {
PopsSafeUtils.setSafeHTML($el, value);
return;
}
Reflect.set($el, propName, value);
});
},
/**
* 为元素设置classname
* @param $el 元素
* @param className
*/
setElementClassName($el: HTMLElement, className?: PopsPanelGeneralConfig<any>["className"]) {
popsDOMUtils.addClassName($el, className);
},
/**
* 创建底部项元素<li>
* @param bottomItemConfig 配置
*/
createBottomItem(bottomItemConfig: PopsPanelBottomContentConfig) {
// 显示的文本
const text = typeof bottomItemConfig.text === "function" ? bottomItemConfig.text() : bottomItemConfig.text;
const className = Array.isArray(bottomItemConfig.className)
? bottomItemConfig.className.join(" ")
: bottomItemConfig.className || "";
const $li = popsDOMUtils.createElement("li", {
className: ["pops-panel-bottom-item", "pops-user-select-none", className].join(" "),
innerHTML: text,
});
// 处理attr
this.setElementAttributes($li, bottomItemConfig.attributes);
// 处理props
this.setElementProps($li, bottomItemConfig.props);
/** 禁用左侧项的hover的CSS样式的类名 */
const disablHoverCSSClassName = "pops-panel-disable-bottom-item-hover-css";
/** 是否禁用左侧项的hover的CSS样式 */
const isDisableHoverCSS =
typeof bottomItemConfig.disableHoverCSS === "function"
? bottomItemConfig.disableHoverCSS()
: bottomItemConfig.disableHoverCSS;
if (isDisableHoverCSS) {
popsDOMUtils.addClassName($li, disablHoverCSSClassName);
} else {
popsDOMUtils.removeClassName($li, disablHoverCSSClassName);
}
return $li;
},
/**
* 为底部元素添加点击事件
* @param $bottomItem 底部<li>元素
* @param bottomItemConfig 配置
*/
onBottomItemClick($bottomItem: HTMLElement, bottomItemConfig: PopsPanelBottomContentConfig) {
popsDOMUtils.on<MouseEvent | PointerEvent>($bottomItem, "click", async (event) => {
if (typeof bottomItemConfig.clickCallback === "function") {
// 执行回调
const asideClickCallbackResult = await bottomItemConfig.clickCallback(event);
if (typeof asideClickCallbackResult === "boolean" && !asideClickCallbackResult) {
return;
}
}
});
},
/**
* 创建左侧容器元素<li>
* @param asideConfig 配置
*/
createAsideItem(asideConfig: PopsPanelContentConfig) {
// 显示的文本
const text = typeof asideConfig.title === "function" ? asideConfig.title() : asideConfig.title;
const $li = popsDOMUtils.createElement("li", {
id: asideConfig.id,
innerHTML: text,
});
Reflect.set($li, "__forms__", asideConfig.views);
// 处理className
this.setElementClassName($li, "pops-panel-aside-item");
this.setElementClassName($li, asideConfig.className);
// 处理attr
this.setElementAttributes($li, asideConfig.attributes);
// 处理props
this.setElementProps($li, asideConfig.props);
/** 禁用左侧项的hover的CSS样式的类名 */
const disablHoverCSSClassName = "pops-panel-disabled-aside-hover-css";
/** 是否禁用左侧项的hover的CSS样式 */
const isDisableItemHoverCSS =
typeof asideConfig.disableAsideItemHoverCSS === "function"
? asideConfig.disableAsideItemHoverCSS()
: asideConfig.disableAsideItemHoverCSS;
if (isDisableItemHoverCSS) {
popsDOMUtils.addClassName($li, disablHoverCSSClassName);
} else {
popsDOMUtils.removeClassName($li, disablHoverCSSClassName);
}
return $li;
},
/**
* type ==> switch
* 创建中间容器的元素<li>
* @param viewConfig
*/
createSectionContainerItem_switch(viewConfig: PopsPanelSwitchConfig) {
const $li = popsDOMUtils.createElement("li");
Reflect.set($li, this.$data.nodeStoreConfigKey, viewConfig);
this.setElementClassName($li, viewConfig.className);
this.setElementAttributes($li, viewConfig.attributes);
this.setElementProps($li, viewConfig.props);
// 左边底部的描述的文字
let leftDescriptionText = "";
if (viewConfig.description) {
leftDescriptionText = /*html*/ `<p class="pops-panel-item-left-desc-text">${viewConfig.description}</p>`;
}
PopsSafeUtils.setSafeHTML(
$li,
/*html*/ `
<div class="pops-panel-item-left-text">
<p class="pops-panel-item-left-main-text">${viewConfig.text}</p>${leftDescriptionText}</div>
<div class="pops-panel-switch">
<input class="pops-panel-switch__input" type="checkbox">
<span class="pops-panel-switch__core">
<div class="pops-panel-switch__action">
</div>
</span>
</div>`
);
const PopsPanelSwitch = {
[Symbol.toStringTag]: "PopsPanelSwitch",
$data: {
value: Boolean(viewConfig.getValue()),
},
$ele: {
itemLeftTextContainer: $li.querySelector<HTMLElement>(".pops-panel-item-left-text"),
switch: $li.querySelector<HTMLDivElement>(".pops-panel-switch")!,
input: $li.querySelector<HTMLInputElement>(".pops-panel-switch__input")!,
core: $li.querySelector<HTMLSpanElement>(".pops-panel-switch__core")!,
},
init() {
this.setStatus(this.$data.value);
const disabled = typeof viewConfig.disabled === "function" ? viewConfig.disabled() : viewConfig.disabled;
if (disabled) {
this.disable();
}
this.onClick();
},
/**
* 设置点击事件
*/
onClick() {
popsDOMUtils.on(this.$ele.core, "click", async (event) => {
if (this.$ele.input.disabled || this.$ele.switch.hasAttribute("data-disabled")) {
return;
}
const status = this.getStatus();
if (typeof viewConfig.beforeSwitchStatusChangeCallBack === "function") {
const flag = await viewConfig.beforeSwitchStatusChangeCallBack(event, status);
if (typeof flag === "boolean" && !flag) {
return;
}
}
// 设置为逆反状态
this.$data.value = !status;
this.setStatus(this.$data.value);
if (typeof viewConfig.callback === "function") {
await viewConfig.callback(event, this.$data.value);
}
});
},
/**
* 设置状态
*/
setStatus(isChecked = false) {
isChecked = Boolean(isChecked);
this.$ele.input.checked = isChecked;
if (isChecked) {
popsDOMUtils.addClassName(this.$ele.switch, "pops-panel-switch-is-checked");
} else {
popsDOMUtils.removeClassName(this.$ele.switch, "pops-panel-switch-is-checked");
}
},
/**
* 获取开/关的逆反状态
*/
getReverseStatus() {
return !this.getStatus();
},
/**
* 获取开/关的状态
*/
getStatus() {
let checkedValue = false;
if (popsDOMUtils.containsClassName(this.$ele.switch, "pops-panel-switch-is-checked")) {
checkedValue = true;
}
return checkedValue;
},
/**
* 禁用复选框
*/
disable() {
this.$ele.input.disabled = true;
this.$ele.switch.setAttribute("data-disabled", "true");
popsDOMUtils.addClassName(this.$ele.itemLeftTextContainer, PopsCommonCSSClassName.textIsDisabled);
},
/**
* 取消禁用复选框
*/
notDisable() {
this.$ele.input.disabled = false;
this.$ele.switch.removeAttribute("data-disabled");
popsDOMUtils.removeClassName(this.$ele.itemLeftTextContainer, PopsCommonCSSClassName.textIsDisabled);
},
};
PopsPanelSwitch.init();
Reflect.set($li, "data-switch", PopsPanelSwitch);
return {
$el: $li,
handler: PopsPanelSwitch,
};
},
/**
* type ==> slider
* 获取中间容器的元素<li>
* @param viewConfig
*/
createSectionContainerItem_slider(viewConfig: PopsPanelSliderConfig) {
const $li = popsDOMUtils.createElement("li");
Reflect.set($li, this.$data.nodeStoreConfigKey, viewConfig);
this.setElementClassName($li, viewConfig.className);
this.setElementAttributes($li, viewConfig.attributes);
this.setElementProps($li, viewConfig.props);
// 左边底部的描述的文字
let leftDescriptionText = "";
if (viewConfig.description) {
leftDescriptionText = /*html*/ `<p class="pops-panel-item-left-desc-text">${viewConfig.description}</p>`;
}
PopsSafeUtils.setSafeHTML(
$li,
/*html*/ `
<div class="pops-panel-item-left-text" style="flex: 1;">
<p class="pops-panel-item-left-main-text">${viewConfig.text}</p>${leftDescriptionText}</div>
<div class="pops-slider pops-slider-width">
<div class="pops-slider__runway">
<div class="pops-slider__bar" style="width: 0%; left: 0%"></div>
<div class="pops-slider__button-wrapper" style="left: 0%">
<div class="pops-slider__button"></div>
</div>
</div>
</div>`
);
const PopsPanelSlider = {
[Symbol.toStringTag]: "PopsPanelSlider",
/**
* 值
*/
value: viewConfig.getValue(),
/**
* 最小值
*/
min: viewConfig.min,
/**
* 最大值
*/
max: viewConfig.max,
/**
* 间隔
*/
step: viewConfig.step || 1,
$data: {
/**
* 是否正在移动
*/
isMove: false,
/**
* 是否已初始化已拖拽的距离
*/
isInitDragPosition: false,
/**
* 是否正在检测是否停止拖拽
*/
isCheckingStopDragMove: false,
/**
* 总宽度(px)
*/
totalWidth: 0,
/**
* 每一块的间隔(px)
*/
stepPx: 0,
/**
* 已拖拽的距离(px)
*/
dragWidth: 0,
/**
* 已拖拽的百分比
*/
dragPercent: 0,
/**
* 每一次块的信息
* 例如:当最小值是2,最大值是10,step为2
* 那么生成[2,4,6,8,10] 共计5个
* 又获取到当前滑块总长度是200px
* 那么生成映射
* 2 => 0px~40px
* 4 => 40px~80px
* 6 => 80px~120px
* 8 => 120px~160px
* 10 => 160px~200px
*/
stepBlockMap: new Map<
number,
{
value: number;
px: number;
pxLeft: number;
pxRight: number;
percent: number;
}
>(),
tooltip: null as any as ReturnType<typeof PopsTooltip.init>,
},
$ele: {
itemLeftTextContainer: $li.querySelector<HTMLElement>(".pops-panel-item-left-text"),
slider: $li.querySelector<HTMLElement>(".pops-slider")!,
runAway: $li.querySelector<HTMLElement>(".pops-slider__runway")!,
bar: $li.querySelector<HTMLElement>(".pops-slider__bar")!,
buttonWrapper: $li.querySelector<HTMLElement>(".pops-slider__button-wrapper")!,
button: $li.querySelector<HTMLElement>(".pops-slider__button")!,
},
$interval: {
isCheck: false,
},
$tooltip: null as any as ReturnType<typeof popsUtils.AnyTouch>["prototype"],
init() {
this.initEleData();
this.setToolTipEvent();
this.setPanEvent();
this.onRunAwayClick();
this.intervalInit();
if (this.isDisabledDragWithConfig()) {
this.disableDrag();
}
},
/**
* 10s内循环获取slider的宽度等信息
* 获取到了就可以初始化left的值
* @param [checkStepTime=200] 每次检测的间隔时间
* @param [maxTime=10000] 最大的检测时间
*/
intervalInit(checkStepTime = 200, maxTime = 10000) {
if (this.$interval.isCheck) {
return;
}
this.$interval.isCheck = true;
let isSuccess = false;
const oldTotalWidth = this.$data.totalWidth;
let timer: number | undefined = void 0;
const interval = setInterval(() => {
if (isSuccess) {
this.$interval.isCheck = false;
clearTimeout(timer);
clearInterval(interval);
} else {
this.initTotalWidth();
if (this.$data.totalWidth !== 0) {
isSuccess = true;
if (this.$data.totalWidth !== oldTotalWidth) {
// slider的总宽度改变了
if (PopsMathFloatUtils.isFloat(this.step)) {
this.initFloatStepMap();
} else {
this.initStepMap();
}
this.initSliderPosition();
}
}
}
}, checkStepTime);
// 最长检测时间是10s
timer = setTimeout(() => {
clearInterval(interval);
}, maxTime);
},
/**
* 把数据添加到元素上
*/
initEleData() {
this.$ele.slider.setAttribute("data-min", this.min.toString());
this.$ele.slider.setAttribute("data-max", this.max.toString());
this.$ele.slider.setAttribute("data-value", this.value.toString());
this.$ele.slider.setAttribute("data-step", this.step.toString());
Reflect.set(this.$ele.slider, "data-min", this.min);
Reflect.set(this.$ele.slider, "data-max", this.max);
Reflect.set(this.$ele.slider, "data-value", this.value);
Reflect.set(this.$ele.slider, "data-step", this.step);
},
/**
* 初始化滑块的总长度的数据(px)
*/
initTotalWidth() {
this.$data.totalWidth = popsDOMUtils.width(this.$ele.runAway);
},
/**
* 初始化每一个块的具体数据信息
*/
initStepMap() {
let index = 0;
// 计算出份数
const blockNums = (this.max - this.min) / this.step;
// 计算出每一份占据的px
this.$data.stepPx = this.$data.totalWidth / blockNums;
let widthPx = 0;
for (let stepValue = this.min; stepValue <= this.max; stepValue += this.step) {
const value = this.formatValue(stepValue);
let info;
if (value === this.min) {
// 起始
info = {
value: value,
px: 0,
pxLeft: 0,
pxRight: this.$data.stepPx / 2,
percent: 0,
};
} else {
info = {
value: value,
px: widthPx,
pxLeft: widthPx - this.$data.stepPx / 2,
pxRight: widthPx + this.$data.stepPx / 2,
percent: widthPx / this.$data.totalWidth,
};
//if (value === this.max) {
// info["pxLeft"] = this.$data.stepBlockMap.get(
// index - 1
// ).pxRight;
// info["pxRight"] = this.$data.totalWidth;
//}
}
this.$data.stepBlockMap.set(index, info);
index++;
widthPx += this.$data.stepPx;
}
},
/**
* 初始化每一个块的具体数据信息(浮点)
*/
initFloatStepMap() {
let index = 0;
// 计算出份数
const blockNums = (this.max - this.min) / this.step;
// 计算出每一份占据的px
this.$data.stepPx = this.$data.totalWidth / blockNums;
let widthPx = 0;
for (
let stepValue = this.min;
stepValue <= this.max;
stepValue = PopsMathFloatUtils.add(stepValue, this.step)
) {
const value = this.formatValue(stepValue);
let info;
if (value === this.min) {
// 起始
info = {
value: value,
px: 0,
pxLeft: 0,
pxRight: this.$data.stepPx / 2,
percent: 0,
};
} else {
info = {
value: value,
px: widthPx,
pxLeft: widthPx - this.$data.stepPx / 2,
pxRight: widthPx + this.$data.stepPx / 2,
percent: widthPx / this.$data.totalWidth,
};
//if (value === this.max) {
// info["pxLeft"] = this.$data.stepBlockMap.get(
// index - 1
// ).pxRight;
// info["pxRight"] = this.$data.totalWidth;
//}
}
this.$data.stepBlockMap.set(index, info);
index++;
widthPx += this.$data.stepPx;
}
},
/**
* 初始化slider的默认起始left的百分比值
*/
initSliderPosition() {
// 设置起始默认style的left值
let percent = 0;
for (const [, stepBlockInfo] of this.$data.stepBlockMap.entries()) {
// 判断值是否和区域内的值相等
if (stepBlockInfo.value == this.value) {
percent = stepBlockInfo.percent;
this.$data.dragWidth = stepBlockInfo.px;
break;
}
}
percent = this.formatValue(percent * 100);
this.setSliderPosition(percent);
},
/**
* 判断数字是否是浮点数
* @param num
*/
isFloat(num: number) {
return Number(num) === num && num % 1 !== 0;
},
/**
* 值改变的回调
* @param event
* @param value
*/
valueChangeCallBack(event: any, value: number) {
if (typeof viewConfig.callback === "function") {
viewConfig.callback(event, value);
}
},
/**
* 根据拖拽距离获取滑块应该在的区间和值
* @param dragX
*/
getDragInfo(dragX: number) {
let result = this.$data.stepBlockMap.get(0);
for (const [, stepBlockInfo] of this.$data.stepBlockMap.entries()) {
if (stepBlockInfo.pxLeft <= dragX && dragX < stepBlockInfo.pxRight) {
result = stepBlockInfo;
break;
}
}
return result;
},
/**
* 获取滑块的当前脱拖拽占据的百分比
* @param dragWidth
*/
getSliderPositonPercent(dragWidth: number) {
return dragWidth / this.$data.totalWidth;
},
/**
* 根据step格式化value
* @param num
*/
formatValue(num: number) {
if (PopsMathFloatUtils.isFloat(this.step)) {
num = parseFloat(num.toFixed(2));
} else {
num = parseInt(num.toString());
}
return num;
},
/**
* 设置滑块的位置偏移(left)
* @param percent 百分比
*/
setSliderPosition(percent: number) {
if (parseInt(percent.toString()) === 1) {
percent = 1;
}
if (percent > 1) {
percent = percent / 100;
}
// 滑块按钮的偏移
this.$ele.buttonWrapper.style.left = `${percent * 100}%`;
// 滑块进度的宽度
this.$ele.bar.style.width = `${percent * 100}%`;
},
/**
* 禁止拖拽
*/
disableDrag() {
popsDOMUtils.addClassName(this.$ele.runAway, "pops-slider-is-disabled");
popsDOMUtils.addClassName(this.$ele.runAway, PopsCommonCSSClassName.textIsDisabled);
},
/**
* 允许拖拽
*/
allowDrag() {
popsDOMUtils.removeClassName(this.$ele.runAway, "pops-slider-is-disabled");
popsDOMUtils.removeClassName(this.$ele.runAway, PopsCommonCSSClassName.textIsDisabled);
},
/**
* 判断当前滑块是否被禁用
*/
isDisabledDrag() {
return popsDOMUtils.containsClassName(this.$ele.runAway, "pops-slider-is-disabled");
},
/**
* 判断当前滑块是否被禁用(配置中判断)
*/
isDisabledDragWithConfig() {
const isDisabled = typeof viewConfig.disabled === "function" ? viewConfig.disabled() : viewConfig.disabled;
if (typeof isDisabled === "boolean") {
return isDisabled;
} else {
return false;
}
},
/**
* 设置进度条点击定位的事件
*/
onRunAwayClick() {
popsDOMUtils.on<PointerEvent | MouseEvent>(
this.$ele.runAway,
"click",
(event) => {
if (event.target !== this.$ele.runAway && event.target !== this.$ele.bar) {
return;
}
const clickX = parseFloat(event.offsetX.toString());
const dragStartResult = this.dragStartCallBack();
if (!dragStartResult) {
return;
}
this.dragMoveCallBack(event, clickX, this.value);
this.dragEndCallBack(clickX);
},
{
capture: false,
}
);
},
/**
* 拖拽开始的回调,如果返回false,禁止拖拽
*/
dragStartCallBack() {
if (this.isDisabledDragWithConfig()) {
// 禁止
this.disableDrag();
return false;
}
if (!this.$data.isMove) {
// 非移动中
if (this.isDisabledDrag()) {
// 允许
this.allowDrag();
}
this.$data.isMove = true;
}
return true;
},
/**
* 拖拽中的回调
* @param event 事件
* @param dragX 当前拖拽的距离
* @param oldValue 旧的值
*/
dragMoveCallBack(event: any, dragX: number, oldValue: number) {
let dragPercent = 0;
if (dragX <= 0) {
dragPercent = 0;
this.value = this.min;
} else if (dragX >= this.$data.totalWidth) {
dragPercent = 1;
this.value = this.max;
} else {
const dragInfo = this.getDragInfo(dragX)!;
dragPercent = dragInfo.percent;
this.value = this.formatValue(dragInfo.value);
}
this.$data.dragPercent = dragPercent;
this.setSliderPosition(this.$data.dragPercent);
this.showToolTip();
if (oldValue !== this.value) {
this.valueChangeCallBack(event, this.value);
}
},
/**
* 拖拽结束的回调
*/
dragEndCallBack(dragX: number) {
this.$data.isMove = false;
if (dragX <= 0) {
this.$data.dragWidth = 0;
} else if (dragX >= this.$data.totalWidth) {
this.$data.dragWidth = this.$data.totalWidth;
} else {
this.$data.dragWidth = dragX;
}
this.closeToolTip();
},
/**
* 设置点击拖拽事件
*/
setPanEvent() {
const AnyTouch = popsUtils.AnyTouch();
this.$tooltip = new AnyTouch(this.$ele.button, {
preventDefault() {
return false;
},
});
/**
* 当前的拖拽的距离px
*/
let currentDragX = 0;
// 监听拖拽
this.$tooltip.on("at:move", (event) => {
if (!this.dragStartCallBack()) {
return;
}
const oldValue = this.value;
const runAwayRect = this.$ele.runAway.getBoundingClientRect();
let displacementX = event.x - (runAwayRect.left + globalThis.screenX);
if (displacementX <= 0) {
displacementX = 0;
} else if (displacementX >= runAwayRect.width) {
displacementX = runAwayRect.width;
}
currentDragX = displacementX;
// 拖拽移动
this.dragMoveCallBack(event, currentDragX, oldValue);
});
// 监听触点离开,处理某些情况下,拖拽松开,但是未触发pan事件,可以通过设置这个来关闭tooltip
this.$tooltip.on("at:end", () => {
this.dragEndCallBack(currentDragX);
});
},
/**
* 显示悬浮的
*/
showToolTip() {
this.$data.tooltip.toolTip.show();
},
/**
* 关闭悬浮的
*/
closeToolTip() {
this.$data.tooltip.toolTip.close();
},
/**
* 检测在1000ms内,是否停止了拖拽
*/
checkStopDragMove() {
if (this.$data.isCheckingStopDragMove) {
return;
}
this.$data.isCheckingStopDragMove = true;
const interval = setInterval(() => {
if (!this.$data.isMove) {
this.$data.isCheckingStopDragMove = false;
this.closeToolTip();
clearInterval(interval);
}
}, 200);
setTimeout(() => {
this.$data.isCheckingStopDragMove = false;
clearInterval(interval);
}, 2000);
},
/**
* 设置拖拽按钮的悬浮事件
*/
setToolTipEvent() {
/**
* 获取提示的内容
*/
function getToolTipContent() {
if (typeof viewConfig.getToolTipContent === "function") {
return viewConfig.getToolTipContent(PopsPanelSlider.value);
} else {
return PopsPanelSlider.value.toString();
}
}
const tooltip = PopsTooltip.init({
$target: this.$ele.button,
content: getToolTipContent,
zIndex: () => {
// return PopsInstanceUtils.getPopsMaxZIndex().zIndex;
return popsUtils.getMaxZIndexNodeInfoFromPoint(this.$ele.button)[0].zIndex;
},
isFixed: true,
className: "github-tooltip",
only: false,
eventOption: {
capture: true,
passive: true,
},
showBeforeCallBack: () => {
const isShowHoverTip =
typeof viewConfig.isShowHoverTip === "function"
? viewConfig.isShowHoverTip()
: typeof viewConfig.isShowHoverTip === "boolean"
? viewConfig.isShowHoverTip
: true;
if (!isShowHoverTip) {
return false;
}
this.intervalInit();
},
showAfterCallBack: () => {
tooltip.toolTip.changeContent(getToolTipContent());
},
closeBeforeCallBack: () => {
if (this.$data.isMove) {
this.checkStopDragMove();
return false;
}
},
alwaysShow: false,
position: "top",
arrowDistance: 10,
});
this.$data.tooltip = tooltip;
},
};
PopsPanelSlider.init();
Reflect.set($li, "data-slider", PopsPanelSlider);
return {
$el: $li,
handler: PopsPanelSlider,
};
},
/**
* type ==> input
* 获取中间容器的元素<li>
* @param viewConfig
*/
createSectionContainerItem_input(viewConfig: PopsPanelInputConfig) {
const $li = popsDOMUtils.createElement("li");
Reflect.set($li, this.$data.nodeStoreConfigKey, viewConfig);
this.setElementClassName($li, viewConfig.className);
this.setElementAttributes($li, viewConfig.attributes);
this.setElementProps($li, viewConfig.props);
const defaultInputType: PopsPanelInputType = "text";
const inputType = viewConfig.inputType || defaultInputType;
// 左边底部的描述的文字
let leftDescriptionText = "";
if (viewConfig.description) {
leftDescriptionText = `<p class="pops-panel-item-left-desc-text">${viewConfig.description}</p>`;
}
PopsSafeUtils.setSafeHTML(
$li,
/*html*/ `
<div class="pops-panel-item-left-text">
<p class="pops-panel-item-left-main-text">${viewConfig.text}</p>${leftDescriptionText}</div>
<div class="pops-panel-input">
<div class="pops-panel-input_inner">
<input type="${inputType}" placeholder="${viewConfig.placeholder ?? ""}">
</div>
</div>
`
);
const PopsPanelInput = {
[Symbol.toStringTag]: "PopsPanelInput",
$el: {
itemLeftTextContainer: $li.querySelector<HTMLElement>(".pops-panel-item-left-text"),
panelInput: $li.querySelector<HTMLDivElement>(".pops-panel-input")!,
panelInputInner: $li.querySelector<HTMLDivElement>(".pops-panel-input_inner")!,
input: $li.querySelector<HTMLInputElement>("input")!,
/** span.pops-panel-input__suffix */
suffix: popsDOMUtils.createElement("span"),
/** span.pops-panel-input__suffix-inner */
suffixInner: null as any as HTMLSpanElement,
/** i.pops-panel-icon */
icon: null as any as HTMLElement,
},
$data: {
value: viewConfig.getValue(),
/**
* inputType 为 password时使用该值
*
* 当前内容的可见性
*/
isVisible: false,
},
init() {
this.initEle();
this.setInputValue(this.$data.value);
// 如果是密码框,放进图标
if (viewConfig.inputType === "password") {
// 显示密码
this.setCircleIcon(PopsIcon.getIcon("view")!);
this.onIconClick();
} else {
// 先判断预设值是否为空,不为空添加清空图标按钮
// 且为普通的输入框
if (this.$el.input.value != "" && this.isTextInputType()) {
// 清除内容的图标
this.setCircleIcon(PopsIcon.getIcon("circleClose")!);
this.onIconClick();
} else {
// 隐藏图标
this.hideCircleIconWrapper();
}
}
this.onValueChange();
// 是否禁用复选框
const disabled = typeof viewConfig.disabled === "function" ? viewConfig.disabled() : viewConfig.disabled;
if (disabled) {
this.disable();
}
if (typeof viewConfig.handlerCallBack === "function") {
viewConfig.handlerCallBack($li, this.$el.input);
}
},
/**
* 初始化$ele的配置
*/
initEle() {
this.$el.input.parentElement!.insertBefore(this.$el.suffix, this.$el.input.nextSibling);
popsDOMUtils.addClassName(this.$el.suffix, "pops-panel-input__suffix");
PopsSafeUtils.setSafeHTML(
this.$el.suffix,
/*html*/ `
<span class="pops-panel-input__suffix-inner">
<i class="pops-panel-icon"></i>
</span>
`
);
this.$el.suffixInner = this.$el.suffix.querySelector<HTMLElement>(".pops-panel-input__suffix-inner")!;
this.$el.icon = this.$el.suffix.querySelector<HTMLElement>(".pops-panel-icon")!;
popsDOMUtils.addClassName(this.$el.panelInput, PopsCommonCSSClassName.userSelectNone);
},
/**
* 校验输入框类型是否是字符串输入框类型
*/
isTextInputType() {
const typeList: PopsPanelInputType[] = ["text", "search", "email", "tel", "url"];
return typeList.includes(viewConfig.inputType || defaultInputType);
},
/**
* 是否是时间输入框类型
*/
isDateInputType() {
const typeList: PopsPanelInputType[] = ["date", "datetime-local", "month", "time", "week"];
return typeList.includes(viewConfig.inputType || defaultInputType);
},
/**
* 是否是数字输入框类型
*/
isNumberInputType() {
const typeList: PopsPanelInputType[] = ["number"];
return typeList.includes(viewConfig.inputType || defaultInputType);
},
/**
* 禁用
*/
disable() {
this.$el.input.disabled = true;
popsDOMUtils.addClassName(this.$el.panelInput, "pops-input-disabled");
popsDOMUtils.addClassName(this.$el.itemLeftTextContainer, PopsCommonCSSClassName.textIsDisabled);
},
/**
* 取消禁用
*/
notDisable() {
this.$el.input.disabled = false;
popsDOMUtils.removeClassName(this.$el.panelInput, "pops-input-disabled");
popsDOMUtils.removeClassName(this.$el.itemLeftTextContainer, PopsCommonCSSClassName.textIsDisabled);
},
/**
* 判断是否已被禁用
*/
isDisabled() {
return this.$el.input.disabled;
},
/**
* 设置输入框内容
* @param value 值
*/
setInputValue(value: string | number | Date = "") {
if (typeof value === "number" && this.isNumberInputType()) {
this.$el.input.valueAsNumber = value;
} else if (typeof value === "object" && value instanceof Date && this.isDateInputType()) {
this.$el.input.valueAsDate = value;
} else {
this.$el.input.value = value.toString();
}
},
/**
* 设置input元素的type
* @param typeValue type值
*/
setInputType(typeValue: PopsPanelInputType = "text") {
this.$el.input.setAttribute("type", typeValue);
},
/**
* 删除图标按钮
*/
removeCircleIcon() {
PopsSafeUtils.setSafeHTML(this.$el.icon, "");
},
/**
* 添加清空图标按钮
* @param svgHTML svg图标,默认为清空的图标
*/
setCircleIcon(svgHTML: string = PopsIcon.getIcon("circleClose")!) {
PopsSafeUtils.setSafeHTML(this.$el.icon, svgHTML);
},
/**
* 隐藏图标容器
*/
hideCircleIconWrapper() {
popsDOMUtils.cssHide(this.$el.suffix, true);
},
/**
* 显示图标容器
*/
showCircleIconWrapper() {
popsDOMUtils.cssShow(this.$el.suffix);
},
/**
* 添加图标按钮的点击事件
*/
onIconClick() {
popsDOMUtils.on(this.$el.icon, "click", (evt) => {
popsDOMUtils.preventEvent(evt);
if (this.isDisabled()) {
return;
}
// 删除图标
this.removeCircleIcon();
if (inputType === "password") {
// 配置类型为密码输入框
if (this.$data.isVisible) {
// 当前可见 => 点击改变为隐藏
this.$data.isVisible = false;
// 显示输入框内容,且更换图标为隐藏图标
this.setInputType("text");
this.setCircleIcon(PopsIcon.getIcon("hide")!);
} else {
// 当前不可见 => 点击改变为显示
this.$data.isVisible = true;
// 隐藏输入框内容,且更换图标为显示图标
this.setInputType("password");
this.setCircleIcon(PopsIcon.getIcon("view")!);
}
} else {
// 普通输入框
// 清空内容
this.setInputValue("");
// 获取焦点
this.$el.input.focus();
// 触发内容改变事件
this.$el.input.dispatchEvent(new Event("input"));
}
});
},
/**
* 监听输入框内容改变
*/
onValueChange() {
popsDOMUtils.on<InputEvent>(this.$el.input, ["input", "propertychange"], (event) => {
this.$data.value = this.$el.input.value;
if (inputType !== "password") {
// 不是密码框
if (this.$el.input.value !== "" && this.$el.icon.innerHTML === "" && this.isTextInputType()) {
// 不为空,显示清空图标
this.setCircleIcon(PopsIcon.getIcon("circleClose")!);
this.onIconClick();
this.showCircleIconWrapper();
} else if (this.$el.input.value === "") {
this.removeCircleIcon();
}
}
if (typeof viewConfig.callback === "function") {
let ret;
if (viewConfig.inputType === "number") {
ret = viewConfig.callback(event, this.$el.input.value, this.$el.input.valueAsNumber);
} else if (this.isDateInputType()) {
ret = viewConfig.callback(
event,
this.$el.input.value,
this.$el.input.valueAsNumber,
this.$el.input.valueAsDate
);
} else {
ret = viewConfig.callback(event, this.$el.input.value);
}
if (ret) {
if (ret.valid) {
// 校验通过
this.removeValidErrorMsg();
} else {
// 校验失败
// 显示失败提示
this.addValidErrorMsg(ret.message);
}
} else {
this.removeValidErrorMsg();
}
}
});
},
/**
* 主动触发输入框改变事件
*/
emitValueChange() {
this.$el.input.dispatchEvent(new Event("input"));
},
/**
* 添加校验失败的提示信息
* @param msg 提示信息
*/
addValidErrorMsg(msg?: string) {
if (msg == null) return;
const $validErrorMsg =
this.$el.panelInput.querySelector<HTMLDivElement>(".pops-panel-input-valid-error") ||
popsDOMUtils.createElement("div", {
className: "pops-panel-input-valid-error",
innerHTML: /*html*/ `<span></span>`,
});
const $validErrorMsgSpan = $validErrorMsg.querySelector("span")!;
if ($validErrorMsgSpan.innerHTML !== msg) {
PopsSafeUtils.setSafeHTML($validErrorMsgSpan, msg);
}
if (!$validErrorMsg.parentElement) {
popsDOMUtils.after(this.$el.panelInputInner, $validErrorMsg);
}
},
/**
* 移除校验失败的提示信息
*/
removeValidErrorMsg() {
const $validErrorMsg = this.$el.panelInput.querySelector(".pops-panel-input-valid-error");
popsDOMUtils.remove($validErrorMsg);
},
};
PopsPanelInput.init();
Reflect.set($li, "data-input", PopsPanelInput);
return {
$el: $li,
handler: PopsPanelInput,
};
},
/**
* type ==> textarea
* 获取中间容器的元素<li>
* @param viewConfig
*/
createSectionContainerItem_textarea(viewConfig: PopsPanelTextAreaConfig) {
const $li = popsDOMUtils.createElement("li");
Reflect.set($li, this.$data.nodeStoreConfigKey, viewConfig);
this.setElementClassName($li, viewConfig.className);
this.setElementAttributes($li, viewConfig.attributes);
this.setElementProps($li, viewConfig.props);
// 左边底部的描述的文字
let leftDescriptionText = "";
if (viewConfig.description) {
leftDescriptionText = `<p class="pops-panel-item-left-desc-text">${viewConfig.description}</p>`;
}
PopsSafeUtils.setSafeHTML(
$li,
/*html*/ `
<div class="pops-panel-item-left-text">
<p class="pops-panel-item-left-main-text">${viewConfig.text}</p>${leftDescriptionText}</div>
<div class="pops-panel-textarea">
<textarea placeholder="${viewConfig.placeholder ?? ""}"></textarea>
</div>
`
);
const PopsPanelTextArea = {
[Symbol.toStringTag]: "PopsPanelTextArea",
$ele: {
itemLeftTextContainer: $li.querySelector<HTMLElement>(".pops-panel-item-left-text"),
panelTextarea: $li.querySelector<HTMLDivElement>(".pops-panel-textarea")!,
textarea: $li.querySelector<HTMLTextAreaElement>(".pops-panel-textarea textarea")!,
},
$data: {
value: viewConfig.getValue(),
},
init() {
this.setValue(this.$data.value);
this.onValueChange();
const disabled = typeof viewConfig.disabled === "function" ? viewConfig.disabled() : viewConfig.disabled;
if (disabled) {
this.disable();
}
},
disable() {
this.$ele.textarea.setAttribute("disabled", "true");
popsDOMUtils.addClassName(this.$ele.panelTextarea, "pops-panel-textarea-disable");
popsDOMUtils.addClassName(this.$ele.itemLeftTextContainer, PopsCommonCSSClassName.textIsDisabled);
},
notDisable() {
this.$ele.textarea.removeAttribute("disabled");
popsDOMUtils.removeClassName(this.$ele.panelTextarea, "pops-panel-textarea-disable");
popsDOMUtils.removeClassName(this.$ele.itemLeftTextContainer, PopsCommonCSSClassName.textIsDisabled);
},
isDisabled() {
return (
this.$ele.textarea.hasAttribute("disabled") ||
popsDOMUtils.containsClassName(this.$ele.panelTextarea, "pops-panel-textarea-disable")
);
},
setValue(value: string) {
this.$ele.textarea.value = value;
},
/**
* 监听选择内容改变
*/
onValueChange() {
popsDOMUtils.on<InputEvent>(this.$ele.textarea, ["input", "propertychange"], (event) => {
const valu