@magic-spells/cart-panel 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +381 -190
- package/dist/cart-panel.cjs.css +8 -12
- package/dist/cart-panel.cjs.js +338 -56
- package/dist/cart-panel.cjs.js.map +1 -1
- package/dist/cart-panel.css +8 -12
- package/dist/cart-panel.esm.css +8 -12
- package/dist/cart-panel.esm.js +335 -56
- package/dist/cart-panel.esm.js.map +1 -1
- package/dist/cart-panel.js +1637 -1067
- package/dist/cart-panel.js.map +1 -1
- package/dist/cart-panel.min.css +1 -1
- package/dist/cart-panel.min.js +1 -1
- package/dist/cart-panel.scss +2 -12
- package/package.json +3 -3
- package/src/cart-panel.js +333 -58
- package/src/cart-panel.scss +2 -12
package/dist/cart-panel.esm.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import '@magic-spells/cart-item';
|
|
1
|
+
import { CartItem } from '@magic-spells/cart-item';
|
|
2
|
+
export { CartItem } from '@magic-spells/cart-item';
|
|
2
3
|
import '@magic-spells/focus-trap';
|
|
3
4
|
import EventEmitter from '@magic-spells/event-emitter';
|
|
4
5
|
|
|
@@ -8,9 +9,9 @@ import EventEmitter from '@magic-spells/event-emitter';
|
|
|
8
9
|
*/
|
|
9
10
|
class CartDialog extends HTMLElement {
|
|
10
11
|
#handleTransitionEnd;
|
|
11
|
-
#scrollPosition = 0;
|
|
12
12
|
#currentCart = null;
|
|
13
13
|
#eventEmitter;
|
|
14
|
+
#isInitialRender = true;
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* Clean up event listeners when component is removed from DOM
|
|
@@ -30,31 +31,21 @@ class CartDialog extends HTMLElement {
|
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
/**
|
|
33
|
-
*
|
|
34
|
+
* Locks body scrolling
|
|
34
35
|
* @private
|
|
35
36
|
*/
|
|
36
37
|
#lockScroll() {
|
|
37
|
-
|
|
38
|
-
// Save current scroll position
|
|
39
|
-
_.#scrollPosition = window.pageYOffset;
|
|
40
|
-
|
|
41
|
-
// Apply fixed position to body
|
|
38
|
+
// Apply overflow hidden to body
|
|
42
39
|
document.body.classList.add('overflow-hidden');
|
|
43
|
-
document.body.style.top = `-${_.#scrollPosition}px`;
|
|
44
40
|
}
|
|
45
41
|
|
|
46
42
|
/**
|
|
47
|
-
* Restores
|
|
43
|
+
* Restores body scrolling when cart dialog is closed
|
|
48
44
|
* @private
|
|
49
45
|
*/
|
|
50
46
|
#restoreScroll() {
|
|
51
|
-
|
|
52
|
-
// Remove fixed positioning
|
|
47
|
+
// Remove overflow hidden from body
|
|
53
48
|
document.body.classList.remove('overflow-hidden');
|
|
54
|
-
document.body.style.removeProperty('top');
|
|
55
|
-
|
|
56
|
-
// Restore scroll position
|
|
57
|
-
window.scrollTo(0, _.#scrollPosition);
|
|
58
49
|
}
|
|
59
50
|
|
|
60
51
|
/**
|
|
@@ -95,7 +86,18 @@ class CartDialog extends HTMLElement {
|
|
|
95
86
|
return;
|
|
96
87
|
}
|
|
97
88
|
|
|
98
|
-
|
|
89
|
+
// Check if focus-trap already exists, if not create one
|
|
90
|
+
_.focusTrap = _.contentPanel.querySelector('focus-trap');
|
|
91
|
+
if (!_.focusTrap) {
|
|
92
|
+
_.focusTrap = document.createElement('focus-trap');
|
|
93
|
+
|
|
94
|
+
// Move all existing cart-panel content into the focus trap
|
|
95
|
+
const existingContent = Array.from(_.contentPanel.childNodes);
|
|
96
|
+
existingContent.forEach((child) => _.focusTrap.appendChild(child));
|
|
97
|
+
|
|
98
|
+
// Insert focus trap inside the cart-panel
|
|
99
|
+
_.contentPanel.appendChild(_.focusTrap);
|
|
100
|
+
}
|
|
99
101
|
|
|
100
102
|
// Ensure we have labelledby and describedby references
|
|
101
103
|
if (!_.getAttribute('aria-labelledby')) {
|
|
@@ -108,15 +110,15 @@ class CartDialog extends HTMLElement {
|
|
|
108
110
|
}
|
|
109
111
|
}
|
|
110
112
|
|
|
111
|
-
|
|
112
|
-
_.
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
// Add modal overlay
|
|
117
|
-
_.prepend(document.createElement('cart-overlay'));
|
|
113
|
+
// Add modal overlay if it doesn't already exist
|
|
114
|
+
if (!_.querySelector('cart-overlay')) {
|
|
115
|
+
_.prepend(document.createElement('cart-overlay'));
|
|
116
|
+
}
|
|
118
117
|
_.#attachListeners();
|
|
119
118
|
_.#bindKeyboard();
|
|
119
|
+
|
|
120
|
+
// Load cart data immediately after component initialization
|
|
121
|
+
_.refreshCart();
|
|
120
122
|
}
|
|
121
123
|
|
|
122
124
|
/**
|
|
@@ -149,6 +151,14 @@ class CartDialog extends HTMLElement {
|
|
|
149
151
|
*/
|
|
150
152
|
#emit(eventName, data = null) {
|
|
151
153
|
this.#eventEmitter.emit(eventName, data);
|
|
154
|
+
|
|
155
|
+
// Also emit as native DOM events for better compatibility
|
|
156
|
+
this.dispatchEvent(
|
|
157
|
+
new CustomEvent(eventName, {
|
|
158
|
+
detail: data,
|
|
159
|
+
bubbles: true,
|
|
160
|
+
})
|
|
161
|
+
);
|
|
152
162
|
}
|
|
153
163
|
|
|
154
164
|
/**
|
|
@@ -172,7 +182,7 @@ class CartDialog extends HTMLElement {
|
|
|
172
182
|
|
|
173
183
|
// Handle close buttons
|
|
174
184
|
_.addEventListener('click', (e) => {
|
|
175
|
-
if (!e.target.closest('[data-action
|
|
185
|
+
if (!e.target.closest('[data-action-hide-cart]')) return;
|
|
176
186
|
_.hide();
|
|
177
187
|
});
|
|
178
188
|
|
|
@@ -227,14 +237,15 @@ class CartDialog extends HTMLElement {
|
|
|
227
237
|
this.updateCartItem(cartKey, 0)
|
|
228
238
|
.then((updatedCart) => {
|
|
229
239
|
if (updatedCart && !updatedCart.error) {
|
|
230
|
-
// Success -
|
|
231
|
-
element.destroyYourself();
|
|
240
|
+
// Success - let smart comparison handle the removal animation
|
|
232
241
|
this.#currentCart = updatedCart;
|
|
233
|
-
this.#
|
|
242
|
+
this.#renderCartItems(updatedCart);
|
|
243
|
+
this.#renderCartPanel(updatedCart);
|
|
234
244
|
|
|
235
245
|
// Emit cart updated and data changed events
|
|
236
|
-
this.#
|
|
237
|
-
this.#emit('cart-dialog:
|
|
246
|
+
const cartWithCalculatedFields = this.#addCalculatedFields(updatedCart);
|
|
247
|
+
this.#emit('cart-dialog:updated', { cart: cartWithCalculatedFields });
|
|
248
|
+
this.#emit('cart-dialog:data-changed', cartWithCalculatedFields);
|
|
238
249
|
} else {
|
|
239
250
|
// Error - reset to ready state
|
|
240
251
|
element.setState('ready');
|
|
@@ -262,14 +273,16 @@ class CartDialog extends HTMLElement {
|
|
|
262
273
|
this.updateCartItem(cartKey, quantity)
|
|
263
274
|
.then((updatedCart) => {
|
|
264
275
|
if (updatedCart && !updatedCart.error) {
|
|
265
|
-
// Success - update cart data
|
|
276
|
+
// Success - update cart data and refresh items
|
|
266
277
|
this.#currentCart = updatedCart;
|
|
267
|
-
this.#
|
|
278
|
+
this.#renderCartItems(updatedCart);
|
|
279
|
+
this.#renderCartPanel(updatedCart);
|
|
268
280
|
element.setState('ready');
|
|
269
281
|
|
|
270
282
|
// Emit cart updated and data changed events
|
|
271
|
-
this.#
|
|
272
|
-
this.#emit('cart-dialog:
|
|
283
|
+
const cartWithCalculatedFields = this.#addCalculatedFields(updatedCart);
|
|
284
|
+
this.#emit('cart-dialog:updated', { cart: cartWithCalculatedFields });
|
|
285
|
+
this.#emit('cart-dialog:data-changed', cartWithCalculatedFields);
|
|
273
286
|
} else {
|
|
274
287
|
// Error - reset to ready state
|
|
275
288
|
element.setState('ready');
|
|
@@ -284,13 +297,82 @@ class CartDialog extends HTMLElement {
|
|
|
284
297
|
}
|
|
285
298
|
|
|
286
299
|
/**
|
|
287
|
-
* Update cart
|
|
300
|
+
* Update cart count elements across the site
|
|
301
|
+
* @private
|
|
302
|
+
*/
|
|
303
|
+
#renderCartCount(cartData) {
|
|
304
|
+
if (!cartData) return;
|
|
305
|
+
|
|
306
|
+
// Calculate visible item count (excluding _hide_in_cart items)
|
|
307
|
+
const visibleItems = this.#getVisibleCartItems(cartData);
|
|
308
|
+
const visibleItemCount = visibleItems.reduce((total, item) => total + item.quantity, 0);
|
|
309
|
+
|
|
310
|
+
// Update all cart count elements across the site
|
|
311
|
+
const cartCountElements = document.querySelectorAll('[data-content-cart-count]');
|
|
312
|
+
cartCountElements.forEach((element) => {
|
|
313
|
+
element.textContent = visibleItemCount;
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Update cart subtotal elements across the site
|
|
319
|
+
* @private
|
|
320
|
+
*/
|
|
321
|
+
#renderCartSubtotal(cartData) {
|
|
322
|
+
if (!cartData) return;
|
|
323
|
+
|
|
324
|
+
// Calculate subtotal from all items except those marked to ignore pricing
|
|
325
|
+
const pricedItems = cartData.items.filter(item => {
|
|
326
|
+
const ignorePrice = item.properties?._ignore_price_in_subtotal;
|
|
327
|
+
return !ignorePrice;
|
|
328
|
+
});
|
|
329
|
+
const subtotal = pricedItems.reduce((total, item) => total + (item.line_price || 0), 0);
|
|
330
|
+
|
|
331
|
+
// Update all cart subtotal elements across the site
|
|
332
|
+
const cartSubtotalElements = document.querySelectorAll('[data-content-cart-subtotal]');
|
|
333
|
+
cartSubtotalElements.forEach((element) => {
|
|
334
|
+
// Format as currency (assuming cents, convert to dollars)
|
|
335
|
+
const formatted = (subtotal / 100).toFixed(2);
|
|
336
|
+
element.textContent = `$${formatted}`;
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Update cart items display based on cart data
|
|
288
342
|
* @private
|
|
289
343
|
*/
|
|
290
|
-
#
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
344
|
+
#renderCartPanel(cart = null) {
|
|
345
|
+
const cartData = cart || this.#currentCart;
|
|
346
|
+
if (!cartData) return;
|
|
347
|
+
|
|
348
|
+
// Get cart sections
|
|
349
|
+
const hasItemsSection = this.querySelector('[data-cart-has-items]');
|
|
350
|
+
const emptySection = this.querySelector('[data-cart-is-empty]');
|
|
351
|
+
const itemsContainer = this.querySelector('[data-content-cart-items]');
|
|
352
|
+
|
|
353
|
+
if (!hasItemsSection || !emptySection || !itemsContainer) {
|
|
354
|
+
console.warn(
|
|
355
|
+
'Cart sections not found. Expected [data-cart-has-items], [data-cart-is-empty], and [data-content-cart-items]'
|
|
356
|
+
);
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Check visible item count for showing/hiding sections
|
|
361
|
+
const visibleItems = this.#getVisibleCartItems(cartData);
|
|
362
|
+
const hasVisibleItems = visibleItems.length > 0;
|
|
363
|
+
|
|
364
|
+
// Show/hide sections based on visible item count
|
|
365
|
+
if (hasVisibleItems) {
|
|
366
|
+
hasItemsSection.style.display = '';
|
|
367
|
+
emptySection.style.display = 'none';
|
|
368
|
+
} else {
|
|
369
|
+
hasItemsSection.style.display = 'none';
|
|
370
|
+
emptySection.style.display = '';
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Update cart count and subtotal across the site
|
|
374
|
+
this.#renderCartCount(cartData);
|
|
375
|
+
this.#renderCartSubtotal(cartData);
|
|
294
376
|
}
|
|
295
377
|
|
|
296
378
|
/**
|
|
@@ -342,31 +424,216 @@ class CartDialog extends HTMLElement {
|
|
|
342
424
|
|
|
343
425
|
/**
|
|
344
426
|
* Refresh cart data from server and update components
|
|
427
|
+
* @param {Object} [cartObj=null] - Optional cart object to use instead of fetching
|
|
345
428
|
* @returns {Promise<Object>} Cart data object
|
|
346
429
|
*/
|
|
347
|
-
refreshCart() {
|
|
430
|
+
refreshCart(cartObj = null) {
|
|
431
|
+
// If cart object is provided, use it directly
|
|
432
|
+
if (cartObj && !cartObj.error) {
|
|
433
|
+
// console.log('Using provided cart data:', cartObj);
|
|
434
|
+
this.#currentCart = cartObj;
|
|
435
|
+
this.#renderCartItems(cartObj);
|
|
436
|
+
this.#renderCartPanel(cartObj);
|
|
437
|
+
|
|
438
|
+
// Emit cart refreshed and data changed events
|
|
439
|
+
const cartWithCalculatedFields = this.#addCalculatedFields(cartObj);
|
|
440
|
+
this.#emit('cart-dialog:refreshed', { cart: cartWithCalculatedFields });
|
|
441
|
+
this.#emit('cart-dialog:data-changed', cartWithCalculatedFields);
|
|
442
|
+
|
|
443
|
+
return Promise.resolve(cartObj);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Otherwise fetch from server
|
|
348
447
|
return this.getCart().then((cartData) => {
|
|
448
|
+
// console.log('Cart data received:', cartData);
|
|
349
449
|
if (cartData && !cartData.error) {
|
|
350
450
|
this.#currentCart = cartData;
|
|
351
|
-
this.#
|
|
451
|
+
this.#renderCartItems(cartData);
|
|
452
|
+
this.#renderCartPanel(cartData);
|
|
352
453
|
|
|
353
454
|
// Emit cart refreshed and data changed events
|
|
354
|
-
this.#
|
|
355
|
-
this.#emit('cart-dialog:
|
|
455
|
+
const cartWithCalculatedFields = this.#addCalculatedFields(cartData);
|
|
456
|
+
this.#emit('cart-dialog:refreshed', { cart: cartWithCalculatedFields });
|
|
457
|
+
this.#emit('cart-dialog:data-changed', cartWithCalculatedFields);
|
|
458
|
+
} else {
|
|
459
|
+
console.warn('Cart data has error or is null:', cartData);
|
|
356
460
|
}
|
|
357
461
|
return cartData;
|
|
358
462
|
});
|
|
359
463
|
}
|
|
360
464
|
|
|
465
|
+
/**
|
|
466
|
+
* Remove items from DOM that are no longer in cart data
|
|
467
|
+
* @private
|
|
468
|
+
*/
|
|
469
|
+
#removeItemsFromDOM(itemsContainer, newKeysSet) {
|
|
470
|
+
const currentItems = Array.from(itemsContainer.querySelectorAll('cart-item'));
|
|
471
|
+
|
|
472
|
+
const itemsToRemove = currentItems.filter((item) => !newKeysSet.has(item.getAttribute('key')));
|
|
473
|
+
|
|
474
|
+
itemsToRemove.forEach((item) => {
|
|
475
|
+
console.log('destroy yourself', item);
|
|
476
|
+
item.destroyYourself();
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Add new items to DOM with animation delay
|
|
482
|
+
* @private
|
|
483
|
+
*/
|
|
484
|
+
#addItemsToDOM(itemsContainer, itemsToAdd, newKeys) {
|
|
485
|
+
// Delay adding new items by 300ms to let cart slide open first
|
|
486
|
+
setTimeout(() => {
|
|
487
|
+
itemsToAdd.forEach((itemData) => {
|
|
488
|
+
const cartItem = CartItem.createAnimated(itemData);
|
|
489
|
+
const targetIndex = newKeys.indexOf(itemData.key || itemData.id);
|
|
490
|
+
|
|
491
|
+
// Find the correct position to insert the new item
|
|
492
|
+
if (targetIndex === 0) {
|
|
493
|
+
// Insert at the beginning
|
|
494
|
+
itemsContainer.insertBefore(cartItem, itemsContainer.firstChild);
|
|
495
|
+
} else {
|
|
496
|
+
// Find the item that should come before this one
|
|
497
|
+
let insertAfter = null;
|
|
498
|
+
for (let i = targetIndex - 1; i >= 0; i--) {
|
|
499
|
+
const prevKey = newKeys[i];
|
|
500
|
+
const prevItem = itemsContainer.querySelector(`cart-item[key="${prevKey}"]`);
|
|
501
|
+
if (prevItem) {
|
|
502
|
+
insertAfter = prevItem;
|
|
503
|
+
break;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
if (insertAfter) {
|
|
508
|
+
insertAfter.insertAdjacentElement('afterend', cartItem);
|
|
509
|
+
} else {
|
|
510
|
+
itemsContainer.appendChild(cartItem);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
}, 100);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Filter cart items to exclude those with _hide_in_cart property
|
|
519
|
+
* @private
|
|
520
|
+
*/
|
|
521
|
+
#getVisibleCartItems(cartData) {
|
|
522
|
+
if (!cartData || !cartData.items) return [];
|
|
523
|
+
return cartData.items.filter((item) => {
|
|
524
|
+
// Check for _hide_in_cart in various possible locations
|
|
525
|
+
const hidden = item.properties?._hide_in_cart;
|
|
526
|
+
|
|
527
|
+
return !hidden;
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* Add calculated fields to cart object for events
|
|
533
|
+
* @private
|
|
534
|
+
*/
|
|
535
|
+
#addCalculatedFields(cartData) {
|
|
536
|
+
if (!cartData) return cartData;
|
|
537
|
+
|
|
538
|
+
const visibleItems = this.#getVisibleCartItems(cartData);
|
|
539
|
+
const calculated_count = visibleItems.reduce((total, item) => total + item.quantity, 0);
|
|
540
|
+
const calculated_subtotal = visibleItems.reduce(
|
|
541
|
+
(total, item) => total + (item.line_price || 0),
|
|
542
|
+
0
|
|
543
|
+
);
|
|
544
|
+
|
|
545
|
+
return {
|
|
546
|
+
...cartData,
|
|
547
|
+
calculated_count,
|
|
548
|
+
calculated_subtotal,
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* Render cart items from Shopify cart data with smart comparison
|
|
554
|
+
* @private
|
|
555
|
+
*/
|
|
556
|
+
#renderCartItems(cartData) {
|
|
557
|
+
const itemsContainer = this.querySelector('[data-content-cart-items]');
|
|
558
|
+
|
|
559
|
+
if (!itemsContainer || !cartData || !cartData.items) {
|
|
560
|
+
console.warn('Cannot render cart items:', {
|
|
561
|
+
itemsContainer: !!itemsContainer,
|
|
562
|
+
cartData: !!cartData,
|
|
563
|
+
items: cartData?.items?.length,
|
|
564
|
+
});
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// Filter out items with _hide_in_cart property
|
|
569
|
+
const visibleItems = this.#getVisibleCartItems(cartData);
|
|
570
|
+
|
|
571
|
+
// Handle initial render - load all items without animation
|
|
572
|
+
if (this.#isInitialRender) {
|
|
573
|
+
// console.log('Initial cart render:', visibleItems.length, 'visible items');
|
|
574
|
+
|
|
575
|
+
// Clear existing items
|
|
576
|
+
itemsContainer.innerHTML = '';
|
|
577
|
+
|
|
578
|
+
// Create cart-item elements without animation
|
|
579
|
+
visibleItems.forEach((itemData) => {
|
|
580
|
+
const cartItem = new CartItem(itemData); // No animation
|
|
581
|
+
// const cartItem = document.createElement('cart-item');
|
|
582
|
+
// cartItem.setData(itemData);
|
|
583
|
+
itemsContainer.appendChild(cartItem);
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
this.#isInitialRender = false;
|
|
587
|
+
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// Get current DOM items and their keys
|
|
592
|
+
const currentItems = Array.from(itemsContainer.querySelectorAll('cart-item'));
|
|
593
|
+
const currentKeys = new Set(currentItems.map((item) => item.getAttribute('key')));
|
|
594
|
+
|
|
595
|
+
// Get new cart data keys in order (only visible items)
|
|
596
|
+
const newKeys = visibleItems.map((item) => item.key || item.id);
|
|
597
|
+
const newKeysSet = new Set(newKeys);
|
|
598
|
+
|
|
599
|
+
// Step 1: Remove items that are no longer in cart data
|
|
600
|
+
this.#removeItemsFromDOM(itemsContainer, newKeysSet);
|
|
601
|
+
|
|
602
|
+
// Step 2: Add new items that weren't in DOM (with animation delay)
|
|
603
|
+
const itemsToAdd = visibleItems.filter(
|
|
604
|
+
(itemData) => !currentKeys.has(itemData.key || itemData.id)
|
|
605
|
+
);
|
|
606
|
+
this.#addItemsToDOM(itemsContainer, itemsToAdd, newKeys);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* Set the template function for cart items
|
|
611
|
+
* @param {Function} templateFn - Function that takes item data and returns HTML string
|
|
612
|
+
*/
|
|
613
|
+
setCartItemTemplate(templateName, templateFn) {
|
|
614
|
+
CartItem.setTemplate(templateName, templateFn);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Set the processing template function for cart items
|
|
619
|
+
* @param {Function} templateFn - Function that returns HTML string for processing state
|
|
620
|
+
*/
|
|
621
|
+
setCartItemProcessingTemplate(templateFn) {
|
|
622
|
+
CartItem.setProcessingTemplate(templateFn);
|
|
623
|
+
}
|
|
624
|
+
|
|
361
625
|
/**
|
|
362
626
|
* Shows the cart dialog and traps focus within it
|
|
363
627
|
* @param {HTMLElement} [triggerEl=null] - The element that triggered the cart dialog
|
|
364
628
|
* @fires CartDialog#show - Fired when the cart dialog has been shown
|
|
365
629
|
*/
|
|
366
|
-
show(triggerEl = null) {
|
|
630
|
+
show(triggerEl = null, cartObj) {
|
|
367
631
|
const _ = this;
|
|
368
632
|
_.triggerEl = triggerEl || false;
|
|
369
633
|
|
|
634
|
+
// Lock body scrolling
|
|
635
|
+
_.#lockScroll();
|
|
636
|
+
|
|
370
637
|
// Remove the hidden class first to ensure content is rendered
|
|
371
638
|
_.contentPanel.classList.remove('hidden');
|
|
372
639
|
|
|
@@ -374,17 +641,16 @@ class CartDialog extends HTMLElement {
|
|
|
374
641
|
requestAnimationFrame(() => {
|
|
375
642
|
// Update ARIA states
|
|
376
643
|
_.setAttribute('aria-hidden', 'false');
|
|
644
|
+
|
|
377
645
|
if (_.triggerEl) {
|
|
378
646
|
_.triggerEl.setAttribute('aria-expanded', 'true');
|
|
379
647
|
}
|
|
380
648
|
|
|
381
|
-
// Lock body scrolling and save scroll position
|
|
382
|
-
_.#lockScroll();
|
|
383
|
-
|
|
384
649
|
// Focus management
|
|
385
650
|
const firstFocusable = _.querySelector(
|
|
386
651
|
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
387
652
|
);
|
|
653
|
+
|
|
388
654
|
if (firstFocusable) {
|
|
389
655
|
requestAnimationFrame(() => {
|
|
390
656
|
firstFocusable.focus();
|
|
@@ -392,7 +658,7 @@ class CartDialog extends HTMLElement {
|
|
|
392
658
|
}
|
|
393
659
|
|
|
394
660
|
// Refresh cart data when showing
|
|
395
|
-
_.refreshCart();
|
|
661
|
+
_.refreshCart(cartObj);
|
|
396
662
|
|
|
397
663
|
// Emit show event - cart dialog is now visible
|
|
398
664
|
_.#emit('cart-dialog:show', { triggerElement: _.triggerEl });
|
|
@@ -407,23 +673,31 @@ class CartDialog extends HTMLElement {
|
|
|
407
673
|
hide() {
|
|
408
674
|
const _ = this;
|
|
409
675
|
|
|
410
|
-
// Restore body scroll and scroll position
|
|
411
|
-
_.#restoreScroll();
|
|
412
|
-
|
|
413
676
|
// Update ARIA states
|
|
414
677
|
if (_.triggerEl) {
|
|
415
678
|
// remove focus from modal panel first
|
|
416
679
|
_.triggerEl.focus();
|
|
417
680
|
// mark trigger as no longer expanded
|
|
418
681
|
_.triggerEl.setAttribute('aria-expanded', 'false');
|
|
682
|
+
} else {
|
|
683
|
+
// If no trigger element, blur any focused element inside the panel
|
|
684
|
+
const activeElement = document.activeElement;
|
|
685
|
+
if (activeElement && _.contains(activeElement)) {
|
|
686
|
+
activeElement.blur();
|
|
687
|
+
}
|
|
419
688
|
}
|
|
420
689
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
690
|
+
requestAnimationFrame(() => {
|
|
691
|
+
// Set aria-hidden to start transition
|
|
692
|
+
// The transitionend event handler will add display:none when complete
|
|
693
|
+
_.setAttribute('aria-hidden', 'true');
|
|
694
|
+
|
|
695
|
+
// Emit hide event - cart dialog is now starting to hide
|
|
696
|
+
_.#emit('cart-dialog:hide', { triggerElement: _.triggerEl });
|
|
424
697
|
|
|
425
|
-
|
|
426
|
-
|
|
698
|
+
// Restore body scroll
|
|
699
|
+
_.#restoreScroll();
|
|
700
|
+
});
|
|
427
701
|
}
|
|
428
702
|
}
|
|
429
703
|
|
|
@@ -468,5 +742,10 @@ if (!customElements.get('cart-panel')) {
|
|
|
468
742
|
customElements.define('cart-panel', CartPanel);
|
|
469
743
|
}
|
|
470
744
|
|
|
745
|
+
// Make CartItem available globally for Shopify themes
|
|
746
|
+
if (typeof window !== 'undefined') {
|
|
747
|
+
window.CartItem = CartItem;
|
|
748
|
+
}
|
|
749
|
+
|
|
471
750
|
export { CartDialog, CartOverlay, CartPanel, CartDialog as default };
|
|
472
751
|
//# sourceMappingURL=cart-panel.esm.js.map
|