@magic-spells/cart-panel 0.1.2 → 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 +2 -9
- package/dist/cart-panel.cjs.js +185 -77
- 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 +185 -77
- package/dist/cart-panel.esm.js.map +1 -1
- package/dist/cart-panel.js +1637 -1254
- 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 +185 -78
- package/src/cart-panel.scss +2 -12
package/src/cart-panel.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import './cart-panel.scss';
|
|
2
|
-
import '@magic-spells/cart-item';
|
|
3
2
|
import { CartItem } from '@magic-spells/cart-item';
|
|
4
3
|
import '@magic-spells/focus-trap';
|
|
5
4
|
import EventEmitter from '@magic-spells/event-emitter';
|
|
@@ -10,7 +9,6 @@ import EventEmitter from '@magic-spells/event-emitter';
|
|
|
10
9
|
*/
|
|
11
10
|
class CartDialog extends HTMLElement {
|
|
12
11
|
#handleTransitionEnd;
|
|
13
|
-
#scrollPosition = 0;
|
|
14
12
|
#currentCart = null;
|
|
15
13
|
#eventEmitter;
|
|
16
14
|
#isInitialRender = true;
|
|
@@ -33,31 +31,21 @@ class CartDialog extends HTMLElement {
|
|
|
33
31
|
}
|
|
34
32
|
|
|
35
33
|
/**
|
|
36
|
-
*
|
|
34
|
+
* Locks body scrolling
|
|
37
35
|
* @private
|
|
38
36
|
*/
|
|
39
37
|
#lockScroll() {
|
|
40
|
-
|
|
41
|
-
// Save current scroll position
|
|
42
|
-
_.#scrollPosition = window.pageYOffset;
|
|
43
|
-
|
|
44
|
-
// Apply fixed position to body
|
|
38
|
+
// Apply overflow hidden to body
|
|
45
39
|
document.body.classList.add('overflow-hidden');
|
|
46
|
-
document.body.style.top = `-${_.#scrollPosition}px`;
|
|
47
40
|
}
|
|
48
41
|
|
|
49
42
|
/**
|
|
50
|
-
* Restores
|
|
43
|
+
* Restores body scrolling when cart dialog is closed
|
|
51
44
|
* @private
|
|
52
45
|
*/
|
|
53
46
|
#restoreScroll() {
|
|
54
|
-
|
|
55
|
-
// Remove fixed positioning
|
|
47
|
+
// Remove overflow hidden from body
|
|
56
48
|
document.body.classList.remove('overflow-hidden');
|
|
57
|
-
document.body.style.removeProperty('top');
|
|
58
|
-
|
|
59
|
-
// Restore scroll position
|
|
60
|
-
window.scrollTo(0, _.#scrollPosition);
|
|
61
49
|
}
|
|
62
50
|
|
|
63
51
|
/**
|
|
@@ -98,7 +86,18 @@ class CartDialog extends HTMLElement {
|
|
|
98
86
|
return;
|
|
99
87
|
}
|
|
100
88
|
|
|
101
|
-
|
|
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
|
+
}
|
|
102
101
|
|
|
103
102
|
// Ensure we have labelledby and describedby references
|
|
104
103
|
if (!_.getAttribute('aria-labelledby')) {
|
|
@@ -111,20 +110,15 @@ class CartDialog extends HTMLElement {
|
|
|
111
110
|
}
|
|
112
111
|
}
|
|
113
112
|
|
|
114
|
-
// Insert focus trap before the cart-panel
|
|
115
|
-
_.contentPanel.parentNode.insertBefore(_.focusTrap, _.contentPanel);
|
|
116
|
-
// Move cart-panel inside the focus trap
|
|
117
|
-
_.focusTrap.appendChild(_.contentPanel);
|
|
118
|
-
|
|
119
|
-
// Setup the trap - this will add focus-trap-start/end elements around the content
|
|
120
|
-
_.focusTrap.setupTrap();
|
|
121
|
-
|
|
122
113
|
// Add modal overlay if it doesn't already exist
|
|
123
114
|
if (!_.querySelector('cart-overlay')) {
|
|
124
115
|
_.prepend(document.createElement('cart-overlay'));
|
|
125
116
|
}
|
|
126
117
|
_.#attachListeners();
|
|
127
118
|
_.#bindKeyboard();
|
|
119
|
+
|
|
120
|
+
// Load cart data immediately after component initialization
|
|
121
|
+
_.refreshCart();
|
|
128
122
|
}
|
|
129
123
|
|
|
130
124
|
/**
|
|
@@ -157,6 +151,14 @@ class CartDialog extends HTMLElement {
|
|
|
157
151
|
*/
|
|
158
152
|
#emit(eventName, data = null) {
|
|
159
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
|
+
);
|
|
160
162
|
}
|
|
161
163
|
|
|
162
164
|
/**
|
|
@@ -238,11 +240,12 @@ class CartDialog extends HTMLElement {
|
|
|
238
240
|
// Success - let smart comparison handle the removal animation
|
|
239
241
|
this.#currentCart = updatedCart;
|
|
240
242
|
this.#renderCartItems(updatedCart);
|
|
241
|
-
this.#
|
|
243
|
+
this.#renderCartPanel(updatedCart);
|
|
242
244
|
|
|
243
245
|
// Emit cart updated and data changed events
|
|
244
|
-
this.#
|
|
245
|
-
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);
|
|
246
249
|
} else {
|
|
247
250
|
// Error - reset to ready state
|
|
248
251
|
element.setState('ready');
|
|
@@ -273,12 +276,13 @@ class CartDialog extends HTMLElement {
|
|
|
273
276
|
// Success - update cart data and refresh items
|
|
274
277
|
this.#currentCart = updatedCart;
|
|
275
278
|
this.#renderCartItems(updatedCart);
|
|
276
|
-
this.#
|
|
279
|
+
this.#renderCartPanel(updatedCart);
|
|
277
280
|
element.setState('ready');
|
|
278
281
|
|
|
279
282
|
// Emit cart updated and data changed events
|
|
280
|
-
this.#
|
|
281
|
-
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);
|
|
282
286
|
} else {
|
|
283
287
|
// Error - reset to ready state
|
|
284
288
|
element.setState('ready');
|
|
@@ -292,11 +296,52 @@ class CartDialog extends HTMLElement {
|
|
|
292
296
|
});
|
|
293
297
|
}
|
|
294
298
|
|
|
299
|
+
/**
|
|
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
|
+
|
|
295
340
|
/**
|
|
296
341
|
* Update cart items display based on cart data
|
|
297
342
|
* @private
|
|
298
343
|
*/
|
|
299
|
-
#
|
|
344
|
+
#renderCartPanel(cart = null) {
|
|
300
345
|
const cartData = cart || this.#currentCart;
|
|
301
346
|
if (!cartData) return;
|
|
302
347
|
|
|
@@ -312,14 +357,22 @@ class CartDialog extends HTMLElement {
|
|
|
312
357
|
return;
|
|
313
358
|
}
|
|
314
359
|
|
|
315
|
-
//
|
|
316
|
-
|
|
317
|
-
|
|
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 = '';
|
|
318
367
|
emptySection.style.display = 'none';
|
|
319
368
|
} else {
|
|
320
369
|
hasItemsSection.style.display = 'none';
|
|
321
|
-
emptySection.style.display = '
|
|
370
|
+
emptySection.style.display = '';
|
|
322
371
|
}
|
|
372
|
+
|
|
373
|
+
// Update cart count and subtotal across the site
|
|
374
|
+
this.#renderCartCount(cartData);
|
|
375
|
+
this.#renderCartSubtotal(cartData);
|
|
323
376
|
}
|
|
324
377
|
|
|
325
378
|
/**
|
|
@@ -371,20 +424,37 @@ class CartDialog extends HTMLElement {
|
|
|
371
424
|
|
|
372
425
|
/**
|
|
373
426
|
* Refresh cart data from server and update components
|
|
427
|
+
* @param {Object} [cartObj=null] - Optional cart object to use instead of fetching
|
|
374
428
|
* @returns {Promise<Object>} Cart data object
|
|
375
429
|
*/
|
|
376
|
-
refreshCart() {
|
|
377
|
-
|
|
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
|
|
378
447
|
return this.getCart().then((cartData) => {
|
|
379
|
-
console.log('Cart data received:', cartData);
|
|
448
|
+
// console.log('Cart data received:', cartData);
|
|
380
449
|
if (cartData && !cartData.error) {
|
|
381
450
|
this.#currentCart = cartData;
|
|
382
451
|
this.#renderCartItems(cartData);
|
|
383
|
-
this.#
|
|
452
|
+
this.#renderCartPanel(cartData);
|
|
384
453
|
|
|
385
454
|
// Emit cart refreshed and data changed events
|
|
386
|
-
this.#
|
|
387
|
-
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);
|
|
388
458
|
} else {
|
|
389
459
|
console.warn('Cart data has error or is null:', cartData);
|
|
390
460
|
}
|
|
@@ -398,14 +468,11 @@ class CartDialog extends HTMLElement {
|
|
|
398
468
|
*/
|
|
399
469
|
#removeItemsFromDOM(itemsContainer, newKeysSet) {
|
|
400
470
|
const currentItems = Array.from(itemsContainer.querySelectorAll('cart-item'));
|
|
401
|
-
const itemsToRemove = currentItems.filter((item) => !newKeysSet.has(item.getAttribute('key')));
|
|
402
471
|
|
|
403
|
-
|
|
404
|
-
`Removing ${itemsToRemove.length} items:`,
|
|
405
|
-
itemsToRemove.map((item) => item.getAttribute('key'))
|
|
406
|
-
);
|
|
472
|
+
const itemsToRemove = currentItems.filter((item) => !newKeysSet.has(item.getAttribute('key')));
|
|
407
473
|
|
|
408
474
|
itemsToRemove.forEach((item) => {
|
|
475
|
+
console.log('destroy yourself', item);
|
|
409
476
|
item.destroyYourself();
|
|
410
477
|
});
|
|
411
478
|
}
|
|
@@ -415,11 +482,6 @@ class CartDialog extends HTMLElement {
|
|
|
415
482
|
* @private
|
|
416
483
|
*/
|
|
417
484
|
#addItemsToDOM(itemsContainer, itemsToAdd, newKeys) {
|
|
418
|
-
console.log(
|
|
419
|
-
`Adding ${itemsToAdd.length} items:`,
|
|
420
|
-
itemsToAdd.map((item) => item.key || item.id)
|
|
421
|
-
);
|
|
422
|
-
|
|
423
485
|
// Delay adding new items by 300ms to let cart slide open first
|
|
424
486
|
setTimeout(() => {
|
|
425
487
|
itemsToAdd.forEach((itemData) => {
|
|
@@ -452,6 +514,41 @@ class CartDialog extends HTMLElement {
|
|
|
452
514
|
}, 100);
|
|
453
515
|
}
|
|
454
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
|
+
|
|
455
552
|
/**
|
|
456
553
|
* Render cart items from Shopify cart data with smart comparison
|
|
457
554
|
* @private
|
|
@@ -468,53 +565,53 @@ class CartDialog extends HTMLElement {
|
|
|
468
565
|
return;
|
|
469
566
|
}
|
|
470
567
|
|
|
568
|
+
// Filter out items with _hide_in_cart property
|
|
569
|
+
const visibleItems = this.#getVisibleCartItems(cartData);
|
|
570
|
+
|
|
471
571
|
// Handle initial render - load all items without animation
|
|
472
572
|
if (this.#isInitialRender) {
|
|
473
|
-
console.log('Initial cart render:',
|
|
573
|
+
// console.log('Initial cart render:', visibleItems.length, 'visible items');
|
|
474
574
|
|
|
475
575
|
// Clear existing items
|
|
476
576
|
itemsContainer.innerHTML = '';
|
|
477
577
|
|
|
478
578
|
// Create cart-item elements without animation
|
|
479
|
-
|
|
579
|
+
visibleItems.forEach((itemData) => {
|
|
480
580
|
const cartItem = new CartItem(itemData); // No animation
|
|
581
|
+
// const cartItem = document.createElement('cart-item');
|
|
582
|
+
// cartItem.setData(itemData);
|
|
481
583
|
itemsContainer.appendChild(cartItem);
|
|
482
584
|
});
|
|
483
585
|
|
|
484
586
|
this.#isInitialRender = false;
|
|
485
|
-
|
|
587
|
+
|
|
486
588
|
return;
|
|
487
589
|
}
|
|
488
590
|
|
|
489
|
-
console.log('Smart rendering cart items:', cartData.items.length, 'items');
|
|
490
|
-
|
|
491
591
|
// Get current DOM items and their keys
|
|
492
592
|
const currentItems = Array.from(itemsContainer.querySelectorAll('cart-item'));
|
|
493
593
|
const currentKeys = new Set(currentItems.map((item) => item.getAttribute('key')));
|
|
494
594
|
|
|
495
|
-
// Get new cart data keys in order
|
|
496
|
-
const newKeys =
|
|
595
|
+
// Get new cart data keys in order (only visible items)
|
|
596
|
+
const newKeys = visibleItems.map((item) => item.key || item.id);
|
|
497
597
|
const newKeysSet = new Set(newKeys);
|
|
498
598
|
|
|
499
599
|
// Step 1: Remove items that are no longer in cart data
|
|
500
600
|
this.#removeItemsFromDOM(itemsContainer, newKeysSet);
|
|
501
601
|
|
|
502
602
|
// Step 2: Add new items that weren't in DOM (with animation delay)
|
|
503
|
-
const itemsToAdd =
|
|
603
|
+
const itemsToAdd = visibleItems.filter(
|
|
504
604
|
(itemData) => !currentKeys.has(itemData.key || itemData.id)
|
|
505
605
|
);
|
|
506
|
-
|
|
507
606
|
this.#addItemsToDOM(itemsContainer, itemsToAdd, newKeys);
|
|
508
|
-
|
|
509
|
-
console.log('Smart rendering complete, container children:', itemsContainer.children.length);
|
|
510
607
|
}
|
|
511
608
|
|
|
512
609
|
/**
|
|
513
610
|
* Set the template function for cart items
|
|
514
611
|
* @param {Function} templateFn - Function that takes item data and returns HTML string
|
|
515
612
|
*/
|
|
516
|
-
setCartItemTemplate(templateFn) {
|
|
517
|
-
CartItem.setTemplate(templateFn);
|
|
613
|
+
setCartItemTemplate(templateName, templateFn) {
|
|
614
|
+
CartItem.setTemplate(templateName, templateFn);
|
|
518
615
|
}
|
|
519
616
|
|
|
520
617
|
/**
|
|
@@ -530,10 +627,13 @@ class CartDialog extends HTMLElement {
|
|
|
530
627
|
* @param {HTMLElement} [triggerEl=null] - The element that triggered the cart dialog
|
|
531
628
|
* @fires CartDialog#show - Fired when the cart dialog has been shown
|
|
532
629
|
*/
|
|
533
|
-
show(triggerEl = null) {
|
|
630
|
+
show(triggerEl = null, cartObj) {
|
|
534
631
|
const _ = this;
|
|
535
632
|
_.triggerEl = triggerEl || false;
|
|
536
633
|
|
|
634
|
+
// Lock body scrolling
|
|
635
|
+
_.#lockScroll();
|
|
636
|
+
|
|
537
637
|
// Remove the hidden class first to ensure content is rendered
|
|
538
638
|
_.contentPanel.classList.remove('hidden');
|
|
539
639
|
|
|
@@ -541,17 +641,16 @@ class CartDialog extends HTMLElement {
|
|
|
541
641
|
requestAnimationFrame(() => {
|
|
542
642
|
// Update ARIA states
|
|
543
643
|
_.setAttribute('aria-hidden', 'false');
|
|
644
|
+
|
|
544
645
|
if (_.triggerEl) {
|
|
545
646
|
_.triggerEl.setAttribute('aria-expanded', 'true');
|
|
546
647
|
}
|
|
547
648
|
|
|
548
|
-
// Lock body scrolling and save scroll position
|
|
549
|
-
_.#lockScroll();
|
|
550
|
-
|
|
551
649
|
// Focus management
|
|
552
650
|
const firstFocusable = _.querySelector(
|
|
553
651
|
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
554
652
|
);
|
|
653
|
+
|
|
555
654
|
if (firstFocusable) {
|
|
556
655
|
requestAnimationFrame(() => {
|
|
557
656
|
firstFocusable.focus();
|
|
@@ -559,7 +658,7 @@ class CartDialog extends HTMLElement {
|
|
|
559
658
|
}
|
|
560
659
|
|
|
561
660
|
// Refresh cart data when showing
|
|
562
|
-
_.refreshCart();
|
|
661
|
+
_.refreshCart(cartObj);
|
|
563
662
|
|
|
564
663
|
// Emit show event - cart dialog is now visible
|
|
565
664
|
_.#emit('cart-dialog:show', { triggerElement: _.triggerEl });
|
|
@@ -574,23 +673,31 @@ class CartDialog extends HTMLElement {
|
|
|
574
673
|
hide() {
|
|
575
674
|
const _ = this;
|
|
576
675
|
|
|
577
|
-
// Restore body scroll and scroll position
|
|
578
|
-
_.#restoreScroll();
|
|
579
|
-
|
|
580
676
|
// Update ARIA states
|
|
581
677
|
if (_.triggerEl) {
|
|
582
678
|
// remove focus from modal panel first
|
|
583
679
|
_.triggerEl.focus();
|
|
584
680
|
// mark trigger as no longer expanded
|
|
585
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
|
+
}
|
|
586
688
|
}
|
|
587
689
|
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
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');
|
|
591
694
|
|
|
592
|
-
|
|
593
|
-
|
|
695
|
+
// Emit hide event - cart dialog is now starting to hide
|
|
696
|
+
_.#emit('cart-dialog:hide', { triggerElement: _.triggerEl });
|
|
697
|
+
|
|
698
|
+
// Restore body scroll
|
|
699
|
+
_.#restoreScroll();
|
|
700
|
+
});
|
|
594
701
|
}
|
|
595
702
|
}
|
|
596
703
|
|
package/src/cart-panel.scss
CHANGED
|
@@ -85,8 +85,7 @@ cart-panel {
|
|
|
85
85
|
top: 0;
|
|
86
86
|
right: 0;
|
|
87
87
|
width: var(--cart-panel-width);
|
|
88
|
-
height:
|
|
89
|
-
opacity: 0;
|
|
88
|
+
height: 100dvh;
|
|
90
89
|
transform: translateX(100%);
|
|
91
90
|
pointer-events: none;
|
|
92
91
|
z-index: var(--cart-panel-z-index);
|
|
@@ -94,9 +93,7 @@ cart-panel {
|
|
|
94
93
|
box-shadow: var(--cart-panel-shadow);
|
|
95
94
|
border-radius: var(--cart-panel-border-radius);
|
|
96
95
|
overflow: hidden;
|
|
97
|
-
transition:
|
|
98
|
-
opacity var(--cart-transition-duration) var(--cart-transition-timing),
|
|
99
|
-
transform var(--cart-transition-duration) var(--cart-transition-timing);
|
|
96
|
+
transition: transform var(--cart-transition-duration) var(--cart-transition-timing);
|
|
100
97
|
|
|
101
98
|
// When explicitly hidden, remove from layout
|
|
102
99
|
&.hidden {
|
|
@@ -107,11 +104,4 @@ cart-panel {
|
|
|
107
104
|
// Body scroll lock when cart is open
|
|
108
105
|
body.overflow-hidden {
|
|
109
106
|
overflow: hidden;
|
|
110
|
-
position: fixed;
|
|
111
|
-
width: 100%;
|
|
112
|
-
height: 100%;
|
|
113
|
-
left: 0;
|
|
114
|
-
right: 0;
|
|
115
|
-
margin: 0;
|
|
116
|
-
// The top property will be set dynamically by the component
|
|
117
107
|
}
|