@magic-spells/cart-panel 0.1.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.
@@ -0,0 +1,473 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ require('@magic-spells/cart-item');
6
+ require('@magic-spells/focus-trap');
7
+ var EventEmitter = require('@magic-spells/event-emitter');
8
+
9
+ /**
10
+ * Custom element that creates an accessible modal cart dialog with focus management
11
+ * @extends HTMLElement
12
+ */
13
+ class CartDialog extends HTMLElement {
14
+ #handleTransitionEnd;
15
+ #scrollPosition = 0;
16
+ #currentCart = null;
17
+ #eventEmitter;
18
+
19
+ /**
20
+ * Clean up event listeners when component is removed from DOM
21
+ */
22
+ disconnectedCallback() {
23
+ const _ = this;
24
+ if (_.contentPanel) {
25
+ _.contentPanel.removeEventListener('transitionend', _.#handleTransitionEnd);
26
+ }
27
+
28
+ // Ensure body scroll is restored if component is removed while open
29
+ document.body.classList.remove('overflow-hidden');
30
+ this.#restoreScroll();
31
+
32
+ // Detach event listeners
33
+ this.#detachListeners();
34
+ }
35
+
36
+ /**
37
+ * Saves current scroll position and locks body scrolling
38
+ * @private
39
+ */
40
+ #lockScroll() {
41
+ const _ = this;
42
+ // Save current scroll position
43
+ _.#scrollPosition = window.pageYOffset;
44
+
45
+ // Apply fixed position to body
46
+ document.body.classList.add('overflow-hidden');
47
+ document.body.style.top = `-${_.#scrollPosition}px`;
48
+ }
49
+
50
+ /**
51
+ * Restores scroll position when cart dialog is closed
52
+ * @private
53
+ */
54
+ #restoreScroll() {
55
+ const _ = this;
56
+ // Remove fixed positioning
57
+ document.body.classList.remove('overflow-hidden');
58
+ document.body.style.removeProperty('top');
59
+
60
+ // Restore scroll position
61
+ window.scrollTo(0, _.#scrollPosition);
62
+ }
63
+
64
+ /**
65
+ * Initializes the cart dialog, sets up focus trap and overlay
66
+ */
67
+ constructor() {
68
+ super();
69
+ const _ = this;
70
+ _.id = _.getAttribute('id');
71
+ _.setAttribute('role', 'dialog');
72
+ _.setAttribute('aria-modal', 'true');
73
+ _.setAttribute('aria-hidden', 'true');
74
+
75
+ _.triggerEl = null;
76
+
77
+ // Initialize event emitter
78
+ _.#eventEmitter = new EventEmitter();
79
+
80
+ // Create a handler for transition end events
81
+ _.#handleTransitionEnd = (e) => {
82
+ if (e.propertyName === 'opacity' && _.getAttribute('aria-hidden') === 'true') {
83
+ _.contentPanel.classList.add('hidden');
84
+
85
+ // Emit afterHide event - cart dialog has completed its transition
86
+ _.#emit('cart-dialog:afterHide', { triggerElement: _.triggerEl });
87
+ }
88
+ };
89
+ }
90
+
91
+ connectedCallback() {
92
+ const _ = this;
93
+
94
+ // Now that we're in the DOM, find the content panel and set up focus trap
95
+ _.contentPanel = _.querySelector('cart-panel');
96
+
97
+ if (!_.contentPanel) {
98
+ console.error('cart-panel element not found inside cart-dialog');
99
+ return;
100
+ }
101
+
102
+ _.focusTrap = document.createElement('focus-trap');
103
+
104
+ // Ensure we have labelledby and describedby references
105
+ if (!_.getAttribute('aria-labelledby')) {
106
+ const heading = _.querySelector('h1, h2, h3');
107
+ if (heading && !heading.id) {
108
+ heading.id = `${_.id}-title`;
109
+ }
110
+ if (heading?.id) {
111
+ _.setAttribute('aria-labelledby', heading.id);
112
+ }
113
+ }
114
+
115
+ _.contentPanel.parentNode.insertBefore(_.focusTrap, _.contentPanel);
116
+ _.focusTrap.appendChild(_.contentPanel);
117
+
118
+ _.focusTrap.setupTrap();
119
+
120
+ // Add modal overlay
121
+ _.prepend(document.createElement('cart-overlay'));
122
+ _.#attachListeners();
123
+ _.#bindKeyboard();
124
+ }
125
+
126
+ /**
127
+ * Event emitter method - Add an event listener with a cleaner API
128
+ * @param {string} eventName - Name of the event to listen for
129
+ * @param {Function} callback - Callback function to execute when event is fired
130
+ * @returns {CartDialog} Returns this for method chaining
131
+ */
132
+ on(eventName, callback) {
133
+ this.#eventEmitter.on(eventName, callback);
134
+ return this;
135
+ }
136
+
137
+ /**
138
+ * Event emitter method - Remove an event listener
139
+ * @param {string} eventName - Name of the event to stop listening for
140
+ * @param {Function} callback - Callback function to remove
141
+ * @returns {CartDialog} Returns this for method chaining
142
+ */
143
+ off(eventName, callback) {
144
+ this.#eventEmitter.off(eventName, callback);
145
+ return this;
146
+ }
147
+
148
+ /**
149
+ * Internal method to emit events via the event emitter
150
+ * @param {string} eventName - Name of the event to emit
151
+ * @param {*} [data] - Optional data to include with the event
152
+ * @private
153
+ */
154
+ #emit(eventName, data = null) {
155
+ this.#eventEmitter.emit(eventName, data);
156
+ }
157
+
158
+ /**
159
+ * Attach event listeners for cart dialog functionality
160
+ * @private
161
+ */
162
+ #attachListeners() {
163
+ const _ = this;
164
+
165
+ // Handle trigger buttons
166
+ document.addEventListener('click', (e) => {
167
+ const trigger = e.target.closest(`[aria-controls="${_.id}"]`);
168
+ if (!trigger) return;
169
+
170
+ if (trigger.getAttribute('data-prevent-default') === 'true') {
171
+ e.preventDefault();
172
+ }
173
+
174
+ _.show(trigger);
175
+ });
176
+
177
+ // Handle close buttons
178
+ _.addEventListener('click', (e) => {
179
+ if (!e.target.closest('[data-action="hide-cart"]')) return;
180
+ _.hide();
181
+ });
182
+
183
+ // Handle cart item remove events
184
+ _.addEventListener('cart-item:remove', (e) => {
185
+ _.#handleCartItemRemove(e);
186
+ });
187
+
188
+ // Handle cart item quantity change events
189
+ _.addEventListener('cart-item:quantity-change', (e) => {
190
+ _.#handleCartItemQuantityChange(e);
191
+ });
192
+
193
+ // Add transition end listener
194
+ _.contentPanel.addEventListener('transitionend', _.#handleTransitionEnd);
195
+ }
196
+
197
+ /**
198
+ * Detach event listeners
199
+ * @private
200
+ */
201
+ #detachListeners() {
202
+ const _ = this;
203
+ if (_.contentPanel) {
204
+ _.contentPanel.removeEventListener('transitionend', _.#handleTransitionEnd);
205
+ }
206
+ }
207
+
208
+ /**
209
+ * Binds keyboard events for accessibility
210
+ * @private
211
+ */
212
+ #bindKeyboard() {
213
+ this.addEventListener('keydown', (e) => {
214
+ if (e.key === 'Escape') {
215
+ this.hide();
216
+ }
217
+ });
218
+ }
219
+
220
+ /**
221
+ * Handle cart item removal
222
+ * @private
223
+ */
224
+ #handleCartItemRemove(e) {
225
+ const { cartKey, element } = e.detail;
226
+
227
+ // Set item to processing state
228
+ element.setState('processing');
229
+
230
+ // Remove item by setting quantity to 0
231
+ this.updateCartItem(cartKey, 0)
232
+ .then((updatedCart) => {
233
+ if (updatedCart && !updatedCart.error) {
234
+ // Success - remove with animation
235
+ element.destroyYourself();
236
+ this.#currentCart = updatedCart;
237
+ this.#updateCartItems(updatedCart);
238
+
239
+ // Emit cart updated and data changed events
240
+ this.#emit('cart-dialog:updated', { cart: updatedCart });
241
+ this.#emit('cart-dialog:data-changed', updatedCart);
242
+ } else {
243
+ // Error - reset to ready state
244
+ element.setState('ready');
245
+ console.error('Failed to remove cart item:', cartKey);
246
+ }
247
+ })
248
+ .catch((error) => {
249
+ // Error - reset to ready state
250
+ element.setState('ready');
251
+ console.error('Error removing cart item:', error);
252
+ });
253
+ }
254
+
255
+ /**
256
+ * Handle cart item quantity change
257
+ * @private
258
+ */
259
+ #handleCartItemQuantityChange(e) {
260
+ const { cartKey, quantity, element } = e.detail;
261
+
262
+ // Set item to processing state
263
+ element.setState('processing');
264
+
265
+ // Update item quantity
266
+ this.updateCartItem(cartKey, quantity)
267
+ .then((updatedCart) => {
268
+ if (updatedCart && !updatedCart.error) {
269
+ // Success - update cart data
270
+ this.#currentCart = updatedCart;
271
+ this.#updateCartItems(updatedCart);
272
+ element.setState('ready');
273
+
274
+ // Emit cart updated and data changed events
275
+ this.#emit('cart-dialog:updated', { cart: updatedCart });
276
+ this.#emit('cart-dialog:data-changed', updatedCart);
277
+ } else {
278
+ // Error - reset to ready state
279
+ element.setState('ready');
280
+ console.error('Failed to update cart item quantity:', cartKey, quantity);
281
+ }
282
+ })
283
+ .catch((error) => {
284
+ // Error - reset to ready state
285
+ element.setState('ready');
286
+ console.error('Error updating cart item quantity:', error);
287
+ });
288
+ }
289
+
290
+ /**
291
+ * Update cart items
292
+ * @private
293
+ */
294
+ #updateCartItems(cart = null) {
295
+ // Placeholder for cart item updates
296
+ // Could be used to sync cart items with server data
297
+ cart || this.#currentCart;
298
+ }
299
+
300
+ /**
301
+ * Fetch current cart data from server
302
+ * @returns {Promise<Object>} Cart data object
303
+ */
304
+ getCart() {
305
+ return fetch('/cart.json', {
306
+ crossDomain: true,
307
+ credentials: 'same-origin',
308
+ })
309
+ .then((response) => {
310
+ if (!response.ok) {
311
+ throw Error(response.statusText);
312
+ }
313
+ return response.json();
314
+ })
315
+ .catch((error) => {
316
+ console.error('Error fetching cart:', error);
317
+ return { error: true, message: error.message };
318
+ });
319
+ }
320
+
321
+ /**
322
+ * Update cart item quantity on server
323
+ * @param {string|number} key - Cart item key/ID
324
+ * @param {number} quantity - New quantity (0 to remove)
325
+ * @returns {Promise<Object>} Updated cart data object
326
+ */
327
+ updateCartItem(key, quantity) {
328
+ return fetch('/cart/change.json', {
329
+ crossDomain: true,
330
+ method: 'POST',
331
+ credentials: 'same-origin',
332
+ body: JSON.stringify({ id: key, quantity: quantity }),
333
+ headers: { 'Content-Type': 'application/json' },
334
+ })
335
+ .then((response) => {
336
+ if (!response.ok) {
337
+ throw Error(response.statusText);
338
+ }
339
+ return response.json();
340
+ })
341
+ .catch((error) => {
342
+ console.error('Error updating cart item:', error);
343
+ return { error: true, message: error.message };
344
+ });
345
+ }
346
+
347
+ /**
348
+ * Refresh cart data from server and update components
349
+ * @returns {Promise<Object>} Cart data object
350
+ */
351
+ refreshCart() {
352
+ return this.getCart().then((cartData) => {
353
+ if (cartData && !cartData.error) {
354
+ this.#currentCart = cartData;
355
+ this.#updateCartItems(cartData);
356
+
357
+ // Emit cart refreshed and data changed events
358
+ this.#emit('cart-dialog:refreshed', { cart: cartData });
359
+ this.#emit('cart-dialog:data-changed', cartData);
360
+ }
361
+ return cartData;
362
+ });
363
+ }
364
+
365
+ /**
366
+ * Shows the cart dialog and traps focus within it
367
+ * @param {HTMLElement} [triggerEl=null] - The element that triggered the cart dialog
368
+ * @fires CartDialog#show - Fired when the cart dialog has been shown
369
+ */
370
+ show(triggerEl = null) {
371
+ const _ = this;
372
+ _.triggerEl = triggerEl || false;
373
+
374
+ // Remove the hidden class first to ensure content is rendered
375
+ _.contentPanel.classList.remove('hidden');
376
+
377
+ // Give the browser a moment to process before starting animation
378
+ requestAnimationFrame(() => {
379
+ // Update ARIA states
380
+ _.setAttribute('aria-hidden', 'false');
381
+ if (_.triggerEl) {
382
+ _.triggerEl.setAttribute('aria-expanded', 'true');
383
+ }
384
+
385
+ // Lock body scrolling and save scroll position
386
+ _.#lockScroll();
387
+
388
+ // Focus management
389
+ const firstFocusable = _.querySelector(
390
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
391
+ );
392
+ if (firstFocusable) {
393
+ requestAnimationFrame(() => {
394
+ firstFocusable.focus();
395
+ });
396
+ }
397
+
398
+ // Refresh cart data when showing
399
+ _.refreshCart();
400
+
401
+ // Emit show event - cart dialog is now visible
402
+ _.#emit('cart-dialog:show', { triggerElement: _.triggerEl });
403
+ });
404
+ }
405
+
406
+ /**
407
+ * Hides the cart dialog and restores focus
408
+ * @fires CartDialog#hide - Fired when the cart dialog has started hiding (transition begins)
409
+ * @fires CartDialog#afterHide - Fired when the cart dialog has completed its hide transition
410
+ */
411
+ hide() {
412
+ const _ = this;
413
+
414
+ // Restore body scroll and scroll position
415
+ _.#restoreScroll();
416
+
417
+ // Update ARIA states
418
+ if (_.triggerEl) {
419
+ // remove focus from modal panel first
420
+ _.triggerEl.focus();
421
+ // mark trigger as no longer expanded
422
+ _.triggerEl.setAttribute('aria-expanded', 'false');
423
+ }
424
+
425
+ // Set aria-hidden to start transition
426
+ // The transitionend event handler will add display:none when complete
427
+ _.setAttribute('aria-hidden', 'true');
428
+
429
+ // Emit hide event - cart dialog is now starting to hide
430
+ _.#emit('cart-dialog:hide', { triggerElement: _.triggerEl });
431
+ }
432
+ }
433
+
434
+ /**
435
+ * Custom element that creates a clickable overlay for the cart dialog
436
+ * @extends HTMLElement
437
+ */
438
+ class CartOverlay extends HTMLElement {
439
+ constructor() {
440
+ super();
441
+ this.setAttribute('tabindex', '-1');
442
+ this.setAttribute('aria-hidden', 'true');
443
+ this.cartDialog = this.closest('cart-dialog');
444
+ this.#attachListeners();
445
+ }
446
+
447
+ #attachListeners() {
448
+ this.addEventListener('click', () => {
449
+ this.cartDialog.hide();
450
+ });
451
+ }
452
+ }
453
+
454
+ /**
455
+ * Custom element that wraps the content of the cart dialog
456
+ * @extends HTMLElement
457
+ */
458
+ class CartPanel extends HTMLElement {
459
+ constructor() {
460
+ super();
461
+ this.setAttribute('role', 'document');
462
+ }
463
+ }
464
+
465
+ customElements.define('cart-dialog', CartDialog);
466
+ customElements.define('cart-overlay', CartOverlay);
467
+ customElements.define('cart-panel', CartPanel);
468
+
469
+ exports.CartDialog = CartDialog;
470
+ exports.CartOverlay = CartOverlay;
471
+ exports.CartPanel = CartPanel;
472
+ exports.default = CartDialog;
473
+ //# sourceMappingURL=cart-panel.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cart-panel.cjs.js","sources":["../src/cart-panel.js"],"sourcesContent":["import './cart-panel.scss';\nimport '@magic-spells/cart-item';\nimport '@magic-spells/focus-trap';\nimport EventEmitter from '@magic-spells/event-emitter';\n\n/**\n * Custom element that creates an accessible modal cart dialog with focus management\n * @extends HTMLElement\n */\nclass CartDialog extends HTMLElement {\n\t#handleTransitionEnd;\n\t#scrollPosition = 0;\n\t#currentCart = null;\n\t#eventEmitter;\n\n\t/**\n\t * Clean up event listeners when component is removed from DOM\n\t */\n\tdisconnectedCallback() {\n\t\tconst _ = this;\n\t\tif (_.contentPanel) {\n\t\t\t_.contentPanel.removeEventListener('transitionend', _.#handleTransitionEnd);\n\t\t}\n\n\t\t// Ensure body scroll is restored if component is removed while open\n\t\tdocument.body.classList.remove('overflow-hidden');\n\t\tthis.#restoreScroll();\n\n\t\t// Detach event listeners\n\t\tthis.#detachListeners();\n\t}\n\n\t/**\n\t * Saves current scroll position and locks body scrolling\n\t * @private\n\t */\n\t#lockScroll() {\n\t\tconst _ = this;\n\t\t// Save current scroll position\n\t\t_.#scrollPosition = window.pageYOffset;\n\n\t\t// Apply fixed position to body\n\t\tdocument.body.classList.add('overflow-hidden');\n\t\tdocument.body.style.top = `-${_.#scrollPosition}px`;\n\t}\n\n\t/**\n\t * Restores scroll position when cart dialog is closed\n\t * @private\n\t */\n\t#restoreScroll() {\n\t\tconst _ = this;\n\t\t// Remove fixed positioning\n\t\tdocument.body.classList.remove('overflow-hidden');\n\t\tdocument.body.style.removeProperty('top');\n\n\t\t// Restore scroll position\n\t\twindow.scrollTo(0, _.#scrollPosition);\n\t}\n\n\t/**\n\t * Initializes the cart dialog, sets up focus trap and overlay\n\t */\n\tconstructor() {\n\t\tsuper();\n\t\tconst _ = this;\n\t\t_.id = _.getAttribute('id');\n\t\t_.setAttribute('role', 'dialog');\n\t\t_.setAttribute('aria-modal', 'true');\n\t\t_.setAttribute('aria-hidden', 'true');\n\n\t\t_.triggerEl = null;\n\n\t\t// Initialize event emitter\n\t\t_.#eventEmitter = new EventEmitter();\n\n\t\t// Create a handler for transition end events\n\t\t_.#handleTransitionEnd = (e) => {\n\t\t\tif (e.propertyName === 'opacity' && _.getAttribute('aria-hidden') === 'true') {\n\t\t\t\t_.contentPanel.classList.add('hidden');\n\n\t\t\t\t// Emit afterHide event - cart dialog has completed its transition\n\t\t\t\t_.#emit('cart-dialog:afterHide', { triggerElement: _.triggerEl });\n\t\t\t}\n\t\t};\n\t}\n\n\tconnectedCallback() {\n\t\tconst _ = this;\n\n\t\t// Now that we're in the DOM, find the content panel and set up focus trap\n\t\t_.contentPanel = _.querySelector('cart-panel');\n\n\t\tif (!_.contentPanel) {\n\t\t\tconsole.error('cart-panel element not found inside cart-dialog');\n\t\t\treturn;\n\t\t}\n\n\t\t_.focusTrap = document.createElement('focus-trap');\n\n\t\t// Ensure we have labelledby and describedby references\n\t\tif (!_.getAttribute('aria-labelledby')) {\n\t\t\tconst heading = _.querySelector('h1, h2, h3');\n\t\t\tif (heading && !heading.id) {\n\t\t\t\theading.id = `${_.id}-title`;\n\t\t\t}\n\t\t\tif (heading?.id) {\n\t\t\t\t_.setAttribute('aria-labelledby', heading.id);\n\t\t\t}\n\t\t}\n\n\t\t_.contentPanel.parentNode.insertBefore(_.focusTrap, _.contentPanel);\n\t\t_.focusTrap.appendChild(_.contentPanel);\n\n\t\t_.focusTrap.setupTrap();\n\n\t\t// Add modal overlay\n\t\t_.prepend(document.createElement('cart-overlay'));\n\t\t_.#attachListeners();\n\t\t_.#bindKeyboard();\n\t}\n\n\t/**\n\t * Event emitter method - Add an event listener with a cleaner API\n\t * @param {string} eventName - Name of the event to listen for\n\t * @param {Function} callback - Callback function to execute when event is fired\n\t * @returns {CartDialog} Returns this for method chaining\n\t */\n\ton(eventName, callback) {\n\t\tthis.#eventEmitter.on(eventName, callback);\n\t\treturn this;\n\t}\n\n\t/**\n\t * Event emitter method - Remove an event listener\n\t * @param {string} eventName - Name of the event to stop listening for\n\t * @param {Function} callback - Callback function to remove\n\t * @returns {CartDialog} Returns this for method chaining\n\t */\n\toff(eventName, callback) {\n\t\tthis.#eventEmitter.off(eventName, callback);\n\t\treturn this;\n\t}\n\n\t/**\n\t * Internal method to emit events via the event emitter\n\t * @param {string} eventName - Name of the event to emit\n\t * @param {*} [data] - Optional data to include with the event\n\t * @private\n\t */\n\t#emit(eventName, data = null) {\n\t\tthis.#eventEmitter.emit(eventName, data);\n\t}\n\n\t/**\n\t * Attach event listeners for cart dialog functionality\n\t * @private\n\t */\n\t#attachListeners() {\n\t\tconst _ = this;\n\n\t\t// Handle trigger buttons\n\t\tdocument.addEventListener('click', (e) => {\n\t\t\tconst trigger = e.target.closest(`[aria-controls=\"${_.id}\"]`);\n\t\t\tif (!trigger) return;\n\n\t\t\tif (trigger.getAttribute('data-prevent-default') === 'true') {\n\t\t\t\te.preventDefault();\n\t\t\t}\n\n\t\t\t_.show(trigger);\n\t\t});\n\n\t\t// Handle close buttons\n\t\t_.addEventListener('click', (e) => {\n\t\t\tif (!e.target.closest('[data-action=\"hide-cart\"]')) return;\n\t\t\t_.hide();\n\t\t});\n\n\t\t// Handle cart item remove events\n\t\t_.addEventListener('cart-item:remove', (e) => {\n\t\t\t_.#handleCartItemRemove(e);\n\t\t});\n\n\t\t// Handle cart item quantity change events\n\t\t_.addEventListener('cart-item:quantity-change', (e) => {\n\t\t\t_.#handleCartItemQuantityChange(e);\n\t\t});\n\n\t\t// Add transition end listener\n\t\t_.contentPanel.addEventListener('transitionend', _.#handleTransitionEnd);\n\t}\n\n\t/**\n\t * Detach event listeners\n\t * @private\n\t */\n\t#detachListeners() {\n\t\tconst _ = this;\n\t\tif (_.contentPanel) {\n\t\t\t_.contentPanel.removeEventListener('transitionend', _.#handleTransitionEnd);\n\t\t}\n\t}\n\n\t/**\n\t * Binds keyboard events for accessibility\n\t * @private\n\t */\n\t#bindKeyboard() {\n\t\tthis.addEventListener('keydown', (e) => {\n\t\t\tif (e.key === 'Escape') {\n\t\t\t\tthis.hide();\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Handle cart item removal\n\t * @private\n\t */\n\t#handleCartItemRemove(e) {\n\t\tconst { cartKey, element } = e.detail;\n\n\t\t// Set item to processing state\n\t\telement.setState('processing');\n\n\t\t// Remove item by setting quantity to 0\n\t\tthis.updateCartItem(cartKey, 0)\n\t\t\t.then((updatedCart) => {\n\t\t\t\tif (updatedCart && !updatedCart.error) {\n\t\t\t\t\t// Success - remove with animation\n\t\t\t\t\telement.destroyYourself();\n\t\t\t\t\tthis.#currentCart = updatedCart;\n\t\t\t\t\tthis.#updateCartItems(updatedCart);\n\n\t\t\t\t\t// Emit cart updated and data changed events\n\t\t\t\t\tthis.#emit('cart-dialog:updated', { cart: updatedCart });\n\t\t\t\t\tthis.#emit('cart-dialog:data-changed', updatedCart);\n\t\t\t\t} else {\n\t\t\t\t\t// Error - reset to ready state\n\t\t\t\t\telement.setState('ready');\n\t\t\t\t\tconsole.error('Failed to remove cart item:', cartKey);\n\t\t\t\t}\n\t\t\t})\n\t\t\t.catch((error) => {\n\t\t\t\t// Error - reset to ready state\n\t\t\t\telement.setState('ready');\n\t\t\t\tconsole.error('Error removing cart item:', error);\n\t\t\t});\n\t}\n\n\t/**\n\t * Handle cart item quantity change\n\t * @private\n\t */\n\t#handleCartItemQuantityChange(e) {\n\t\tconst { cartKey, quantity, element } = e.detail;\n\n\t\t// Set item to processing state\n\t\telement.setState('processing');\n\n\t\t// Update item quantity\n\t\tthis.updateCartItem(cartKey, quantity)\n\t\t\t.then((updatedCart) => {\n\t\t\t\tif (updatedCart && !updatedCart.error) {\n\t\t\t\t\t// Success - update cart data\n\t\t\t\t\tthis.#currentCart = updatedCart;\n\t\t\t\t\tthis.#updateCartItems(updatedCart);\n\t\t\t\t\telement.setState('ready');\n\n\t\t\t\t\t// Emit cart updated and data changed events\n\t\t\t\t\tthis.#emit('cart-dialog:updated', { cart: updatedCart });\n\t\t\t\t\tthis.#emit('cart-dialog:data-changed', updatedCart);\n\t\t\t\t} else {\n\t\t\t\t\t// Error - reset to ready state\n\t\t\t\t\telement.setState('ready');\n\t\t\t\t\tconsole.error('Failed to update cart item quantity:', cartKey, quantity);\n\t\t\t\t}\n\t\t\t})\n\t\t\t.catch((error) => {\n\t\t\t\t// Error - reset to ready state\n\t\t\t\telement.setState('ready');\n\t\t\t\tconsole.error('Error updating cart item quantity:', error);\n\t\t\t});\n\t}\n\n\t/**\n\t * Update cart items\n\t * @private\n\t */\n\t#updateCartItems(cart = null) {\n\t\t// Placeholder for cart item updates\n\t\t// Could be used to sync cart items with server data\n\t\tconst cartData = cart || this.#currentCart;\n\t\tif (cartData) {\n\t\t\t// Future implementation: update cart item components\n\t\t}\n\t}\n\n\t/**\n\t * Fetch current cart data from server\n\t * @returns {Promise<Object>} Cart data object\n\t */\n\tgetCart() {\n\t\treturn fetch('/cart.json', {\n\t\t\tcrossDomain: true,\n\t\t\tcredentials: 'same-origin',\n\t\t})\n\t\t\t.then((response) => {\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\tthrow Error(response.statusText);\n\t\t\t\t}\n\t\t\t\treturn response.json();\n\t\t\t})\n\t\t\t.catch((error) => {\n\t\t\t\tconsole.error('Error fetching cart:', error);\n\t\t\t\treturn { error: true, message: error.message };\n\t\t\t});\n\t}\n\n\t/**\n\t * Update cart item quantity on server\n\t * @param {string|number} key - Cart item key/ID\n\t * @param {number} quantity - New quantity (0 to remove)\n\t * @returns {Promise<Object>} Updated cart data object\n\t */\n\tupdateCartItem(key, quantity) {\n\t\treturn fetch('/cart/change.json', {\n\t\t\tcrossDomain: true,\n\t\t\tmethod: 'POST',\n\t\t\tcredentials: 'same-origin',\n\t\t\tbody: JSON.stringify({ id: key, quantity: quantity }),\n\t\t\theaders: { 'Content-Type': 'application/json' },\n\t\t})\n\t\t\t.then((response) => {\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\tthrow Error(response.statusText);\n\t\t\t\t}\n\t\t\t\treturn response.json();\n\t\t\t})\n\t\t\t.catch((error) => {\n\t\t\t\tconsole.error('Error updating cart item:', error);\n\t\t\t\treturn { error: true, message: error.message };\n\t\t\t});\n\t}\n\n\t/**\n\t * Refresh cart data from server and update components\n\t * @returns {Promise<Object>} Cart data object\n\t */\n\trefreshCart() {\n\t\treturn this.getCart().then((cartData) => {\n\t\t\tif (cartData && !cartData.error) {\n\t\t\t\tthis.#currentCart = cartData;\n\t\t\t\tthis.#updateCartItems(cartData);\n\n\t\t\t\t// Emit cart refreshed and data changed events\n\t\t\t\tthis.#emit('cart-dialog:refreshed', { cart: cartData });\n\t\t\t\tthis.#emit('cart-dialog:data-changed', cartData);\n\t\t\t}\n\t\t\treturn cartData;\n\t\t});\n\t}\n\n\t/**\n\t * Shows the cart dialog and traps focus within it\n\t * @param {HTMLElement} [triggerEl=null] - The element that triggered the cart dialog\n\t * @fires CartDialog#show - Fired when the cart dialog has been shown\n\t */\n\tshow(triggerEl = null) {\n\t\tconst _ = this;\n\t\t_.triggerEl = triggerEl || false;\n\n\t\t// Remove the hidden class first to ensure content is rendered\n\t\t_.contentPanel.classList.remove('hidden');\n\n\t\t// Give the browser a moment to process before starting animation\n\t\trequestAnimationFrame(() => {\n\t\t\t// Update ARIA states\n\t\t\t_.setAttribute('aria-hidden', 'false');\n\t\t\tif (_.triggerEl) {\n\t\t\t\t_.triggerEl.setAttribute('aria-expanded', 'true');\n\t\t\t}\n\n\t\t\t// Lock body scrolling and save scroll position\n\t\t\t_.#lockScroll();\n\n\t\t\t// Focus management\n\t\t\tconst firstFocusable = _.querySelector(\n\t\t\t\t'button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])'\n\t\t\t);\n\t\t\tif (firstFocusable) {\n\t\t\t\trequestAnimationFrame(() => {\n\t\t\t\t\tfirstFocusable.focus();\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// Refresh cart data when showing\n\t\t\t_.refreshCart();\n\n\t\t\t// Emit show event - cart dialog is now visible\n\t\t\t_.#emit('cart-dialog:show', { triggerElement: _.triggerEl });\n\t\t});\n\t}\n\n\t/**\n\t * Hides the cart dialog and restores focus\n\t * @fires CartDialog#hide - Fired when the cart dialog has started hiding (transition begins)\n\t * @fires CartDialog#afterHide - Fired when the cart dialog has completed its hide transition\n\t */\n\thide() {\n\t\tconst _ = this;\n\n\t\t// Restore body scroll and scroll position\n\t\t_.#restoreScroll();\n\n\t\t// Update ARIA states\n\t\tif (_.triggerEl) {\n\t\t\t// remove focus from modal panel first\n\t\t\t_.triggerEl.focus();\n\t\t\t// mark trigger as no longer expanded\n\t\t\t_.triggerEl.setAttribute('aria-expanded', 'false');\n\t\t}\n\n\t\t// Set aria-hidden to start transition\n\t\t// The transitionend event handler will add display:none when complete\n\t\t_.setAttribute('aria-hidden', 'true');\n\n\t\t// Emit hide event - cart dialog is now starting to hide\n\t\t_.#emit('cart-dialog:hide', { triggerElement: _.triggerEl });\n\t}\n}\n\n/**\n * Custom element that creates a clickable overlay for the cart dialog\n * @extends HTMLElement\n */\nclass CartOverlay extends HTMLElement {\n\tconstructor() {\n\t\tsuper();\n\t\tthis.setAttribute('tabindex', '-1');\n\t\tthis.setAttribute('aria-hidden', 'true');\n\t\tthis.cartDialog = this.closest('cart-dialog');\n\t\tthis.#attachListeners();\n\t}\n\n\t#attachListeners() {\n\t\tthis.addEventListener('click', () => {\n\t\t\tthis.cartDialog.hide();\n\t\t});\n\t}\n}\n\n/**\n * Custom element that wraps the content of the cart dialog\n * @extends HTMLElement\n */\nclass CartPanel extends HTMLElement {\n\tconstructor() {\n\t\tsuper();\n\t\tthis.setAttribute('role', 'document');\n\t}\n}\n\ncustomElements.define('cart-dialog', CartDialog);\ncustomElements.define('cart-overlay', CartOverlay);\ncustomElements.define('cart-panel', CartPanel);\n\nexport { CartDialog, CartOverlay, CartPanel };\nexport default CartDialog;\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,171 @@
1
+ cart-item {
2
+ --cart-item-processing-duration: 250ms;
3
+ --cart-item-destroying-duration: 600ms;
4
+ --cart-item-shadow-color: rgba(0, 0, 0, 0.15);
5
+ --cart-item-shadow-color-strong: rgba(0, 0, 0, 0.5);
6
+ --cart-item-processing-bg: rgba(100, 100, 100, 0.2);
7
+ --cart-item-destroying-bg: rgba(0, 0, 0, 0.1);
8
+ --cart-item-processing-scale: 0.98;
9
+ --cart-item-destroying-scale: 0.85;
10
+ --cart-item-processing-blur: 1px;
11
+ --cart-item-destroying-blur: 10px;
12
+ --cart-item-destroying-opacity: 0.2;
13
+ --cart-item-destroying-brightness: 0.6;
14
+ --cart-item-destroying-saturate: 0.3;
15
+ display: block;
16
+ position: relative;
17
+ overflow: hidden;
18
+ padding: 0px;
19
+ box-shadow: inset 0px 0px 0px rgba(0, 0, 0, 0);
20
+ transition: filter var(--cart-item-processing-duration) ease-out, background-color var(--cart-item-processing-duration) ease-out, box-shadow var(--cart-item-processing-duration) ease-out;
21
+ }
22
+ cart-item::after {
23
+ content: "";
24
+ display: block;
25
+ position: absolute;
26
+ background: rgba(0, 0, 0, 0);
27
+ width: 100%;
28
+ pointer-events: none;
29
+ height: 100%;
30
+ top: 0px;
31
+ left: 0px;
32
+ transition: background-color var(--cart-item-processing-duration) ease;
33
+ }
34
+ cart-item[data-state=ready] cart-item-content {
35
+ transform: scale(1);
36
+ filter: blur(0px);
37
+ opacity: 1;
38
+ }
39
+ cart-item[data-state=ready] cart-item-processing {
40
+ opacity: 0;
41
+ visibility: hidden;
42
+ }
43
+ cart-item[data-state=processing] {
44
+ box-shadow: inset 0px 2px 10px var(--cart-item-shadow-color);
45
+ }
46
+ cart-item[data-state=processing]::after {
47
+ background: rgba(0, 0, 0, 0.15);
48
+ }
49
+ cart-item[data-state=processing] cart-item-content {
50
+ transform: scale(var(--cart-item-processing-scale));
51
+ filter: blur(var(--cart-item-processing-blur));
52
+ opacity: 0.9;
53
+ pointer-events: none;
54
+ }
55
+ cart-item[data-state=processing] cart-item-processing {
56
+ opacity: 1;
57
+ visibility: visible;
58
+ }
59
+ cart-item[data-state=destroying] {
60
+ background-color: var(--cart-item-destroying-bg);
61
+ box-shadow: inset 0px 4px 20px var(--cart-item-shadow-color-strong);
62
+ margin-top: 0px;
63
+ margin-bottom: 0px;
64
+ transition: filter var(--cart-item-destroying-duration) ease, background-color var(--cart-item-destroying-duration) ease, box-shadow var(--cart-item-destroying-duration) ease, margin var(--cart-item-destroying-duration) ease;
65
+ }
66
+ cart-item[data-state=destroying]::after {
67
+ background: rgba(0, 0, 0, 0.9);
68
+ transition: background-color var(--cart-item-destroying-duration) ease;
69
+ }
70
+ cart-item[data-state=destroying] cart-item-content {
71
+ transition: transform var(--cart-item-destroying-duration) ease, filter var(--cart-item-destroying-duration) ease, opacity var(--cart-item-destroying-duration) ease;
72
+ transform: scale(var(--cart-item-destroying-scale));
73
+ filter: blur(var(--cart-item-destroying-blur)) saturate(var(--cart-item-destroying-saturate));
74
+ opacity: var(--cart-item-destroying-opacity);
75
+ pointer-events: none;
76
+ }
77
+ cart-item[data-state=destroying] cart-item-processing {
78
+ opacity: 0;
79
+ transition: opacity var(--cart-item-processing-duration) ease;
80
+ }
81
+
82
+ cart-item-content {
83
+ display: block;
84
+ transition: transform var(--cart-item-processing-duration) ease-out, filter var(--cart-item-processing-duration) ease-out, opacity var(--cart-item-processing-duration) ease-out;
85
+ }
86
+
87
+ cart-item-processing {
88
+ position: absolute;
89
+ top: 0;
90
+ left: 0;
91
+ right: 0;
92
+ bottom: 0;
93
+ display: flex;
94
+ align-items: center;
95
+ justify-content: center;
96
+ background: transparent;
97
+ opacity: 0;
98
+ visibility: hidden;
99
+ transition: opacity var(--cart-item-processing-duration) ease-out, visibility var(--cart-item-processing-duration) ease-out;
100
+ z-index: 10;
101
+ }
102
+
103
+ :root {
104
+ --cart-dialog-z-index: 1000;
105
+ --cart-overlay-z-index: 1000;
106
+ --cart-panel-z-index: 1001;
107
+ --cart-panel-width: min(400px, 90vw);
108
+ --cart-overlay-background: rgba(0, 0, 0, 0.15);
109
+ --cart-overlay-backdrop-filter: blur(4px);
110
+ --cart-panel-background: #ffffff;
111
+ --cart-panel-shadow: -5px 0 25px rgba(0, 0, 0, 0.15);
112
+ --cart-panel-border-radius: 0;
113
+ --cart-transition-duration: 350ms;
114
+ --cart-transition-timing: cubic-bezier(0.4, 0, 0.2, 1);
115
+ }
116
+
117
+ cart-dialog {
118
+ display: contents;
119
+ }
120
+ cart-dialog[aria-hidden=false] cart-overlay,
121
+ cart-dialog[aria-hidden=false] cart-panel {
122
+ pointer-events: auto;
123
+ opacity: 1;
124
+ }
125
+ cart-dialog[aria-hidden=false] cart-panel {
126
+ transform: translateX(0);
127
+ }
128
+
129
+ cart-overlay {
130
+ position: fixed;
131
+ top: 0;
132
+ left: 0;
133
+ width: 100vw;
134
+ height: 100vh;
135
+ opacity: 0;
136
+ pointer-events: none;
137
+ z-index: var(--cart-overlay-z-index);
138
+ background-color: var(--cart-overlay-background);
139
+ backdrop-filter: var(--cart-overlay-backdrop-filter);
140
+ transition: opacity var(--cart-transition-duration) var(--cart-transition-timing), backdrop-filter var(--cart-transition-duration) var(--cart-transition-timing);
141
+ }
142
+
143
+ cart-panel {
144
+ position: fixed;
145
+ top: 0;
146
+ right: 0;
147
+ width: var(--cart-panel-width);
148
+ height: 100vh;
149
+ opacity: 0;
150
+ transform: translateX(100%);
151
+ pointer-events: none;
152
+ z-index: var(--cart-panel-z-index);
153
+ background: var(--cart-panel-background);
154
+ box-shadow: var(--cart-panel-shadow);
155
+ border-radius: var(--cart-panel-border-radius);
156
+ overflow: hidden;
157
+ transition: opacity var(--cart-transition-duration) var(--cart-transition-timing), transform var(--cart-transition-duration) var(--cart-transition-timing);
158
+ }
159
+ cart-panel.hidden {
160
+ display: none;
161
+ }
162
+
163
+ body.overflow-hidden {
164
+ overflow: hidden;
165
+ position: fixed;
166
+ width: 100%;
167
+ height: 100%;
168
+ left: 0;
169
+ right: 0;
170
+ margin: 0;
171
+ }