158 lines
5.8 KiB
JavaScript
158 lines
5.8 KiB
JavaScript
/**
|
|
* Standard Actions configuration for Dawn.
|
|
*
|
|
* Storefront Renderer injects the Shopify Standard Actions bundle
|
|
* (`window.Shopify.actions.{updateCart,openCart,getCart,…}`). This file
|
|
* overrides the bundle's built-in Dawn refresh path with an explicit,
|
|
* in-theme version so forks that change Dawn's cart contract keep
|
|
* working. Remove this file and the built-in defaults take over.
|
|
*
|
|
* - openCart — opens <cart-drawer>; falls back to /cart.
|
|
* - updateCart — after the Storefront API mutation, refreshes the
|
|
* affected cart sections and publishes `cart-update` so Dawn's
|
|
* pubsub subscribers react.
|
|
* - other actions (getCart, etc.) keep the default implementation.
|
|
*/
|
|
|
|
// Cart custom elements that advertise sections via getSectionsToRender().
|
|
// If Dawn adds a new cart custom element, add its tag here.
|
|
const DAWN_CART_TAGS = ['cart-drawer', 'cart-items', 'cart-drawer-items', 'cart-notification'];
|
|
|
|
// Sections that Dawn's own pubsub subscribers refresh (cart.js's
|
|
// CartItems#onCartUpdate fetches and replaces these directly when
|
|
// cart-update fires; cart-drawer.js's renderContents handles the
|
|
// drawer body). We skip them here to avoid double-rendering.
|
|
// Format is '<element-tag>:<getSectionsToRender entry id>'.
|
|
// If you change which sections those subscribers refresh, update this set.
|
|
const DAWN_PUBSUB_REFRESHED_SECTIONS = new Set([
|
|
'cart-drawer:cart-drawer',
|
|
'cart-drawer-items:CartDrawer',
|
|
'cart-items:main-cart-items',
|
|
]);
|
|
|
|
// Walk every mounted Dawn cart custom element, collect the sections it
|
|
// wants rendered, and dedupe. Returns a Map keyed by section id, each
|
|
// entry pointing at the DOM mount and the selector used to extract the
|
|
// fresh fragment.
|
|
function collectCartSections() {
|
|
const sections = new Map();
|
|
|
|
for (const el of document.querySelectorAll(DAWN_CART_TAGS.join(','))) {
|
|
let entries;
|
|
try {
|
|
entries = el.getSectionsToRender?.();
|
|
} catch {
|
|
continue;
|
|
}
|
|
|
|
const tag = el.tagName.toLowerCase();
|
|
for (const entry of entries ?? []) {
|
|
if (DAWN_PUBSUB_REFRESHED_SECTIONS.has(`${tag}:${entry.id}`)) continue;
|
|
|
|
const sectionId = entry.section ?? entry.id;
|
|
if (!sectionId || sections.has(sectionId)) continue;
|
|
|
|
// Two patterns coexist in getSectionsToRender():
|
|
// - cart-items style: entry.section is the parent Liquid section
|
|
// id, entry.selector is a child node inside it.
|
|
// - cart-drawer / cart-notification style: entry.id IS the mount;
|
|
// there is no parent wrapper.
|
|
const root = entry.section ? document.getElementById(entry.id) : document;
|
|
if (!root) continue;
|
|
|
|
const mount = entry.selector
|
|
? (root.querySelector(entry.selector) ?? (entry.section ? root : null))
|
|
: document.getElementById(entry.id);
|
|
if (!mount) continue;
|
|
|
|
sections.set(sectionId, {
|
|
mount,
|
|
extractSelector: entry.selector || '.shopify-section',
|
|
});
|
|
}
|
|
}
|
|
|
|
return sections;
|
|
}
|
|
|
|
// After a Storefront API mutation, refresh every Dawn cart section
|
|
// that isn't already refreshed by Dawn's own pubsub subscribers, then
|
|
// publish 'cart-update' so the subscribers run.
|
|
//
|
|
// We always fetch /cart.js (with sections= when we have any) so that
|
|
// `cartData` is defined for subscribers. quick-add-bulk.js reads
|
|
// `event.cartData.items` unconditionally — publishing without cartData
|
|
// makes it throw.
|
|
async function refreshDawnCartUI() {
|
|
const sections = collectCartSections();
|
|
const sectionsQuery = sections.size
|
|
? `?sections=${[...sections.keys()].join(',')}`
|
|
: '';
|
|
// `routes` is a Dawn global, but don't assume it's defined.
|
|
const cartUrl = (typeof routes !== 'undefined' && routes?.cart_url) || '/cart';
|
|
const url = `${cartUrl}.js${sectionsQuery}`;
|
|
const cartData = await fetch(url, { headers: { Accept: 'application/json' } })
|
|
.then((r) => (r.ok ? r.json() : null))
|
|
.catch(() => null);
|
|
|
|
if (cartData?.sections) {
|
|
for (const [id, { mount, extractSelector }] of sections) {
|
|
const html = cartData.sections[id];
|
|
if (!html) continue;
|
|
const source = new DOMParser()
|
|
.parseFromString(html, 'text/html')
|
|
.querySelector(extractSelector);
|
|
if (source) mount.replaceChildren(...source.childNodes);
|
|
}
|
|
}
|
|
|
|
// Hand off to Dawn's existing subscribers. cartData is the full
|
|
// Cart Ajax payload (items, item_count, token, …) plus sections;
|
|
// quick-add-bulk.js and price-per-item.js read it directly.
|
|
publish(PUB_SUB_EVENTS.cartUpdate, {
|
|
source: 'external-refresh',
|
|
cartData: cartData ?? undefined,
|
|
});
|
|
}
|
|
|
|
function initStandardActions() {
|
|
const actions = window.Shopify?.actions;
|
|
if (!actions) return;
|
|
|
|
actions.openCart.configure({
|
|
async handler(defaultHandler) {
|
|
const drawer = document.querySelector('cart-drawer');
|
|
if (drawer && typeof drawer.open === 'function') {
|
|
drawer.open();
|
|
return;
|
|
}
|
|
return defaultHandler();
|
|
},
|
|
});
|
|
|
|
actions.updateCart.configure({
|
|
// Dawn doesn't currently listen for shopify:cart:* events, but the
|
|
// bundle requires an eventTarget. document is the conventional root.
|
|
eventTarget: () => document,
|
|
async handler(defaultHandler) {
|
|
const result = await defaultHandler();
|
|
try {
|
|
await refreshDawnCartUI();
|
|
} catch (error) {
|
|
console.error('[Dawn] Standard Actions cart refresh failed; reloading.', error);
|
|
window.location.reload();
|
|
}
|
|
return result;
|
|
},
|
|
});
|
|
}
|
|
|
|
// Run immediately if the standard-actions bundle has already attached
|
|
// `Shopify.actions`; otherwise wait for DOMContentLoaded, which fires after
|
|
// all module scripts have executed regardless of document order.
|
|
if (window.Shopify?.actions) {
|
|
initStandardActions();
|
|
} else {
|
|
document.addEventListener('DOMContentLoaded', initStandardActions, { once: true });
|
|
}
|