import cx from "classnames"; import { api } from "lwc"; import { TreeNode } from "typings/custom"; import { toJson } from "dxUtils/normalizers"; import { SidebarBase } from "dxBaseElements/sidebarBase"; import { escapeRegExp } from "dxUtils/regexps"; const WAIT_TIME = 500; const MOBILE_SIZE_MATCH = "768px"; export default class Sidebar extends SidebarBase { @api header: string = ""; @api get trees() { return this._trees; } set trees(value: string | Array) { let parsedValue = toJson(value); if (parsedValue && !Array.isArray(parsedValue)) { parsedValue = [parsedValue]; } this._trees = parsedValue || []; this._trees.forEach((tree) => this.assignValueToLabel(tree)); this.assignFilteredTrees(); } @api get emptyStateMessage(): string | undefined { return this._emptyStateMessage; } set emptyStateMessage(value: string) { this._emptyStateMessage = value; } private _searchTimeout?: number; private _trees!: Array; private filteredTrees: Array<{ key: string; tree: TreeNode }> = []; private searchText: string = ""; private matchMedia!: MediaQueryList; private valueToLabel: { [key: string]: string } = {}; private _emptyStateMessage?: string; private get anyResultMatch() { return this.filteredTrees.length > 0; } private get containerClass() { return cx("container", { collapsed: !this.expanded, "container-mobile": this.mobile }); } private get headerMobile() { return ( (this.value && this.valueToLabel[this.value]) || Object.values(this.valueToLabel)[0] || "" ); } private get menuSymbol() { return this.expanded ? "close" : "custom-rows"; } private get emptyStateBody(): string { return "Search Tips:"; } private get emptyStateSuggestions(): string { return ( this.emptyStateMessage || JSON.stringify([ "Please consider misspellings", "Try different search keywords" ]) ); } private get isDesktop(): boolean { return !this.mobile; } connectedCallback() { this.matchMedia = window.matchMedia( `(max-width: ${MOBILE_SIZE_MATCH})` ); this.expanded = !this.matchMedia.matches; this.onMediaChange(this.matchMedia); this.matchMedia.addEventListener("change", this.onMediaChange); } renderedCallback() { super.renderedCallback(); } disconnectedCallback() { super.disconnectedCallback(); this.matchMedia.removeEventListener("change", this.onMediaChange); } /* * Debouncing this method: Do not apply searchText filter as long as this * function is being called to avoid running the recursivity logic very often. **/ private onSearchChange(event: CustomEvent) { this.searchText = event.detail; window.clearTimeout(this._searchTimeout); this._searchTimeout = window.setTimeout(() => { this.assignFilteredTrees(); this.dispatchEvent( new CustomEvent("highlightedtermchange", { detail: this.searchText, composed: true, bubbles: true }) ); }, WAIT_TIME); } private assignFilteredTrees() { this.filteredTrees = this._trees .map((singleTree) => { return { key: this.makeKey(), tree: this.searchText ? this.findMatch( new RegExp( escapeRegExp( this.searchText.toLowerCase().trim() ) ), singleTree )! : singleTree }; }) .filter((filteredTree) => filteredTree.tree); } private makeKey(): string { return Math.random().toString(36).substring(7); } private findMatch( valuePattern: RegExp, node: TreeNode ): TreeNode | undefined { const isExpanded = node.isChildrenLoading ? false : true; if (valuePattern.test(node.label.toLowerCase())) { return { ...node, isExpanded }; } const filteredChildren = this.filterChildren( valuePattern, node.children ); return filteredChildren.length > 0 ? { ...node, children: filteredChildren, isExpanded } : undefined; } private filterChildren( valuePattern: RegExp, children?: Array ): Array { return !children || children.length === 0 ? [] : (children .map((child) => this.findMatch(valuePattern, child)) .filter((child) => child) as Array); } private assignValueToLabel(node: TreeNode): void { this.valueToLabel[node.name] = node.label; if (node.children && node.children.length > 0) { node.children.forEach((child) => this.assignValueToLabel(child)); } } }