@whitesev/pops
Version:
弹窗库
685 lines (676 loc) • 19.4 kB
text/typescript
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;
}
}