UNPKG

@whitesev/pops

Version:

弹窗库,包含了alert、confirm、prompt、drawer、folder、loading、iframe、panel、tooltip、searchSuggestion、rightClickMenu组件

1,458 lines (1,450 loc) 152 kB
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