zz-shopify-components 0.22.0 → 0.22.1-beta.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.
Files changed (2) hide show
  1. package/assets/zz-modal.js +100 -5
  2. package/package.json +1 -1
@@ -4,11 +4,30 @@
4
4
  - Attribute reflection and programmatic API: show(), hide(), toggle()
5
5
  - Options via attributes: close-on-esc, close-on-backdrop, scroll-lock, inert-others
6
6
  - Global open/close triggers: [data-zz-modal-target], [data-zz-modal-close]
7
+ - Analytics tracking: modal open/close/confirm events
7
8
  */
8
9
 
9
10
  (() => {
10
11
  if (customElements.get('zz-modal')) return;
11
12
 
13
+ // Analytics tracking utility
14
+ const trackModalEvent = (eventType, modalId, additionalData = {}) => {
15
+ try {
16
+ const eventData = {
17
+ event: 'modal_interaction',
18
+ modal_action: eventType,
19
+ modal_id: modalId || 'unknown',
20
+ timestamp: new Date().toISOString(),
21
+ ...additionalData
22
+ };
23
+ window.zzAnalytics && window.zzAnalytics.trackEvent(eventType, eventData);
24
+
25
+ console.log('[ZZ-Modal Analytics]', eventData);
26
+ } catch (error) {
27
+ console.warn('[ZZ-Modal Analytics] Tracking failed:', error);
28
+ }
29
+ };
30
+
12
31
  const STYLE_TEXT = `
13
32
  :host {
14
33
  position: relative;
@@ -155,6 +174,9 @@
155
174
  this._onClick = this._onClick.bind(this);
156
175
  this._previousActive = null;
157
176
  this._mouseDownInsidePanel = false;
177
+ this._openTimestamp = null;
178
+ this._lastTriggerSource = null;
179
+ this._lastCloseMethod = null;
158
180
 
159
181
  const shadow = this.attachShadow({ mode: 'open' });
160
182
  const style = document.createElement('style');
@@ -176,7 +198,10 @@
176
198
 
177
199
  connectedCallback() {
178
200
  // Delegated internal events
179
- this._closeBtn.addEventListener('click', () => this.hide());
201
+ this._closeBtn.addEventListener('click', () => {
202
+ this._lastCloseMethod = 'close_button';
203
+ this.hide();
204
+ });
180
205
  this._dialog.addEventListener('close', this._onNativeClose);
181
206
  this._dialog.addEventListener('mousedown', this._onMouseDown);
182
207
  this._dialog.addEventListener('click', this._onClick);
@@ -214,8 +239,18 @@
214
239
  get open() { return this._dialog?.open || this.hasAttribute('open'); }
215
240
  set open(val) { if (val) this.setAttribute('open', ''); else this.removeAttribute('open'); }
216
241
 
217
- show() { this._openInternal(true); }
218
- hide() { this._closeInternal(true); }
242
+ show() {
243
+ if (!this._lastTriggerSource) {
244
+ this._lastTriggerSource = 'unknown';
245
+ }
246
+ this._openInternal(true);
247
+ }
248
+ hide() {
249
+ if (!this._lastCloseMethod) {
250
+ this._lastCloseMethod = 'unknown';
251
+ }
252
+ this._closeInternal(true);
253
+ }
219
254
  toggle() { this.open ? this.hide() : this.show(); }
220
255
 
221
256
  // Aliases
@@ -229,6 +264,7 @@
229
264
  _openInternal(emit = false) {
230
265
  if (this._dialog.open) return;
231
266
  this._previousActive = document.activeElement;
267
+ this._openTimestamp = Date.now();
232
268
 
233
269
  // Scroll lock
234
270
  if (!this.hasAttribute('no-scroll-lock')) {
@@ -261,7 +297,14 @@
261
297
  // Focus first focusable within panel
262
298
  this._focusInitial();
263
299
 
264
- if (emit) this._emit('zz-modal:open');
300
+ if (emit) {
301
+ this._emit('zz-modal:open');
302
+ const modalId = this.getAttribute('data-modal-id') || this.id || 'unnamed-modal';
303
+ trackModalEvent('modal_open', modalId, {
304
+ trigger_source: this._getTriggerSource(),
305
+ modal_type: this.getAttribute('data-modal-type') || 'default'
306
+ });
307
+ }
265
308
  }
266
309
 
267
310
  _closeInternal(emit = false) {
@@ -292,13 +335,26 @@
292
335
  }
293
336
  this._previousActive = null;
294
337
 
295
- if (emit) this._emit('zz-modal:close');
338
+ if (emit) {
339
+ this._emit('zz-modal:close');
340
+ const modalId = this.getAttribute('data-modal-id') || this.id || 'unnamed-modal';
341
+ trackModalEvent('modal_close', modalId, {
342
+ close_method: this._getCloseMethod(),
343
+ modal_type: this.getAttribute('data-modal-type') || 'default',
344
+ time_open: this._getTimeOpen()
345
+ });
346
+
347
+ this._lastTriggerSource = null;
348
+ this._lastCloseMethod = null;
349
+ this._openTimestamp = null;
350
+ }
296
351
  }
297
352
 
298
353
  _onKeydown(e) {
299
354
  const escAllowed = !this.hasAttribute('no-esc-close');
300
355
  if (e.key === 'Escape' && escAllowed) {
301
356
  e.stopPropagation();
357
+ this._lastCloseMethod = 'escape_key';
302
358
  this.hide();
303
359
  }
304
360
 
@@ -342,6 +398,7 @@
342
398
  const path = e.composedPath();
343
399
  const clickInsidePanel = path.includes(this._panel);
344
400
  if (!clickInsidePanel && !this._mouseDownInsidePanel) {
401
+ this._lastCloseMethod = 'backdrop_click';
345
402
  this.hide();
346
403
  }
347
404
  this._mouseDownInsidePanel = false;
@@ -401,6 +458,30 @@
401
458
  if (isInert) el.setAttribute('inert', ''); else el.removeAttribute('inert');
402
459
  });
403
460
  }
461
+
462
+ // Analytics helper methods
463
+ _getTriggerSource() {
464
+ return this._lastTriggerSource || 'programmatic';
465
+ }
466
+
467
+ _getCloseMethod() {
468
+ return this._lastCloseMethod || 'unknown';
469
+ }
470
+
471
+ _getTimeOpen() {
472
+ if (!this._openTimestamp) return 0;
473
+ return Date.now() - this._openTimestamp;
474
+ }
475
+
476
+ trackConfirm(confirmType = 'default', additionalData = {}) {
477
+ const modalId = this.getAttribute('data-modal-id') || this.id || 'unnamed-modal';
478
+ trackModalEvent('modal_confirm', modalId, {
479
+ confirm_type: confirmType,
480
+ modal_type: this.getAttribute('data-modal-type') || 'default',
481
+ time_to_confirm: this._getTimeOpen(),
482
+ ...additionalData
483
+ });
484
+ }
404
485
  }
405
486
 
406
487
  customElements.define('zz-modal', ZZModal);
@@ -414,6 +495,7 @@
414
495
  const modal = document.querySelector(selector);
415
496
  if (modal && modal.show) {
416
497
  e.preventDefault();
498
+ modal._lastTriggerSource = 'data_attribute';
417
499
  modal.show();
418
500
  }
419
501
  }
@@ -424,9 +506,22 @@
424
506
  const hostModal = closeTrigger.closest('zz-modal');
425
507
  if (hostModal && hostModal.hide) {
426
508
  e.preventDefault();
509
+ hostModal._lastCloseMethod = 'data_attribute';
427
510
  hostModal.hide();
428
511
  }
429
512
  }
513
+
514
+ const confirmTrigger = e.target.closest('[data-zz-modal-confirm]');
515
+ if (confirmTrigger) {
516
+ const hostModal = confirmTrigger.closest('zz-modal');
517
+ if (hostModal && hostModal.trackConfirm) {
518
+ const confirmType = confirmTrigger.getAttribute('data-zz-modal-confirm') || 'default';
519
+ hostModal.trackConfirm(confirmType, {
520
+ button_text: confirmTrigger.textContent?.trim() || '',
521
+ button_class: confirmTrigger.className || ''
522
+ });
523
+ }
524
+ }
430
525
  }, { capture: true });
431
526
  })();
432
527
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zz-shopify-components",
3
- "version": "0.22.0",
3
+ "version": "0.22.1-beta.0",
4
4
  "description": "Reusable Shopify components for theme projects",
5
5
  "keywords": [
6
6
  "shopify",