import { LightningElement, api } from "lwc"; import cx from "classnames"; import { Breadcrumb, OptionWithNested } from "typings/custom"; import { toJson } from "dxUtils/normalizers"; type LabelMap = { [href: string]: string }; type NormalizedBreadcrumb = Breadcrumb & { last: boolean; asLink: boolean }; const toLabel = (val: string) => { const noFileExt = val.replace(/\..+$/, ""); const spaces = noFileExt.replace(/-/g, " "); const titlecase = spaces.replace( /\w\S*/g, (txt) => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase() ); return titlecase; }; export default class Breadcrumbs extends LightningElement { @api ariaLabel: string = "Navigation Breadcrumbs"; @api baseHref: string = "/"; @api navItems: OptionWithNested[] = []; @api pathname: string = window.location.pathname; @api header: string = "Salesforce"; @api logo: boolean = false; @api truncate = false; @api hideCurrentLocation = false; @api get breadcrumbs(): NormalizedBreadcrumb[] { const breadcrumbs = this._breadcrumbs || this.autoBreadcrumbs; return this.normalizeBreadcrumbs( this.hideCurrentLocation && breadcrumbs.length > 1 ? breadcrumbs.slice(0, breadcrumbs.length - 1) : breadcrumbs ); } set breadcrumbs(value) { this._breadcrumbs = toJson(value); } private _breadcrumbs!: Breadcrumb[]; private get labelMap(): LabelMap { const deepReducer = (options: OptionWithNested[]) => options.reduce( (acc: LabelMap, item: OptionWithNested): LabelMap => { if (item.options) { return { ...acc, ...deepReducer(item.options) }; } if (!item.link) { return acc; } return { ...acc, [item.link.href]: item.label }; }, {} ); return this.navItems ? deepReducer(this.navItems) : {}; } private get autoBreadcrumbs() { if (!this.pathname || this.pathname === "/") { return []; } // assumes url starts with "/", which it always should const pieces = this.pathname.slice(1).split("/"); // @ts-ignore return pieces.reduce((acc, val, i) => { const href = `/${pieces.slice(0, i + 1).join("/")}`; const label = this.labelMap[href] || toLabel(val); return [ ...acc, { href, label } ]; }, []) as { href: string; label: string }[]; } private normalizeBreadcrumbs( breadcrumbs: Breadcrumb[] ): NormalizedBreadcrumb[] { const { length } = breadcrumbs; return breadcrumbs.map((breadcrumb, i): NormalizedBreadcrumb => { const last = i === length - 1; return { ...breadcrumb, asLink: this.hideCurrentLocation || !last, last }; }); } private get breadcrumbDropdownOptions() { if (this.breadcrumbs.length < 2) { return null; } return this.breadcrumbs .slice(0, this.breadcrumbs.length - 1) .map((link) => ({ id: link.href, label: link.label, link: { href: link.href } })); } private get breadcrumbsEmpty(): boolean { return this.breadcrumbs.length === 0; } private get className(): string { return cx( this.truncate && this.breadcrumbs.length > 3 && "state-long", this.hideCurrentLocation && "hide-current-location" ); } }