import { LightningElement, api } from "lwc"; import type { OptionWithLink } from "typings/custom"; import { toJson } from "dxUtils/normalizers"; import { Brand, DevCenterConfig } from "typings/custom"; import { handleScroll } from "dxUtils/handleScroll"; export const HEIGHT_OF_SIDEBAR_ITEM = 32; export const WAIT_TIME_BEFORE_SCROLL_IN_MS = 500; export class SidebarBase extends LightningElement { _sidebarContent: HTMLElement | null = null; handleScroll = handleScroll.bind(this); selectedElement: HTMLElement | null = null; timerId: ReturnType | null = null; _languages!: OptionWithLink[]; mobile: boolean = true; expanded: boolean = true; _value?: string = undefined; showBoxShadow: boolean = false; private scrolling: boolean = false; _devCenter!: DevCenterConfig; @api langValuePath: string = "id"; @api language!: string; @api bailHref!: string; @api bailLabel!: string; @api brand: Brand | null = null; @api get devCenter(): DevCenterConfig { return this._devCenter; } set devCenter(value) { this._devCenter = toJson(value); } @api get languages() { return this._languages; } set languages(value) { this._languages = toJson(value); } @api get value() { return this._value; } set value(value) { this._value = value; } get sidebarContent() { if (!this._sidebarContent) { this._sidebarContent = this.template.querySelector( ".sidebar-content-tree" ); } return this._sidebarContent; } renderedCallback(): void { /** * Implementing debouncing kind of logic here to scroll to selected element once tree rendering is done */ if (this.timerId) { clearTimeout(this.timerId); } this.timerId = setTimeout( this.renderedCallbackWithTimeout, WAIT_TIME_BEFORE_SCROLL_IN_MS ); } disconnectedCallback(): void { if (this.timerId) { clearTimeout(this.timerId); } } renderedCallbackWithTimeout = () => { this.scrollToSelectedItem(); }; private onSelectedItemRendered = (event: CustomEvent) => { event.stopPropagation(); this.selectedElement = event.detail.element; }; /*Scroll to selected tree item */ private scrollToSelectedItem() { if (this.selectedElement && this.sidebarContent) { const scrollHeight: number = this.selectedElement.getBoundingClientRect().bottom - this.sidebarContent.getBoundingClientRect().bottom + // We need to remove parent bottom HEIGHT_OF_SIDEBAR_ITEM * 3 + // add height of 3 more items so that user can see at least three more items after selected item this.sidebarContent.offsetTop; // We need to add sidebar offsetTop value as scrollTo will consider it this.sidebarContent.scrollTo({ top: scrollHeight, behavior: "smooth" }); this.selectedElement = null; } } onMediaChange = (event: MediaQueryListEvent | MediaQueryList) => { this.mobile = event.matches; this.expanded = !this.mobile; }; onSelect(event: CustomEvent) { this._value = event.detail.name; if (this.mobile) { this.onToggleClick(); } } onToggleClick() { this.expanded = !this.expanded; this.dispatchEvent( new CustomEvent("togglesidebar", { detail: { open: this.expanded, bubbles: true, composed: true } }) ); } private get hasMobileSidebarFooter(): boolean { return this.mobile && this.languages?.length > 1 && this.expanded; } private get hasSidebarFooter(): boolean { return this.languages?.length > 1 || Boolean(this.bailHref); } private get sidebarContentClass(): string { const sidebarConterCss = "sidebar-content sidebar-content-tree"; return this.hasSidebarFooter || this.hasMobileSidebarFooter ? sidebarConterCss + " sidebar-content-hasfooter" : sidebarConterCss; } private get isDesktop(): boolean { return !this.mobile; } }