UNPKG

@whitesev/pops

Version:

弹窗库

685 lines (676 loc) 19.4 kB
import { PopsHandler } from "../../handler/PopsHandler"; import { pops } from "../../Pops"; import { popsDOMUtils } from "../../utils/PopsDOMUtils"; import { popsUtils } from "../../utils/PopsUtils"; import type { PopsSearchSuggestionDetails } from "./indexType"; import { searchSuggestionConfig as PopsSearchSuggestionConfig } from "./config"; import { GlobalConfig } from "../../GlobalConfig"; import { PopsSafeUtils } from "../../utils/PopsSafeUtils"; export class PopsSearchSuggestion { constructor(details: PopsSearchSuggestionDetails) { const guid = popsUtils.getRandomGUID(); // 设置当前类型 const PopsType = "searchSuggestion"; let config = PopsSearchSuggestionConfig(); config = popsUtils.assign(config, GlobalConfig.getGlobalConfig()); config = popsUtils.assign(config, details); if (config.target == null) { throw new TypeError("config.target 不能为空"); } /* 做下兼容处理 */ if (config.inputTarget == null) { config.inputTarget = config.target as HTMLInputElement; } if (details.data) { config.data = details.data; } const { $shadowContainer, $shadowRoot } = PopsHandler.handlerShadow(config); PopsHandler.handleInit($shadowRoot, [ pops.config.cssText.index, pops.config.cssText.anim, pops.config.cssText.common, ]); if (config.style != null) { let cssNode = document.createElement("style"); popsDOMUtils.createElement( "style", { innerHTML: config.style, }, { type: "text/css", } ); $shadowRoot.appendChild(cssNode); } const SearchSuggestion = { /** * 当前的环境,可以是document,可以是shadowroot,默认是document */ selfDocument: config.selfDocument, $el: { /** 根元素 */ root: null as any as HTMLElement, /** ul元素 */ $hintULContainer: null as any as HTMLUListElement, /** 动态更新CSS */ $dynamicCSS: null as any as HTMLStyleElement, }, $data: { /** 是否结果为空 */ isEmpty: true, }, /** * 初始化 */ init(parentElement = document.body || document.documentElement) { this.initEl(); SearchSuggestion.update( typeof config.data === "function" ? config.data() : config.data ); SearchSuggestion.updateDynamicCSS(); SearchSuggestion.changeHintULElementWidth(); SearchSuggestion.changeHintULElementPosition(); SearchSuggestion.hide(); if (config.isAnimation) { SearchSuggestion.$el.root.classList.add(`pops-${PopsType}-animation`); } $shadowRoot.appendChild(SearchSuggestion.$el.root); parentElement.appendChild($shadowContainer); }, /** 初始化元素变量 */ initEl() { this.$el.root = SearchSuggestion.getSearchSelectElement(); this.$el.$dynamicCSS = this.$el.root.querySelector<HTMLStyleElement>( "style[data-dynamic]" )!; this.$el.$hintULContainer = SearchSuggestion.$el.root.querySelector<HTMLUListElement>("ul")!; }, /** * 获取显示出搜索建议框的html */ getSearchSelectElement() { let element = popsDOMUtils.createElement( "div", { className: `pops pops-${PopsType}-search-suggestion`, innerHTML: /*html*/ ` <style data-dynamic="true"> ${this.getDynamicCSS()} </style> <ul class="pops-${PopsType}-search-suggestion-hint"> ${config.toSearhNotResultHTML} </ul> `, }, { "data-guid": guid, "type-value": PopsType, } ); if (config.className !== "" && config.className != null) { popsDOMUtils.addClassName(element, config.className); } return element; }, /** 动态获取CSS */ getDynamicCSS() { return /*css*/ ` .pops-${PopsType}-animation{ -moz-animation: searchSelectFalIn 0.5s 1 linear; -webkit-animation: searchSelectFalIn 0.5s 1 linear; -o-animation: searchSelectFalIn 0.5s 1 linear; -ms-animation: searchSelectFalIn 0.5s 1 linear; } .pops-${PopsType}-search-suggestion{ border: initial; overflow: initial; } ul.pops-${PopsType}-search-suggestion-hint{ position: ${config.isAbsolute ? "absolute" : "fixed"}; z-index: ${PopsHandler.handleZIndex(config.zIndex)}; width: 0; left: 0; max-height: ${config.maxHeight}; overflow-x: hidden; overflow-y: auto; padding: 5px 0; background-color: #fff; box-sizing: border-box; border-radius: 4px; box-shadow: 0 1px 6px rgb(0 0 0 / 20%); } /* 建议框在上面时 */ ul.pops-${PopsType}-search-suggestion-hint[data-top-reverse]{ display: flex; flex-direction: column-reverse; } ul.pops-${PopsType}-search-suggestion-hint[data-top-reverse] li{ flex-shrink: 0; } ul.pops-${PopsType}-search-suggestion-hint li{ padding: 7px; margin: 0; clear: both; color: #515a6e; font-size: 14px; list-style: none; cursor: pointer; transition: background .2s ease-in-out; overflow: hidden; text-overflow: ellipsis; width: 100%; } ul.pops-${PopsType}-search-suggestion-hint li[data-none]{ text-align: center; font-size: 12px; color: #8e8e8e; } ul.pops-${PopsType}-search-suggestion-hint li:hover{ background-color: rgba(0, 0, 0, .1); } `; }, /** * 获取显示出搜索建议框的每一项的html * @param data 当前项的值 * @param index 当前项的下标 */ getSearchItemLiElement(data: any, index: number) { return popsDOMUtils.createElement("li", { className: `pops-${PopsType}-search-suggestion-hint-item pops-flex-items-center pops-flex-y-center`, "data-index": index, "data-value": SearchSuggestion.getItemDataValue(data), innerHTML: ` ${config.getItemHTML(data)} ${ config.deleteIcon.enable ? SearchSuggestion.getDeleteIconHTML() : "" } `, }); }, /** * 获取data-value值 * @param data */ getItemDataValue(data: any) { return data; }, /** * 设置搜索建议框每一项的点击事件 * @param liElement */ setSearchItemClickEvent(liElement: HTMLLIElement) { popsDOMUtils.on( liElement, "click", void 0, (event) => { popsDOMUtils.preventEvent(event); let $click = event.target as HTMLLIElement; if ($click.closest(`.pops-${PopsType}-delete-icon`)) { /* 点击的是删除按钮 */ if (typeof config.deleteIcon.callback === "function") { config.deleteIcon.callback( event, liElement, (liElement as any)["data-value"] ); } if (!this.$el.$hintULContainer.children.length) { /* 全删完了 */ this.clear(); } } else { /* 点击项主体 */ config.itemClickCallBack( event, liElement, (liElement as any)["data-value"] ); } }, { capture: true, } ); }, /** * 设置搜索建议框每一项的选中事件 * @param liElement */ setSearchItemSelectEvent(liElement: HTMLLIElement) { // popsDOMUtils.on( // liElement, // "keyup", // void 0, // (event) => { // let value = liElement["data-value"]; // config.selectCallBack(event, liElement, value); // }, // { // capture: true, // } // ); }, /** * 监听输入框内容改变 */ setInputChangeEvent( option: AddEventListenerOptions = { capture: true, } ) { /* 必须是input或者textarea才有input事件 */ if ( !( config.inputTarget instanceof HTMLInputElement || config.inputTarget instanceof HTMLTextAreaElement ) ) { return; } /* 是input输入框 */ /* 禁用输入框自动提示 */ config.inputTarget.setAttribute("autocomplete", "off"); /* 内容改变事件 */ popsDOMUtils.on( config.inputTarget, "input", void 0, async (event) => { let getListResult = await config.getData( (event.target as any).value ); SearchSuggestion.update(getListResult); }, option ); }, /** * 移除输入框内容改变的监听 */ removeInputChangeEvent( option: AddEventListenerOptions = { capture: true, } ) { popsDOMUtils.off(config.inputTarget, "input", void 0, void 0, option); }, /** * 显示搜索建议框的事件 */ showEvent() { SearchSuggestion.updateDynamicCSS(); SearchSuggestion.changeHintULElementWidth(); SearchSuggestion.changeHintULElementPosition(); if (config.toHideWithNotResult) { if (SearchSuggestion.$data.isEmpty) { SearchSuggestion.hide(); } else { SearchSuggestion.show(); } } else { SearchSuggestion.show(); } }, /** * 设置显示搜索建议框的事件 */ setShowEvent( option: AddEventListenerOptions = { capture: true, } ) { /* 焦点|点击事件*/ if (config.followPosition === "target") { popsDOMUtils.on( [config.target], ["focus", "click"], void 0, SearchSuggestion.showEvent, option ); } else if (config.followPosition === "input") { popsDOMUtils.on( [config.inputTarget], ["focus", "click"], void 0, SearchSuggestion.showEvent, option ); } else if (config.followPosition === "inputCursor") { popsDOMUtils.on( [config.inputTarget], ["focus", "click", "input"], void 0, SearchSuggestion.showEvent, option ); } else { throw new TypeError("未知followPosition:" + config.followPosition); } }, /** * 移除显示搜索建议框的事件 */ removeShowEvent( option: AddEventListenerOptions = { capture: true, } ) { /* 焦点|点击事件*/ popsDOMUtils.off( [config.target, config.inputTarget], ["focus", "click"], void 0, SearchSuggestion.showEvent, option ); /* 内容改变事件 */ popsDOMUtils.off( [config.inputTarget], ["input"], void 0, SearchSuggestion.showEvent, option ); }, /** * 隐藏搜索建议框的事件 * @param event */ hideEvent(event: PointerEvent | MouseEvent) { if (event.target instanceof Node) { if ($shadowContainer.contains(event.target)) { /* 点击在shadow上的 */ return; } if (config.target.contains(event.target)) { /* 点击在目标元素内 */ return; } if (config.inputTarget.contains(event.target)) { /* 点击在目标input内 */ return; } SearchSuggestion.hide(); } }, /** * 设置隐藏搜索建议框的事件 */ setHideEvent( option: AddEventListenerOptions = { capture: true, } ) { /* 全局点击事件 */ /* 全局触摸屏点击事件 */ if (Array.isArray(SearchSuggestion.selfDocument)) { SearchSuggestion.selfDocument.forEach(($checkParent) => { popsDOMUtils.on( $checkParent, ["click", "touchstart"], void 0, SearchSuggestion.hideEvent, option ); }); } else { popsDOMUtils.on( SearchSuggestion.selfDocument, ["click", "touchstart"], void 0, SearchSuggestion.hideEvent, option ); } }, /** * 移除隐藏搜索建议框的事件 */ removeHideEvent( option: AddEventListenerOptions = { capture: true, } ) { if (Array.isArray(SearchSuggestion.selfDocument)) { SearchSuggestion.selfDocument.forEach(($checkParent) => { popsDOMUtils.off( $checkParent, ["click", "touchstart"], void 0, SearchSuggestion.hideEvent, option ); }); } else { popsDOMUtils.off( SearchSuggestion.selfDocument, ["click", "touchstart"], void 0, SearchSuggestion.hideEvent, option ); } }, /** * 设置所有监听 */ setAllEvent( option: AddEventListenerOptions = { capture: true, } ) { SearchSuggestion.setInputChangeEvent(option); SearchSuggestion.setHideEvent(option); SearchSuggestion.setShowEvent(option); }, /** * 移除所有监听 */ removeAllEvent( option: AddEventListenerOptions = { capture: true, } ) { SearchSuggestion.removeInputChangeEvent(option); SearchSuggestion.removeHideEvent(option); SearchSuggestion.removeShowEvent(option); }, /** * 获取删除按钮的html */ getDeleteIconHTML(size = 16, fill = "#bababa") { return /*html*/ ` <svg class="pops-${PopsType}-delete-icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" fill="${fill}"> <path d="M512 883.2A371.2 371.2 0 1 0 140.8 512 371.2 371.2 0 0 0 512 883.2z m0 64a435.2 435.2 0 1 1 435.2-435.2 435.2 435.2 0 0 1-435.2 435.2z"></path> <path d="M557.056 512l122.368 122.368a31.744 31.744 0 1 1-45.056 45.056L512 557.056l-122.368 122.368a31.744 31.744 0 1 1-45.056-45.056L466.944 512 344.576 389.632a31.744 31.744 0 1 1 45.056-45.056L512 466.944l122.368-122.368a31.744 31.744 0 1 1 45.056 45.056z"></path> </svg> `; }, /** * 设置当前正在搜索中的提示 */ setPromptsInSearch() { let isSearchingElement = popsDOMUtils.createElement("li", { className: `pops-${PopsType}-search-suggestion-hint-searching-item`, innerHTML: config.searchingTip, }); SearchSuggestion.$el.$hintULContainer.appendChild(isSearchingElement); }, /** * 移除正在搜索中的提示 */ removePromptsInSearch() { SearchSuggestion.$el.$hintULContainer .querySelector( `li.pops-${PopsType}-search-suggestion-hint-searching-item` ) ?.remove(); }, /** * 清空所有的搜索结果 */ clearAllSearchItemLi() { PopsSafeUtils.setSafeHTML(SearchSuggestion.$el.$hintULContainer, ""); }, /** * 更新搜索建议框的位置(top、left) * 因为目标元素可能是动态隐藏的 */ changeHintULElementPosition( target = config.target ?? config.inputTarget ) { let targetRect: DOMRect | null = null; if (config.followPosition === "inputCursor") { targetRect = popsDOMUtils.getTextBoundingRect( config.inputTarget, config.inputTarget.selectionStart || 0, config.inputTarget.selectionEnd || 0, false ); } else { targetRect = config.isAbsolute ? popsDOMUtils.offset(target) : target.getBoundingClientRect(); } if (targetRect == null) { return; } // 文档最大高度 let documentHeight = document.documentElement.clientHeight; if (config.isAbsolute) { // 绝对定位 documentHeight = popsDOMUtils.height(document); } // 文档最大宽度 let documentWidth = popsDOMUtils.width(document); let position = config.position; if (config.position === "auto") { // 需目标高度+搜索建议框高度大于文档高度,则显示在上面 let targetBottom = targetRect.bottom; let searchSuggestionContainerHeight = popsDOMUtils.height( SearchSuggestion.$el.$hintULContainer ); if (targetBottom + searchSuggestionContainerHeight > documentHeight) { // 在上面 position = "top"; } else { // 在下面 position = "bottom"; SearchSuggestion.$el.$hintULContainer.removeAttribute("data-top"); } } if (position === "top") { if (config.positionTopToReverse) { SearchSuggestion.$el.$hintULContainer.setAttribute( "data-top-reverse", "true" ); } // 在上面 SearchSuggestion.$el.$hintULContainer.style.top = ""; SearchSuggestion.$el.$hintULContainer.style.bottom = documentHeight - targetRect.top + config.topDistance + "px"; } else if (position === "bottom") { // 在下面 SearchSuggestion.$el.$hintULContainer.removeAttribute( "data-top-reverse" ); SearchSuggestion.$el.$hintULContainer.style.bottom = ""; SearchSuggestion.$el.$hintULContainer.style.top = targetRect.height + targetRect.top + config.topDistance + "px"; } let hintUIWidth = popsDOMUtils.width( SearchSuggestion.$el.$hintULContainer ); SearchSuggestion.$el.$hintULContainer.style.left = (targetRect.left + hintUIWidth > documentWidth ? documentWidth - hintUIWidth : targetRect.left) + "px"; }, /** * 更新搜索建议框的width * 因为目标元素可能是动态隐藏的 */ changeHintULElementWidth(target = config.target ?? config.inputTarget) { let targetRect = target.getBoundingClientRect(); if (config.followTargetWidth) { SearchSuggestion.$el.$hintULContainer.style.width = targetRect.width + "px"; } else { SearchSuggestion.$el.$hintULContainer.style.width = config.width; } }, /** * 动态更新CSS */ updateDynamicCSS() { let cssText = this.getDynamicCSS(); PopsSafeUtils.setSafeHTML(this.$el.$dynamicCSS, cssText); }, /** * 更新页面显示的搜索结果 * @param data */ update(data: any[] = []) { if (!Array.isArray(data)) { throw new TypeError("传入的数据不是数组"); } config.data = data; /* 清空已有的搜索结果 */ if (config.data.length) { SearchSuggestion.$data.isEmpty = false; if (config.toHideWithNotResult) { SearchSuggestion.show(); } SearchSuggestion.clearAllSearchItemLi(); /* 添加进ul中 */ config.data.forEach((item, index) => { let itemElement = SearchSuggestion.getSearchItemLiElement( item, index ); SearchSuggestion.setSearchItemClickEvent(itemElement); SearchSuggestion.setSearchItemSelectEvent(itemElement); SearchSuggestion.$el.$hintULContainer.appendChild(itemElement); }); } else { /* 清空 */ SearchSuggestion.clear(); } }, /** * 清空当前的搜索结果并显示无结果 */ clear() { this.$data.isEmpty = true; this.clearAllSearchItemLi(); this.$el.$hintULContainer.appendChild( popsUtils.parseTextToDOM(config.toSearhNotResultHTML) ); if (config.toHideWithNotResult) { this.hide(); } }, /** * 隐藏搜索建议框 */ hide() { this.$el.root.style.display = "none"; }, /** * 显示搜索建议框 */ show() { this.$el.root.style.display = ""; }, }; return SearchSuggestion; } }