@salla.sa/twilight-components
Version:
Salla Web Component
230 lines (225 loc) • 10.5 kB
JavaScript
/*!
* Crafted with ❤ by Salla
*/
import { proxyCustomElement, HTMLElement, h } from '@stencil/core/internal/client';
import { K as KeyBoardArrowLeftIcon, a as KeyBoardArrowRightIcon } from './keyboard_arrow_right.js';
const sallaBreadcrumbCss = ":host{display:block}";
const SallaBreadcrumb$1 = /*@__PURE__*/ proxyCustomElement(class SallaBreadcrumb extends HTMLElement {
constructor() {
super();
this.__registerHost();
this.sessionStorageKey = "breadcrumb_snapshot";
this.itemSlot = this.host.querySelector('[slot="item"]')?.outerHTML || `<li class="s-breadcrumb-item"><a href={url}>{title}</a></li>`;
this.iconSlot = this.host.querySelector('[slot="icon"]')?.outerHTML;
}
componentWillLoad() {
return (new Promise(resolve => salla.onReady(() => salla.lang.onLoaded(resolve))))
.then(() => {
if (salla.url.is_page('index')) {
throw new Error('salla-breadcrumb:: breadcrumb not supported on home page');
}
})
.then(() => {
if (!salla.config.get('theme.settings.is_breadcrumbs_enabled', true)) {
throw new Error('salla-breadcrumb:: merchant disabled the feature');
}
})
.then(() => {
const page = salla.config.get("page");
if (!page || !page.slug) {
salla.logger.error('salla-breadcrumbs:: page object not existed on salla.config.get("page")!');
this.breadcrumbs = [];
return;
}
let sessionBreadcrumbs = this.getSessionBreadcrumbs();
let lastSessionItem = sessionBreadcrumbs[sessionBreadcrumbs.length - 1];
let isParentCompatible = lastSessionItem?.url && page.parent?.url && lastSessionItem.url === page.parent.url;
if (page.slug === "product.single" && sessionBreadcrumbs && sessionBreadcrumbs.length > 0 && isParentCompatible) {
sessionBreadcrumbs.push({
title: page.title,
url: page.url
});
this.breadcrumbs = this.setBreadcrumbsFromArray(sessionBreadcrumbs);
this.storeBreadcrumbSnapshot();
}
else if (sessionBreadcrumbs && sessionBreadcrumbs.length > 0) {
if (this.isNewPage(page, sessionBreadcrumbs)) {
this.breadcrumbs = this.generateBreadcrumbs(page);
this.storeBreadcrumbSnapshot();
}
else {
this.breadcrumbs = this.setBreadcrumbsFromArray(sessionBreadcrumbs);
}
}
else {
this.breadcrumbs = this.generateBreadcrumbs(page);
this.storeBreadcrumbSnapshot();
}
if (this.breadcrumbs?.length) {
this.breadcrumbs[this.breadcrumbs.length - 1].is_last = true;
}
return this.breadcrumbs;
})
.catch((error) => {
salla.logger.error('salla-breadcrumb:: unexpected error!', error);
this.breadcrumbs = [];
});
}
getSessionBreadcrumbs() {
if (new URLSearchParams(window.location.search).get('from') === 'search-bar') {
return [];
}
return JSON.parse(sessionStorage.getItem(this.sessionStorageKey) || '[]');
}
/**
* Helper function to determine if we're navigating to a new page that requires updating the session storage.
*/
isNewPage(page, sessionBreadcrumbs) {
// Check if the last breadcrumb in sessionStorage matches the current page's URL.
const lastBreadcrumb = sessionBreadcrumbs[sessionBreadcrumbs.length - 1];
return lastBreadcrumb?.url !== page.url; // If the URLs don't match, it's a new page.
}
setBreadcrumbsFromArray(breadcrumbArray) {
return breadcrumbArray.map((item, index) => ({
...item,
is_last: index === breadcrumbArray.length - 1,
}));
}
/**
* Sanitizes the breadcrumb title by splitting it on the `|` character and returning
* the part based on `preferedIndex`. If no separator is found, returns the trimmed title.
*
* @param {string} title - The title to sanitize.
* @param {number} [preferedIndex=1] - Index of the part to return (0 for first, 1 for second).
* @returns {string} - The sanitized title.
*/
sanitizeBreadcrumbTitle(title, preferedIndex = 1) {
if (!title.includes('|')) {
return title.trim();
}
return title.split('|').map(part => part.trim())[preferedIndex];
}
generateBreadcrumbs(page) {
let breadcrumbs = [];
// Same parent-compatibility as main path: only reuse session path when last item matches page.parent
if (page.slug === 'product.single') {
const previousPage = this.getSessionBreadcrumbs();
const lastSessionItem = previousPage[previousPage.length - 1];
const isParentCompatible = lastSessionItem?.url && page.parent?.url && lastSessionItem.url === page.parent.url;
if (previousPage.length > 0 && isParentCompatible) {
return this.setBreadcrumbsFromArray([...previousPage, { title: page.title, url: page.url }]);
}
}
// Start with the current page
let currentPage = page;
// Traverse up to the parent pages
while (currentPage) {
if (currentPage.title) {
breadcrumbs.unshift({
title: currentPage.title,
url: currentPage.url,
});
}
currentPage = currentPage.parent;
}
// Additional logic based on page slug or title
if (page.slug.includes("customer") && page.slug !== 'customer.profile') {
breadcrumbs.unshift({ title: salla.lang.get('common.titles.profile'), url: salla.url.get('profile') });
}
if (page.slug.includes('blog')) {
breadcrumbs.unshift({ title: salla.lang.get('blocks.footer.blog'), url: salla.url.get('blog') });
}
if (page.slug === 'brands.single') {
breadcrumbs.unshift({ title: salla.lang.get('common.titles.brands'), url: salla.url.get('brands') });
}
if (!page.title && page.slug === 'loyalty') {
breadcrumbs.unshift({ title: salla.lang.get('common.titles.loyalty_program'), url: salla.url.get('loyalty') });
}
// Add home breadcrumb
breadcrumbs.unshift({ title: salla.lang.get('common.titles.home'), url: salla.url.get('') });
return breadcrumbs;
}
storeBreadcrumbSnapshot() {
try {
const page = salla.config.get("page");
// Skip storing breadcrumbs for product.single page
if (page?.slug === 'product.single') {
return;
}
const items = [...this.breadcrumbs];
// Find the last item and update its URL
const lastItemIndex = items.length - 1;
if (lastItemIndex >= 0) {
items[lastItemIndex].url = window.location.href;
}
const breadcrumbSnapshot = JSON.stringify(items);
sessionStorage.setItem(this.sessionStorageKey, breadcrumbSnapshot);
}
catch (error) {
salla.logger.error('salla-breadcrumb:: Failed to store breadcrumb snapshot in sessionStorage.', error);
}
}
render() {
if (this.breadcrumbs.length <= 1) {
salla.log('salla-breadcrumb:: There is no breadcrumbs!');
return null;
}
return (h("ol", { class: {
"s-breadcrumb-wrapper": true,
"s-breadcrumb-dark": salla.url.is_page('loyalty'),
"s-breadcrumb-primary-reverse": salla.config.get('page.slug').includes('customer')
} }, this.breadcrumbs.map(item => {
const isProductSingle = salla.config.get('page.slug') === "product.single";
const title = (isProductSingle && item.is_last) ? item.title : this.sanitizeBreadcrumbTitle(item.title);
const itemHTML = this.itemSlot.replace(/\{url\}/g, item.url).replace(/\{title\}/g, title);
return [
h("div", { class: "s-breadcrumb-slot", innerHTML: itemHTML }),
this.getArrowDomForItem(item)
];
})));
}
getArrowDomForItem(item) {
if (item.is_last) {
return '';
}
let iconDom = this.iconSlot || (salla.config.get('theme.is_rtl', true) ? KeyBoardArrowLeftIcon : KeyBoardArrowRightIcon);
return h("li", { class: "s-breadcrumb-arrow" }, h("div", { class: { "s-breadcrumb-icon-slot": true, "s-breadcrumb-default-icon": !this.iconSlot }, innerHTML: iconDom }));
}
/**
* Lifecycle method called after the component is rendered.
* - Reduces the number of elements in the DOM.
* - Removes unnecessary slots parent elements.
* - Replaces the last anchor tag in the breadcrumb with its content.
*/
componentDidRender() {
// Reduces the number of elements in the DOM
this.host.querySelectorAll('.s-breadcrumb-slot').forEach(el => el.replaceWith(el.firstChild));
this.host.querySelectorAll('.s-breadcrumb-icon-slot.s-breadcrumb-default-icon').forEach(el => el.replaceWith(el.querySelector('svg')));
// Removes the slots parent elements if exists
this.host.querySelector('[slot="item"]')?.remove();
this.host.querySelector('[slot="icon"]')?.remove();
let lastEl = this.host.querySelectorAll('.s-breadcrumb-item')[this.breadcrumbs.length - 1]?.querySelector('a');
lastEl && lastEl.replaceWith(lastEl.firstChild);
}
get host() { return this; }
static get style() { return sallaBreadcrumbCss; }
}, [0, "salla-breadcrumb", {
"breadcrumbs": [32]
}]);
function defineCustomElement$1() {
if (typeof customElements === "undefined") {
return;
}
const components = ["salla-breadcrumb"];
components.forEach(tagName => { switch (tagName) {
case "salla-breadcrumb":
if (!customElements.get(tagName)) {
customElements.define(tagName, SallaBreadcrumb$1);
}
break;
} });
}
defineCustomElement$1();
const SallaBreadcrumb = SallaBreadcrumb$1;
const defineCustomElement = defineCustomElement$1;
export { SallaBreadcrumb, defineCustomElement };