import cx from "classnames"; import { api, track } from "lwc"; import { TreeNode, SidebarSearchResult } from "typings/custom"; import { getSidebarSearchParams } from "dxUtils/coveo"; import { toJson } from "dxUtils/normalizers"; import SidebarSearch from "dx/sidebarSearch"; import { SidebarBase } from "dxBaseElements/sidebarBase"; const MOBILE_SIZE_MATCH = "768px"; export default class Sidebar extends SidebarBase { @api coveoOrganizationId!: string; @api coveoPublicAccessToken!: string; @api coveoSearchHub!: string; @api coveoAdvancedQueryConfig!: string; @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]; } parsedValue.forEach((tree: TreeNode) => this.assignValueToLabel(tree)); this._trees = parsedValue.map((tree: TreeNode) => ({ key: this.makeKey(), tree })); } @api setInputValue(searchTerm: string) { ( this.template.querySelector( "dx-sidebar-search" ) as unknown as SidebarSearch )?.setInputValue(searchTerm); } @track private _trees!: Array; private matchMedia!: MediaQueryList; private valueToLabel: { [key: string]: string } = {}; private isSearchLoading: boolean = false; private searchValue: string | null = null; private searchResults: SidebarSearchResult[] = []; private scrollToSelectedSearchResult: boolean = false; private selectedSearchResultIndex: number = -1; private requestedFetchMoreResults: boolean = false; private get areResultsEmpty(): boolean { return ( (!this.searchResults || (this.searchResults && this.searchResults.length < 1)) && !this.isSearchLoading ); } private get isSearchEmpty(): boolean { return this.isUserSearching && this.areResultsEmpty; } private get showResultsHeading(): boolean { return this.isUserSearching && !this.areResultsEmpty; } private get isUserSearching(): boolean { return !!this.searchValue || this.isSearchLoading; } private get showSearchResults(): boolean { return ( !!this.searchValue && !this.isSearchLoading && !this.areResultsEmpty ); } private get emptyStateBody(): string { return "Search Tips:"; } private get emptyStateSuggestions(): string { return JSON.stringify([ "Please consider misspellings", "Try different search keywords" ]); } private get isNearBottomOfSearchResults(): boolean { const scrollArea = this.template.querySelector( ".sidebar-content-search" ); if (!scrollArea) { return false; } const { scrollHeight, scrollTop } = scrollArea; const { height } = scrollArea?.getBoundingClientRect(); return scrollHeight - scrollTop - height < 300; } private get containerClass() { return cx("container", { collapsed: !this.expanded, "container-mobile": this.mobile, "user-searching": this.isUserSearching, "show-tree": !this.isUserSearching, "search-empty": this.isSearchEmpty }); } private get headerMobile() { return ( (this.value && this.valueToLabel[this.value]) || Object.values(this.valueToLabel)[0] || "" ); } private get menuSymbol() { return this.expanded ? "close" : "custom-rows"; } constructor() { super(); if (getSidebarSearchParams()) { this.isSearchLoading = true; this.scrollToSelectedSearchResult = true; } } renderedCallback() { super.renderedCallback(); this.initializeSearchScrollPosition(); } 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); } disconnectedCallback() { super.disconnectedCallback(); this.matchMedia.removeEventListener("change", this.onMediaChange); } private makeKey(): string { return Math.random().toString(36).substring(7); } private onSearchChange(e: CustomEvent) { this.requestedFetchMoreResults = false; this.searchResults = e.detail.results; this.searchValue = e.detail.value; } private onSearchLoading(e: CustomEvent) { if (!e.detail.loading && this.scrollToSelectedSearchResult) { this.selectedSearchResultIndex = this.searchResults.findIndex( (r) => r.selected ); } this.isSearchLoading = e.detail; } private onSidebarSearchContentScroll(scrollEvent) { if (this.isSearchLoading) { return; } const search = this.template.querySelector( "dx-sidebar-search" ) as unknown as SidebarSearch; if ( this.isNearBottomOfSearchResults && search && !this.requestedFetchMoreResults ) { this.requestedFetchMoreResults = true; search.fetchMoreResults(); } this.handleScroll(scrollEvent); } private initializeSearchScrollPosition() { if ( this.scrollToSelectedSearchResult && this.selectedSearchResultIndex >= 0 ) { const selectedResult = this.template.querySelector( `dx-sidebar-search-result:nth-child(${ this.selectedSearchResultIndex + 1 })` ); if (!selectedResult || !selectedResult.parentNode) { return; } (selectedResult.parentNode as HTMLElement).scrollTop = selectedResult.offsetTop - (selectedResult.parentNode as HTMLElement).offsetTop; this.scrollToSelectedSearchResult = false; } } 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)); } } private onItemsFocused(): void { ( this.template.querySelector("dx-sidebar-search") as any )?.requestOpenDropdown(false); } }