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