@magic-spells/cart-panel 0.1.2 → 0.3.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 +2 -9
- package/dist/cart-panel.cjs.js +215 -80
- package/dist/cart-panel.cjs.js.map +1 -1
- package/dist/cart-panel.css +2 -9
- package/dist/cart-panel.esm.css +2 -9
- package/dist/cart-panel.esm.js +216 -81
- package/dist/cart-panel.esm.js.map +1 -1
- package/dist/cart-panel.js +869 -429
- 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 +2 -2
- package/src/cart-panel.js +216 -81
- package/src/cart-panel.scss +2 -12
package/dist/cart-panel.cjs.css
CHANGED
|
@@ -189,8 +189,7 @@ cart-panel {
|
|
|
189
189
|
top: 0;
|
|
190
190
|
right: 0;
|
|
191
191
|
width: var(--cart-panel-width);
|
|
192
|
-
height:
|
|
193
|
-
opacity: 0;
|
|
192
|
+
height: 100dvh;
|
|
194
193
|
transform: translateX(100%);
|
|
195
194
|
pointer-events: none;
|
|
196
195
|
z-index: var(--cart-panel-z-index);
|
|
@@ -198,7 +197,7 @@ cart-panel {
|
|
|
198
197
|
box-shadow: var(--cart-panel-shadow);
|
|
199
198
|
border-radius: var(--cart-panel-border-radius);
|
|
200
199
|
overflow: hidden;
|
|
201
|
-
transition:
|
|
200
|
+
transition: transform var(--cart-transition-duration) var(--cart-transition-timing);
|
|
202
201
|
}
|
|
203
202
|
cart-panel.hidden {
|
|
204
203
|
display: none;
|
|
@@ -206,10 +205,4 @@ cart-panel.hidden {
|
|
|
206
205
|
|
|
207
206
|
body.overflow-hidden {
|
|
208
207
|
overflow: hidden;
|
|
209
|
-
position: fixed;
|
|
210
|
-
width: 100%;
|
|
211
|
-
height: 100%;
|
|
212
|
-
left: 0;
|
|
213
|
-
right: 0;
|
|
214
|
-
margin: 0;
|
|
215
208
|
}
|
package/dist/cart-panel.cjs.js
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
|
-
var cartItem = require('@magic-spells/cart-item');
|
|
6
5
|
require('@magic-spells/focus-trap');
|
|
7
6
|
var EventEmitter = require('@magic-spells/event-emitter');
|
|
7
|
+
var cartItem = require('@magic-spells/cart-item');
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Custom element that creates an accessible modal cart dialog with focus management
|
|
@@ -12,7 +12,6 @@ var EventEmitter = require('@magic-spells/event-emitter');
|
|
|
12
12
|
*/
|
|
13
13
|
class CartDialog extends HTMLElement {
|
|
14
14
|
#handleTransitionEnd;
|
|
15
|
-
#scrollPosition = 0;
|
|
16
15
|
#currentCart = null;
|
|
17
16
|
#eventEmitter;
|
|
18
17
|
#isInitialRender = true;
|
|
@@ -35,31 +34,21 @@ class CartDialog extends HTMLElement {
|
|
|
35
34
|
}
|
|
36
35
|
|
|
37
36
|
/**
|
|
38
|
-
*
|
|
37
|
+
* Locks body scrolling
|
|
39
38
|
* @private
|
|
40
39
|
*/
|
|
41
40
|
#lockScroll() {
|
|
42
|
-
|
|
43
|
-
// Save current scroll position
|
|
44
|
-
_.#scrollPosition = window.pageYOffset;
|
|
45
|
-
|
|
46
|
-
// Apply fixed position to body
|
|
41
|
+
// Apply overflow hidden to body
|
|
47
42
|
document.body.classList.add('overflow-hidden');
|
|
48
|
-
document.body.style.top = `-${_.#scrollPosition}px`;
|
|
49
43
|
}
|
|
50
44
|
|
|
51
45
|
/**
|
|
52
|
-
* Restores
|
|
46
|
+
* Restores body scrolling when cart dialog is closed
|
|
53
47
|
* @private
|
|
54
48
|
*/
|
|
55
49
|
#restoreScroll() {
|
|
56
|
-
|
|
57
|
-
// Remove fixed positioning
|
|
50
|
+
// Remove overflow hidden from body
|
|
58
51
|
document.body.classList.remove('overflow-hidden');
|
|
59
|
-
document.body.style.removeProperty('top');
|
|
60
|
-
|
|
61
|
-
// Restore scroll position
|
|
62
|
-
window.scrollTo(0, _.#scrollPosition);
|
|
63
52
|
}
|
|
64
53
|
|
|
65
54
|
/**
|
|
@@ -100,7 +89,18 @@ class CartDialog extends HTMLElement {
|
|
|
100
89
|
return;
|
|
101
90
|
}
|
|
102
91
|
|
|
103
|
-
|
|
92
|
+
// Check if focus-trap already exists, if not create one
|
|
93
|
+
_.focusTrap = _.contentPanel.querySelector('focus-trap');
|
|
94
|
+
if (!_.focusTrap) {
|
|
95
|
+
_.focusTrap = document.createElement('focus-trap');
|
|
96
|
+
|
|
97
|
+
// Move all existing cart-panel content into the focus trap
|
|
98
|
+
const existingContent = Array.from(_.contentPanel.childNodes);
|
|
99
|
+
existingContent.forEach((child) => _.focusTrap.appendChild(child));
|
|
100
|
+
|
|
101
|
+
// Insert focus trap inside the cart-panel
|
|
102
|
+
_.contentPanel.appendChild(_.focusTrap);
|
|
103
|
+
}
|
|
104
104
|
|
|
105
105
|
// Ensure we have labelledby and describedby references
|
|
106
106
|
if (!_.getAttribute('aria-labelledby')) {
|
|
@@ -113,20 +113,15 @@ class CartDialog extends HTMLElement {
|
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
// Insert focus trap before the cart-panel
|
|
117
|
-
_.contentPanel.parentNode.insertBefore(_.focusTrap, _.contentPanel);
|
|
118
|
-
// Move cart-panel inside the focus trap
|
|
119
|
-
_.focusTrap.appendChild(_.contentPanel);
|
|
120
|
-
|
|
121
|
-
// Setup the trap - this will add focus-trap-start/end elements around the content
|
|
122
|
-
_.focusTrap.setupTrap();
|
|
123
|
-
|
|
124
116
|
// Add modal overlay if it doesn't already exist
|
|
125
117
|
if (!_.querySelector('cart-overlay')) {
|
|
126
118
|
_.prepend(document.createElement('cart-overlay'));
|
|
127
119
|
}
|
|
128
120
|
_.#attachListeners();
|
|
129
121
|
_.#bindKeyboard();
|
|
122
|
+
|
|
123
|
+
// Load cart data immediately after component initialization
|
|
124
|
+
_.refreshCart();
|
|
130
125
|
}
|
|
131
126
|
|
|
132
127
|
/**
|
|
@@ -159,6 +154,14 @@ class CartDialog extends HTMLElement {
|
|
|
159
154
|
*/
|
|
160
155
|
#emit(eventName, data = null) {
|
|
161
156
|
this.#eventEmitter.emit(eventName, data);
|
|
157
|
+
|
|
158
|
+
// Also emit as native DOM events for better compatibility
|
|
159
|
+
this.dispatchEvent(
|
|
160
|
+
new CustomEvent(eventName, {
|
|
161
|
+
detail: data,
|
|
162
|
+
bubbles: true,
|
|
163
|
+
})
|
|
164
|
+
);
|
|
162
165
|
}
|
|
163
166
|
|
|
164
167
|
/**
|
|
@@ -240,11 +243,12 @@ class CartDialog extends HTMLElement {
|
|
|
240
243
|
// Success - let smart comparison handle the removal animation
|
|
241
244
|
this.#currentCart = updatedCart;
|
|
242
245
|
this.#renderCartItems(updatedCart);
|
|
243
|
-
this.#
|
|
246
|
+
this.#renderCartPanel(updatedCart);
|
|
244
247
|
|
|
245
248
|
// Emit cart updated and data changed events
|
|
246
|
-
this.#
|
|
247
|
-
this.#emit('cart-dialog:
|
|
249
|
+
const cartWithCalculatedFields = this.#addCalculatedFields(updatedCart);
|
|
250
|
+
this.#emit('cart-dialog:updated', { cart: cartWithCalculatedFields });
|
|
251
|
+
this.#emit('cart-dialog:data-changed', cartWithCalculatedFields);
|
|
248
252
|
} else {
|
|
249
253
|
// Error - reset to ready state
|
|
250
254
|
element.setState('ready');
|
|
@@ -275,12 +279,12 @@ class CartDialog extends HTMLElement {
|
|
|
275
279
|
// Success - update cart data and refresh items
|
|
276
280
|
this.#currentCart = updatedCart;
|
|
277
281
|
this.#renderCartItems(updatedCart);
|
|
278
|
-
this.#
|
|
279
|
-
element.setState('ready');
|
|
282
|
+
this.#renderCartPanel(updatedCart);
|
|
280
283
|
|
|
281
284
|
// Emit cart updated and data changed events
|
|
282
|
-
this.#
|
|
283
|
-
this.#emit('cart-dialog:
|
|
285
|
+
const cartWithCalculatedFields = this.#addCalculatedFields(updatedCart);
|
|
286
|
+
this.#emit('cart-dialog:updated', { cart: cartWithCalculatedFields });
|
|
287
|
+
this.#emit('cart-dialog:data-changed', cartWithCalculatedFields);
|
|
284
288
|
} else {
|
|
285
289
|
// Error - reset to ready state
|
|
286
290
|
element.setState('ready');
|
|
@@ -294,11 +298,52 @@ class CartDialog extends HTMLElement {
|
|
|
294
298
|
});
|
|
295
299
|
}
|
|
296
300
|
|
|
301
|
+
/**
|
|
302
|
+
* Update cart count elements across the site
|
|
303
|
+
* @private
|
|
304
|
+
*/
|
|
305
|
+
#renderCartCount(cartData) {
|
|
306
|
+
if (!cartData) return;
|
|
307
|
+
|
|
308
|
+
// Calculate visible item count (excluding _hide_in_cart items)
|
|
309
|
+
const visibleItems = this.#getVisibleCartItems(cartData);
|
|
310
|
+
const visibleItemCount = visibleItems.reduce((total, item) => total + item.quantity, 0);
|
|
311
|
+
|
|
312
|
+
// Update all cart count elements across the site
|
|
313
|
+
const cartCountElements = document.querySelectorAll('[data-content-cart-count]');
|
|
314
|
+
cartCountElements.forEach((element) => {
|
|
315
|
+
element.textContent = visibleItemCount;
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Update cart subtotal elements across the site
|
|
321
|
+
* @private
|
|
322
|
+
*/
|
|
323
|
+
#renderCartSubtotal(cartData) {
|
|
324
|
+
if (!cartData) return;
|
|
325
|
+
|
|
326
|
+
// Calculate subtotal from all items except those marked to ignore pricing
|
|
327
|
+
const pricedItems = cartData.items.filter((item) => {
|
|
328
|
+
const ignorePrice = item.properties?._ignore_price_in_subtotal;
|
|
329
|
+
return !ignorePrice;
|
|
330
|
+
});
|
|
331
|
+
const subtotal = pricedItems.reduce((total, item) => total + (item.line_price || 0), 0);
|
|
332
|
+
|
|
333
|
+
// Update all cart subtotal elements across the site
|
|
334
|
+
const cartSubtotalElements = document.querySelectorAll('[data-content-cart-subtotal]');
|
|
335
|
+
cartSubtotalElements.forEach((element) => {
|
|
336
|
+
// Format as currency (assuming cents, convert to dollars)
|
|
337
|
+
const formatted = (subtotal / 100).toFixed(2);
|
|
338
|
+
element.textContent = `$${formatted}`;
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
|
|
297
342
|
/**
|
|
298
343
|
* Update cart items display based on cart data
|
|
299
344
|
* @private
|
|
300
345
|
*/
|
|
301
|
-
#
|
|
346
|
+
#renderCartPanel(cart = null) {
|
|
302
347
|
const cartData = cart || this.#currentCart;
|
|
303
348
|
if (!cartData) return;
|
|
304
349
|
|
|
@@ -314,14 +359,22 @@ class CartDialog extends HTMLElement {
|
|
|
314
359
|
return;
|
|
315
360
|
}
|
|
316
361
|
|
|
317
|
-
//
|
|
318
|
-
|
|
319
|
-
|
|
362
|
+
// Check visible item count for showing/hiding sections
|
|
363
|
+
const visibleItems = this.#getVisibleCartItems(cartData);
|
|
364
|
+
const hasVisibleItems = visibleItems.length > 0;
|
|
365
|
+
|
|
366
|
+
// Show/hide sections based on visible item count
|
|
367
|
+
if (hasVisibleItems) {
|
|
368
|
+
hasItemsSection.style.display = '';
|
|
320
369
|
emptySection.style.display = 'none';
|
|
321
370
|
} else {
|
|
322
371
|
hasItemsSection.style.display = 'none';
|
|
323
|
-
emptySection.style.display = '
|
|
372
|
+
emptySection.style.display = '';
|
|
324
373
|
}
|
|
374
|
+
|
|
375
|
+
// Update cart count and subtotal across the site
|
|
376
|
+
this.#renderCartCount(cartData);
|
|
377
|
+
this.#renderCartSubtotal(cartData);
|
|
325
378
|
}
|
|
326
379
|
|
|
327
380
|
/**
|
|
@@ -373,20 +426,37 @@ class CartDialog extends HTMLElement {
|
|
|
373
426
|
|
|
374
427
|
/**
|
|
375
428
|
* Refresh cart data from server and update components
|
|
429
|
+
* @param {Object} [cartObj=null] - Optional cart object to use instead of fetching
|
|
376
430
|
* @returns {Promise<Object>} Cart data object
|
|
377
431
|
*/
|
|
378
|
-
refreshCart() {
|
|
379
|
-
|
|
432
|
+
refreshCart(cartObj = null) {
|
|
433
|
+
// If cart object is provided, use it directly
|
|
434
|
+
if (cartObj && !cartObj.error) {
|
|
435
|
+
// console.log('Using provided cart data:', cartObj);
|
|
436
|
+
this.#currentCart = cartObj;
|
|
437
|
+
this.#renderCartItems(cartObj);
|
|
438
|
+
this.#renderCartPanel(cartObj);
|
|
439
|
+
|
|
440
|
+
// Emit cart refreshed and data changed events
|
|
441
|
+
const cartWithCalculatedFields = this.#addCalculatedFields(cartObj);
|
|
442
|
+
this.#emit('cart-dialog:refreshed', { cart: cartWithCalculatedFields });
|
|
443
|
+
this.#emit('cart-dialog:data-changed', cartWithCalculatedFields);
|
|
444
|
+
|
|
445
|
+
return Promise.resolve(cartObj);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// Otherwise fetch from server
|
|
380
449
|
return this.getCart().then((cartData) => {
|
|
381
|
-
console.log('Cart data received:', cartData);
|
|
450
|
+
// console.log('Cart data received:', cartData);
|
|
382
451
|
if (cartData && !cartData.error) {
|
|
383
452
|
this.#currentCart = cartData;
|
|
384
453
|
this.#renderCartItems(cartData);
|
|
385
|
-
this.#
|
|
454
|
+
this.#renderCartPanel(cartData);
|
|
386
455
|
|
|
387
456
|
// Emit cart refreshed and data changed events
|
|
388
|
-
this.#
|
|
389
|
-
this.#emit('cart-dialog:
|
|
457
|
+
const cartWithCalculatedFields = this.#addCalculatedFields(cartData);
|
|
458
|
+
this.#emit('cart-dialog:refreshed', { cart: cartWithCalculatedFields });
|
|
459
|
+
this.#emit('cart-dialog:data-changed', cartWithCalculatedFields);
|
|
390
460
|
} else {
|
|
391
461
|
console.warn('Cart data has error or is null:', cartData);
|
|
392
462
|
}
|
|
@@ -400,28 +470,40 @@ class CartDialog extends HTMLElement {
|
|
|
400
470
|
*/
|
|
401
471
|
#removeItemsFromDOM(itemsContainer, newKeysSet) {
|
|
402
472
|
const currentItems = Array.from(itemsContainer.querySelectorAll('cart-item'));
|
|
403
|
-
const itemsToRemove = currentItems.filter((item) => !newKeysSet.has(item.getAttribute('key')));
|
|
404
473
|
|
|
405
|
-
|
|
406
|
-
`Removing ${itemsToRemove.length} items:`,
|
|
407
|
-
itemsToRemove.map((item) => item.getAttribute('key'))
|
|
408
|
-
);
|
|
474
|
+
const itemsToRemove = currentItems.filter((item) => !newKeysSet.has(item.getAttribute('key')));
|
|
409
475
|
|
|
410
476
|
itemsToRemove.forEach((item) => {
|
|
477
|
+
console.log('destroy yourself', item);
|
|
411
478
|
item.destroyYourself();
|
|
412
479
|
});
|
|
413
480
|
}
|
|
414
481
|
|
|
482
|
+
/**
|
|
483
|
+
* Update existing cart-item elements with fresh cart data
|
|
484
|
+
* @private
|
|
485
|
+
*/
|
|
486
|
+
#updateItemsInDOM(itemsContainer, cartData) {
|
|
487
|
+
const visibleItems = this.#getVisibleCartItems(cartData);
|
|
488
|
+
const existingItems = Array.from(itemsContainer.querySelectorAll('cart-item'));
|
|
489
|
+
|
|
490
|
+
existingItems.forEach((cartItemEl) => {
|
|
491
|
+
const key = cartItemEl.getAttribute('key');
|
|
492
|
+
const updatedItemData = visibleItems.find((item) => (item.key || item.id) === key);
|
|
493
|
+
|
|
494
|
+
if (updatedItemData) {
|
|
495
|
+
// Update cart-item with fresh data and full cart context
|
|
496
|
+
// The cart-item will handle HTML comparison and only re-render if needed
|
|
497
|
+
cartItemEl.setData(updatedItemData, cartData);
|
|
498
|
+
}
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
|
|
415
502
|
/**
|
|
416
503
|
* Add new items to DOM with animation delay
|
|
417
504
|
* @private
|
|
418
505
|
*/
|
|
419
506
|
#addItemsToDOM(itemsContainer, itemsToAdd, newKeys) {
|
|
420
|
-
console.log(
|
|
421
|
-
`Adding ${itemsToAdd.length} items:`,
|
|
422
|
-
itemsToAdd.map((item) => item.key || item.id)
|
|
423
|
-
);
|
|
424
|
-
|
|
425
507
|
// Delay adding new items by 300ms to let cart slide open first
|
|
426
508
|
setTimeout(() => {
|
|
427
509
|
itemsToAdd.forEach((itemData) => {
|
|
@@ -454,6 +536,48 @@ class CartDialog extends HTMLElement {
|
|
|
454
536
|
}, 100);
|
|
455
537
|
}
|
|
456
538
|
|
|
539
|
+
/**
|
|
540
|
+
* Filter cart items to exclude those with _hide_in_cart property
|
|
541
|
+
* @private
|
|
542
|
+
*/
|
|
543
|
+
#getVisibleCartItems(cartData) {
|
|
544
|
+
if (!cartData || !cartData.items) return [];
|
|
545
|
+
return cartData.items.filter((item) => {
|
|
546
|
+
// Check for _hide_in_cart in various possible locations
|
|
547
|
+
const hidden = item.properties?._hide_in_cart;
|
|
548
|
+
|
|
549
|
+
return !hidden;
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* Add calculated fields to cart object for events
|
|
555
|
+
* @private
|
|
556
|
+
*/
|
|
557
|
+
#addCalculatedFields(cartData) {
|
|
558
|
+
if (!cartData) return cartData;
|
|
559
|
+
|
|
560
|
+
// For display counts: use visible items (excludes _hide_in_cart)
|
|
561
|
+
const visibleItems = this.#getVisibleCartItems(cartData);
|
|
562
|
+
const calculated_count = visibleItems.reduce((total, item) => total + item.quantity, 0);
|
|
563
|
+
|
|
564
|
+
// For pricing: use all items except those marked to ignore pricing
|
|
565
|
+
const pricedItems = cartData.items.filter((item) => {
|
|
566
|
+
const ignorePrice = item.properties?._ignore_price_in_subtotal;
|
|
567
|
+
return !ignorePrice;
|
|
568
|
+
});
|
|
569
|
+
const calculated_subtotal = pricedItems.reduce(
|
|
570
|
+
(total, item) => total + (item.line_price || 0),
|
|
571
|
+
0
|
|
572
|
+
);
|
|
573
|
+
|
|
574
|
+
return {
|
|
575
|
+
...cartData,
|
|
576
|
+
calculated_count,
|
|
577
|
+
calculated_subtotal,
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
|
|
457
581
|
/**
|
|
458
582
|
* Render cart items from Shopify cart data with smart comparison
|
|
459
583
|
* @private
|
|
@@ -470,53 +594,54 @@ class CartDialog extends HTMLElement {
|
|
|
470
594
|
return;
|
|
471
595
|
}
|
|
472
596
|
|
|
597
|
+
// Filter out items with _hide_in_cart property
|
|
598
|
+
const visibleItems = this.#getVisibleCartItems(cartData);
|
|
599
|
+
|
|
473
600
|
// Handle initial render - load all items without animation
|
|
474
601
|
if (this.#isInitialRender) {
|
|
475
|
-
console.log('Initial cart render:',
|
|
602
|
+
// console.log('Initial cart render:', visibleItems.length, 'visible items');
|
|
476
603
|
|
|
477
604
|
// Clear existing items
|
|
478
605
|
itemsContainer.innerHTML = '';
|
|
479
606
|
|
|
480
607
|
// Create cart-item elements without animation
|
|
481
|
-
|
|
608
|
+
visibleItems.forEach((itemData) => {
|
|
482
609
|
const cartItem$1 = new cartItem.CartItem(itemData); // No animation
|
|
483
610
|
itemsContainer.appendChild(cartItem$1);
|
|
484
611
|
});
|
|
485
612
|
|
|
486
613
|
this.#isInitialRender = false;
|
|
487
|
-
|
|
614
|
+
|
|
488
615
|
return;
|
|
489
616
|
}
|
|
490
617
|
|
|
491
|
-
console.log('Smart rendering cart items:', cartData.items.length, 'items');
|
|
492
|
-
|
|
493
618
|
// Get current DOM items and their keys
|
|
494
619
|
const currentItems = Array.from(itemsContainer.querySelectorAll('cart-item'));
|
|
495
620
|
const currentKeys = new Set(currentItems.map((item) => item.getAttribute('key')));
|
|
496
621
|
|
|
497
|
-
// Get new cart data keys in order
|
|
498
|
-
const newKeys =
|
|
622
|
+
// Get new cart data keys in order (only visible items)
|
|
623
|
+
const newKeys = visibleItems.map((item) => item.key || item.id);
|
|
499
624
|
const newKeysSet = new Set(newKeys);
|
|
500
625
|
|
|
501
626
|
// Step 1: Remove items that are no longer in cart data
|
|
502
627
|
this.#removeItemsFromDOM(itemsContainer, newKeysSet);
|
|
503
628
|
|
|
504
|
-
// Step 2:
|
|
505
|
-
|
|
629
|
+
// Step 2: Update existing items with fresh data (handles templates, bundles, etc.)
|
|
630
|
+
this.#updateItemsInDOM(itemsContainer, cartData);
|
|
631
|
+
|
|
632
|
+
// Step 3: Add new items that weren't in DOM (with animation delay)
|
|
633
|
+
const itemsToAdd = visibleItems.filter(
|
|
506
634
|
(itemData) => !currentKeys.has(itemData.key || itemData.id)
|
|
507
635
|
);
|
|
508
|
-
|
|
509
636
|
this.#addItemsToDOM(itemsContainer, itemsToAdd, newKeys);
|
|
510
|
-
|
|
511
|
-
console.log('Smart rendering complete, container children:', itemsContainer.children.length);
|
|
512
637
|
}
|
|
513
638
|
|
|
514
639
|
/**
|
|
515
640
|
* Set the template function for cart items
|
|
516
641
|
* @param {Function} templateFn - Function that takes item data and returns HTML string
|
|
517
642
|
*/
|
|
518
|
-
setCartItemTemplate(templateFn) {
|
|
519
|
-
cartItem.CartItem.setTemplate(templateFn);
|
|
643
|
+
setCartItemTemplate(templateName, templateFn) {
|
|
644
|
+
cartItem.CartItem.setTemplate(templateName, templateFn);
|
|
520
645
|
}
|
|
521
646
|
|
|
522
647
|
/**
|
|
@@ -532,10 +657,13 @@ class CartDialog extends HTMLElement {
|
|
|
532
657
|
* @param {HTMLElement} [triggerEl=null] - The element that triggered the cart dialog
|
|
533
658
|
* @fires CartDialog#show - Fired when the cart dialog has been shown
|
|
534
659
|
*/
|
|
535
|
-
show(triggerEl = null) {
|
|
660
|
+
show(triggerEl = null, cartObj) {
|
|
536
661
|
const _ = this;
|
|
537
662
|
_.triggerEl = triggerEl || false;
|
|
538
663
|
|
|
664
|
+
// Lock body scrolling
|
|
665
|
+
_.#lockScroll();
|
|
666
|
+
|
|
539
667
|
// Remove the hidden class first to ensure content is rendered
|
|
540
668
|
_.contentPanel.classList.remove('hidden');
|
|
541
669
|
|
|
@@ -543,17 +671,16 @@ class CartDialog extends HTMLElement {
|
|
|
543
671
|
requestAnimationFrame(() => {
|
|
544
672
|
// Update ARIA states
|
|
545
673
|
_.setAttribute('aria-hidden', 'false');
|
|
674
|
+
|
|
546
675
|
if (_.triggerEl) {
|
|
547
676
|
_.triggerEl.setAttribute('aria-expanded', 'true');
|
|
548
677
|
}
|
|
549
678
|
|
|
550
|
-
// Lock body scrolling and save scroll position
|
|
551
|
-
_.#lockScroll();
|
|
552
|
-
|
|
553
679
|
// Focus management
|
|
554
680
|
const firstFocusable = _.querySelector(
|
|
555
681
|
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
556
682
|
);
|
|
683
|
+
|
|
557
684
|
if (firstFocusable) {
|
|
558
685
|
requestAnimationFrame(() => {
|
|
559
686
|
firstFocusable.focus();
|
|
@@ -561,7 +688,7 @@ class CartDialog extends HTMLElement {
|
|
|
561
688
|
}
|
|
562
689
|
|
|
563
690
|
// Refresh cart data when showing
|
|
564
|
-
_.refreshCart();
|
|
691
|
+
_.refreshCart(cartObj);
|
|
565
692
|
|
|
566
693
|
// Emit show event - cart dialog is now visible
|
|
567
694
|
_.#emit('cart-dialog:show', { triggerElement: _.triggerEl });
|
|
@@ -576,23 +703,31 @@ class CartDialog extends HTMLElement {
|
|
|
576
703
|
hide() {
|
|
577
704
|
const _ = this;
|
|
578
705
|
|
|
579
|
-
// Restore body scroll and scroll position
|
|
580
|
-
_.#restoreScroll();
|
|
581
|
-
|
|
582
706
|
// Update ARIA states
|
|
583
707
|
if (_.triggerEl) {
|
|
584
708
|
// remove focus from modal panel first
|
|
585
709
|
_.triggerEl.focus();
|
|
586
710
|
// mark trigger as no longer expanded
|
|
587
711
|
_.triggerEl.setAttribute('aria-expanded', 'false');
|
|
712
|
+
} else {
|
|
713
|
+
// If no trigger element, blur any focused element inside the panel
|
|
714
|
+
const activeElement = document.activeElement;
|
|
715
|
+
if (activeElement && _.contains(activeElement)) {
|
|
716
|
+
activeElement.blur();
|
|
717
|
+
}
|
|
588
718
|
}
|
|
589
719
|
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
720
|
+
requestAnimationFrame(() => {
|
|
721
|
+
// Set aria-hidden to start transition
|
|
722
|
+
// The transitionend event handler will add display:none when complete
|
|
723
|
+
_.setAttribute('aria-hidden', 'true');
|
|
593
724
|
|
|
594
|
-
|
|
595
|
-
|
|
725
|
+
// Emit hide event - cart dialog is now starting to hide
|
|
726
|
+
_.#emit('cart-dialog:hide', { triggerElement: _.triggerEl });
|
|
727
|
+
|
|
728
|
+
// Restore body scroll
|
|
729
|
+
_.#restoreScroll();
|
|
730
|
+
});
|
|
596
731
|
}
|
|
597
732
|
}
|
|
598
733
|
|