317 lines
9.6 KiB
JavaScript
317 lines
9.6 KiB
JavaScript
import {
|
||
hydrateCart,
|
||
subscribe,
|
||
getSnapshot,
|
||
formatPrice,
|
||
} from "/js/cart.js";
|
||
|
||
function qs(sel, root = document) {
|
||
return root.querySelector(sel);
|
||
}
|
||
|
||
function qsa(sel, root = document) {
|
||
return [...root.querySelectorAll(sel)];
|
||
}
|
||
|
||
function escapeHtml(s) {
|
||
return String(s)
|
||
.replaceAll("&", "&")
|
||
.replaceAll("<", "<")
|
||
.replaceAll(">", ">")
|
||
.replaceAll('"', """);
|
||
}
|
||
|
||
export function initHeader() {
|
||
hydrateCart();
|
||
|
||
const header = qs(".site-header");
|
||
const mega = qs(".mega-panel");
|
||
const backdrop = qs(".mega-backdrop");
|
||
const drawer = qs(".mobile-drawer");
|
||
const badge = qs("[data-cart-count]");
|
||
const cartMenu = qs("[data-cart-menu]");
|
||
const cartTrigger = qs("[data-cart-trigger]");
|
||
const cartPreview = qs("[data-cart-preview]");
|
||
const cartList = qs("[data-cart-preview-list]");
|
||
const cartEmpty = qs("[data-cart-preview-empty]");
|
||
const cartFoot = qs("[data-cart-preview-foot]");
|
||
const cartTotal = qs("[data-cart-preview-total]");
|
||
|
||
const fineHover =
|
||
typeof window.matchMedia === "function" &&
|
||
window.matchMedia("(hover: hover) and (pointer: fine)").matches;
|
||
|
||
function syncBadge(snap) {
|
||
if (!badge) return;
|
||
badge.textContent = String(snap.itemCount);
|
||
badge.classList.toggle("is-visible", snap.hydrated && snap.itemCount > 0);
|
||
}
|
||
|
||
function renderCartPreview(snap) {
|
||
if (!cartList || !cartEmpty || !cartFoot || !cartTotal) return;
|
||
const empty = !snap.items.length;
|
||
cartEmpty.hidden = !empty;
|
||
cartFoot.hidden = empty;
|
||
cartList.hidden = empty;
|
||
cartTotal.textContent = formatPrice(snap.subtotal);
|
||
|
||
if (empty) {
|
||
cartList.innerHTML = "";
|
||
return;
|
||
}
|
||
|
||
cartList.innerHTML = snap.items
|
||
.slice(0, 8)
|
||
.map((item) => {
|
||
const href = `/products/${escapeHtml(item.slug)}/`;
|
||
const img = item.image
|
||
? `<img src="${escapeHtml(item.image)}" alt="" width="52" height="66" loading="lazy" />`
|
||
: `<div class="cart-preview-ph" aria-hidden="true"></div>`;
|
||
return `<a class="cart-preview-line" href="${href}">
|
||
${img}
|
||
<div class="cart-preview-meta">
|
||
<p class="cart-preview-name">${escapeHtml(item.name)}</p>
|
||
<p class="cart-preview-qty">× ${item.quantity.toLocaleString("fa-IR")}</p>
|
||
</div>
|
||
<p class="cart-preview-price">${formatPrice(item.price * item.quantity)}</p>
|
||
</a>`;
|
||
})
|
||
.join("");
|
||
}
|
||
|
||
function setCartPreviewOpen(open) {
|
||
if (!cartMenu || !cartPreview || !cartTrigger) return;
|
||
cartMenu.classList.toggle("is-open", open);
|
||
cartPreview.classList.toggle("is-open", open);
|
||
if (open) cartPreview.removeAttribute("hidden");
|
||
else cartPreview.setAttribute("hidden", "");
|
||
cartTrigger.setAttribute("aria-expanded", open ? "true" : "false");
|
||
}
|
||
|
||
function closeCartPreview() {
|
||
setCartPreviewOpen(false);
|
||
}
|
||
|
||
function openCartPreview() {
|
||
setCartPreviewOpen(true);
|
||
}
|
||
|
||
const sideNav = qs(".side-nav");
|
||
const sideNavToggle = qs("[data-side-nav-toggle]");
|
||
const SIDE_NAV_KEY = "kikomiko-side-nav-collapsed";
|
||
|
||
function setSideNavCollapsed(collapsed) {
|
||
if (!sideNav || !sideNavToggle) return;
|
||
sideNav.classList.toggle("is-collapsed", collapsed);
|
||
sideNavToggle.setAttribute("aria-expanded", collapsed ? "false" : "true");
|
||
const label = collapsed ? "نمایش منو" : "پنهان کردن منو";
|
||
sideNavToggle.setAttribute("aria-label", label);
|
||
sideNavToggle.setAttribute("title", label);
|
||
try {
|
||
localStorage.setItem(SIDE_NAV_KEY, collapsed ? "1" : "0");
|
||
} catch {
|
||
/* ignore */
|
||
}
|
||
if (collapsed) closeMega();
|
||
}
|
||
|
||
function hideDesktopMenu() {
|
||
closeMega();
|
||
setSideNavCollapsed(true);
|
||
}
|
||
|
||
subscribe((snap) => {
|
||
syncBadge(snap);
|
||
renderCartPreview(snap);
|
||
});
|
||
const initial = getSnapshot();
|
||
syncBadge(initial);
|
||
renderCartPreview(initial);
|
||
|
||
if (cartMenu && cartTrigger && cartPreview) {
|
||
cartTrigger.addEventListener("click", (e) => {
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
const open = cartMenu.classList.contains("is-open");
|
||
if (open) closeCartPreview();
|
||
else {
|
||
closeMega();
|
||
openCartPreview();
|
||
}
|
||
});
|
||
|
||
if (fineHover) {
|
||
let leaveTimer = 0;
|
||
cartMenu.addEventListener("mouseenter", () => {
|
||
window.clearTimeout(leaveTimer);
|
||
openCartPreview();
|
||
});
|
||
cartMenu.addEventListener("mouseleave", () => {
|
||
leaveTimer = window.setTimeout(closeCartPreview, 180);
|
||
});
|
||
}
|
||
|
||
document.addEventListener("click", (e) => {
|
||
if (!(e.target instanceof Element)) return;
|
||
if (!cartMenu.contains(e.target)) closeCartPreview();
|
||
});
|
||
|
||
document.addEventListener("keydown", (e) => {
|
||
if (e.key === "Escape") closeCartPreview();
|
||
});
|
||
}
|
||
|
||
function closeMega() {
|
||
mega?.classList.remove("is-open");
|
||
mega?.removeAttribute("data-active");
|
||
backdrop?.classList.remove("is-open");
|
||
backdrop?.setAttribute("hidden", "");
|
||
header?.classList.remove("is-open");
|
||
qsa("[data-mega-key]").forEach((b) => b.classList.remove("is-active"));
|
||
qsa("[data-mega-pane]").forEach((pane) => {
|
||
pane.hidden = true;
|
||
});
|
||
// Restore home highlight when on home
|
||
const home = qs('.side-nav-link[href="/"]');
|
||
if (home && (location.pathname === "/" || location.pathname === "/fa")) {
|
||
home.classList.add("is-active");
|
||
}
|
||
}
|
||
|
||
function openMega(key) {
|
||
if (!mega) return;
|
||
closeCartPreview();
|
||
// Keep the side panel open while browsing mega categories
|
||
if (sideNav?.classList.contains("is-collapsed")) {
|
||
setSideNavCollapsed(false);
|
||
}
|
||
mega.setAttribute("data-active", key);
|
||
mega.classList.add("is-open");
|
||
header?.classList.add("is-open");
|
||
backdrop?.classList.add("is-open");
|
||
backdrop?.removeAttribute("hidden");
|
||
qsa("[data-mega-pane]").forEach((pane) => {
|
||
pane.hidden = pane.getAttribute("data-mega-pane") !== key;
|
||
});
|
||
qsa(".side-nav-link").forEach((b) => b.classList.remove("is-active"));
|
||
qsa("[data-mega-key]").forEach((b) =>
|
||
b.classList.toggle("is-active", b.getAttribute("data-mega-key") === key)
|
||
);
|
||
}
|
||
|
||
qsa("[data-mega-key]").forEach((btn) => {
|
||
btn.addEventListener("click", (e) => {
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
const key = btn.getAttribute("data-mega-key");
|
||
if (!key) return;
|
||
const open = mega?.getAttribute("data-active") === key;
|
||
if (open) {
|
||
closeMega();
|
||
return;
|
||
}
|
||
openMega(key);
|
||
});
|
||
});
|
||
|
||
// Close mega immediately when any link inside it is chosen
|
||
// (categories, view-all, featured products, collection title).
|
||
mega?.addEventListener(
|
||
"click",
|
||
(e) => {
|
||
const link = e.target instanceof Element ? e.target.closest("a[href]") : null;
|
||
if (!link || !mega.contains(link)) return;
|
||
hideDesktopMenu();
|
||
},
|
||
true
|
||
);
|
||
|
||
// Home / offers links in side nav should close mega + collapse panel
|
||
qsa(".side-nav-link[href]").forEach((link) => {
|
||
link.addEventListener("click", () => hideDesktopMenu());
|
||
});
|
||
|
||
// Highlight active offers item
|
||
if (location.pathname.startsWith("/offers")) {
|
||
qsa(".side-nav-link").forEach((el) => el.classList.remove("is-active"));
|
||
qs(
|
||
'.side-nav-link[href="/offers/"], .side-nav-link[href="/offers"]'
|
||
)?.classList.add("is-active");
|
||
}
|
||
|
||
// Do not use mouseleave — it fights with clicks on mega links.
|
||
backdrop?.addEventListener("click", closeMega);
|
||
qs("[data-mega-close]")?.addEventListener("click", closeMega);
|
||
|
||
function closeDrawer(e) {
|
||
e?.preventDefault?.();
|
||
e?.stopPropagation?.();
|
||
drawer?.classList.remove("is-open");
|
||
drawer?.setAttribute("aria-hidden", "true");
|
||
document.body.style.overflow = "";
|
||
qsa("[data-mobile-sub]").forEach((p) => p.classList.remove("is-open"));
|
||
qsa(".mobile-cat").forEach((c) => c.classList.remove("is-expanded"));
|
||
}
|
||
|
||
function openDrawer(e) {
|
||
e?.preventDefault?.();
|
||
drawer?.classList.add("is-open");
|
||
drawer?.setAttribute("aria-hidden", "false");
|
||
document.body.style.overflow = "hidden";
|
||
closeMega();
|
||
closeCartPreview();
|
||
}
|
||
|
||
qs("[data-menu-open]")?.addEventListener("click", openDrawer);
|
||
qsa("[data-menu-close]").forEach((el) => {
|
||
el.addEventListener("click", closeDrawer);
|
||
});
|
||
|
||
qsa("[data-mobile-expand]").forEach((btn) => {
|
||
btn.addEventListener("click", () => {
|
||
const id = btn.getAttribute("data-mobile-expand");
|
||
const panel = qs(`[data-mobile-sub="${id}"]`);
|
||
const cat = btn.closest(".mobile-cat");
|
||
const open = panel?.classList.contains("is-open");
|
||
qsa("[data-mobile-sub]").forEach((p) => p.classList.remove("is-open"));
|
||
qsa(".mobile-cat").forEach((c) => c.classList.remove("is-expanded"));
|
||
if (!open) {
|
||
panel?.classList.add("is-open");
|
||
cat?.classList.add("is-expanded");
|
||
}
|
||
});
|
||
});
|
||
|
||
// Highlight current route in the mobile drawer
|
||
const path = window.location.pathname.replace(/\/+$/, "") || "/";
|
||
qsa(".mobile-nav a[href]").forEach((a) => {
|
||
const href = (a.getAttribute("href") || "").replace(/\/+$/, "") || "/";
|
||
if (href === path || (href !== "/" && path.startsWith(href))) {
|
||
a.setAttribute("aria-current", "page");
|
||
a.classList.add("is-active");
|
||
}
|
||
});
|
||
|
||
try {
|
||
if (localStorage.getItem(SIDE_NAV_KEY) === "1") {
|
||
setSideNavCollapsed(true);
|
||
}
|
||
} catch {
|
||
/* ignore */
|
||
}
|
||
|
||
sideNavToggle?.addEventListener("click", (e) => {
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
setSideNavCollapsed(!sideNav?.classList.contains("is-collapsed"));
|
||
});
|
||
|
||
document.addEventListener("keydown", (e) => {
|
||
if (e.key === "Escape") {
|
||
closeMega();
|
||
closeDrawer();
|
||
closeCartPreview();
|
||
}
|
||
});
|
||
}
|