@lime-bundles/widget 0.1.1 → 1.0.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/dist/index.js CHANGED
@@ -2,18 +2,23 @@
2
2
  import {
3
3
  createStorefrontClient,
4
4
  BUNDLE_METAOBJECT_QUERY,
5
+ BUNDLES_FOR_PRODUCT_QUERY,
6
+ CART_CREATE_MUTATION,
7
+ CART_LINES_ADD_MUTATION,
8
+ SHOP_CUSTOM_CSS_QUERY,
5
9
  parseMetaobjectBundle,
6
- detectCartApi,
7
- createAjaxCartApi,
8
- createStorefrontCartApi,
9
10
  observeImpression,
10
11
  reportImpression,
11
- reportAddToCart
12
+ reportAddToCart,
13
+ injectCustomCss,
14
+ fetchBundlesForProduct
12
15
  } from "@lime-bundles/core";
13
16
 
14
17
  // src/renderers/fixed.ts
15
- import { formatMoney } from "@lime-bundles/core";
16
- function renderFixedBundle(container, bundle, addToCart) {
18
+ import {
19
+ formatMoney
20
+ } from "@lime-bundles/core";
21
+ function renderFixedBundle(container, bundle, onAddToCart) {
17
22
  const currency = bundle.products[0]?.priceRange.minVariantPrice.currencyCode ?? "USD";
18
23
  const title = document.createElement("h3");
19
24
  title.className = "lb-bundle__title";
@@ -54,23 +59,20 @@ function renderFixedBundle(container, bundle, addToCart) {
54
59
  button.className = "lb-bundle__cta";
55
60
  button.textContent = bundle.widgetConfig.ctaText ?? "Add Bundle to Cart";
56
61
  button.setAttribute("part", "button");
57
- button.addEventListener("click", async () => {
58
- button.disabled = true;
59
- button.textContent = "Adding...";
60
- const items = bundle.products.filter((p) => p.variants.nodes.some((v) => v.availableForSale)).map((p) => {
62
+ button.addEventListener("click", () => {
63
+ const lines = bundle.products.filter((p) => p.variants.nodes.some((v) => v.availableForSale)).map((p) => {
61
64
  const variant = p.variants.nodes.find((v) => v.availableForSale);
62
- return { variantId: variant.id, quantity: 1 };
65
+ return {
66
+ merchandiseId: variant.id,
67
+ quantity: 1,
68
+ attributes: [
69
+ { key: "_lime_bundle_gid", value: bundle.id },
70
+ { key: "_lime_bundle_type", value: bundle.bundleType }
71
+ ]
72
+ };
63
73
  });
64
- const result = await addToCart(items);
65
- button.disabled = false;
66
- button.textContent = bundle.widgetConfig.ctaText ?? "Add Bundle to Cart";
67
- if (!result.success) {
68
- const error = document.createElement("p");
69
- error.className = "lb-bundle__error";
70
- error.textContent = result.error ?? "Failed to add to cart";
71
- container.appendChild(error);
72
- setTimeout(() => error.remove(), 5e3);
73
- }
74
+ if (lines.length === 0) return;
75
+ onAddToCart(lines);
74
76
  });
75
77
  container.appendChild(button);
76
78
  }
@@ -81,8 +83,11 @@ function escapeHtml(str) {
81
83
  }
82
84
 
83
85
  // src/renderers/mix-match.ts
84
- import { formatMoney as formatMoney2, validateQuantity } from "@lime-bundles/core";
85
- function renderMixMatchBundle(container, bundle, addToCart) {
86
+ import {
87
+ formatMoney as formatMoney2,
88
+ validateQuantity
89
+ } from "@lime-bundles/core";
90
+ function renderMixMatchBundle(container, bundle, onAddToCart) {
86
91
  const currency = bundle.products[0]?.priceRange.minVariantPrice.currencyCode ?? "USD";
87
92
  const selections = /* @__PURE__ */ new Map();
88
93
  const title = document.createElement("h3");
@@ -146,29 +151,31 @@ function renderMixMatchBundle(container, bundle, addToCart) {
146
151
  button.disabled = true;
147
152
  container.appendChild(button);
148
153
  function updateCta() {
149
- const total = Array.from(selections.values()).reduce((s, v) => s + v.quantity, 0);
150
- const validation = validateQuantity(total, bundle.minQuantity, bundle.maxQuantity);
154
+ const total = Array.from(selections.values()).reduce(
155
+ (s, v) => s + v.quantity,
156
+ 0
157
+ );
158
+ const validation = validateQuantity(
159
+ total,
160
+ bundle.minQuantity,
161
+ bundle.maxQuantity
162
+ );
151
163
  button.disabled = !validation.valid;
152
164
  button.textContent = bundle.widgetConfig.ctaText ?? `Add ${total} Items to Cart`;
153
165
  validationEl.textContent = validation.message ?? "";
154
166
  }
155
167
  updateCta();
156
- button.addEventListener("click", async () => {
157
- button.disabled = true;
158
- button.textContent = "Adding...";
159
- const items = Array.from(selections.entries()).map(([, s]) => ({
160
- variantId: s.variantId,
161
- quantity: s.quantity
168
+ button.addEventListener("click", () => {
169
+ const lines = Array.from(selections.values()).map((s) => ({
170
+ merchandiseId: s.variantId,
171
+ quantity: s.quantity,
172
+ attributes: [
173
+ { key: "_lime_bundle_gid", value: bundle.id },
174
+ { key: "_lime_bundle_type", value: bundle.bundleType }
175
+ ]
162
176
  }));
163
- const result = await addToCart(items);
164
- updateCta();
165
- if (!result.success) {
166
- const error = document.createElement("p");
167
- error.className = "lb-bundle__error";
168
- error.textContent = result.error ?? "Failed to add to cart";
169
- container.appendChild(error);
170
- setTimeout(() => error.remove(), 5e3);
171
- }
177
+ if (lines.length === 0) return;
178
+ onAddToCart(lines);
172
179
  });
173
180
  }
174
181
  function escapeHtml2(str) {
@@ -178,8 +185,11 @@ function escapeHtml2(str) {
178
185
  }
179
186
 
180
187
  // src/renderers/volume.ts
181
- import { formatMoney as formatMoney3, calculateTierSavings } from "@lime-bundles/core";
182
- function renderVolumeBundle(container, bundle, addToCart) {
188
+ import {
189
+ formatMoney as formatMoney3,
190
+ calculateTierSavings
191
+ } from "@lime-bundles/core";
192
+ function renderVolumeBundle(container, bundle, onAddToCart) {
183
193
  const product = bundle.products[0];
184
194
  if (!product) return;
185
195
  const basePrice = parseFloat(product.priceRange.minVariantPrice.amount);
@@ -239,7 +249,11 @@ function renderVolumeBundle(container, bundle, addToCart) {
239
249
  button.setAttribute("part", "button");
240
250
  container.appendChild(button);
241
251
  function updateTiers() {
242
- const savings = calculateTierSavings(bundle.volumeTiers, basePrice, quantity);
252
+ const savings = calculateTierSavings(
253
+ bundle.volumeTiers,
254
+ basePrice,
255
+ quantity
256
+ );
243
257
  tiersDiv.innerHTML = "";
244
258
  for (const ts of savings) {
245
259
  const row = document.createElement("div");
@@ -275,22 +289,19 @@ function renderVolumeBundle(container, bundle, addToCart) {
275
289
  updateTiers();
276
290
  }
277
291
  });
278
- button.addEventListener("click", async () => {
292
+ button.addEventListener("click", () => {
279
293
  const variant = product.variants.nodes.find((v) => v.availableForSale);
280
294
  if (!variant) return;
281
- button.disabled = true;
282
- button.textContent = "Adding...";
283
- const items = [{ variantId: variant.id, quantity }];
284
- const result = await addToCart(items);
285
- button.disabled = false;
286
- updateTiers();
287
- if (!result.success) {
288
- const error = document.createElement("p");
289
- error.className = "lb-bundle__error";
290
- error.textContent = result.error ?? "Failed to add to cart";
291
- container.appendChild(error);
292
- setTimeout(() => error.remove(), 5e3);
293
- }
295
+ onAddToCart([
296
+ {
297
+ merchandiseId: variant.id,
298
+ quantity,
299
+ attributes: [
300
+ { key: "_lime_bundle_gid", value: bundle.id },
301
+ { key: "_lime_bundle_type", value: bundle.bundleType }
302
+ ]
303
+ }
304
+ ]);
294
305
  });
295
306
  }
296
307
  function escapeHtml3(str) {
@@ -366,20 +377,35 @@ var WIDGET_STYLES = `
366
377
  `;
367
378
 
368
379
  // src/lime-bundle.ts
380
+ var cartStorageKey = (shopDomain) => `lb_cart_id:${shopDomain}`;
381
+ function resolveProductHandle(explicit) {
382
+ if (explicit) return explicit.trim() || null;
383
+ if (typeof document !== "undefined") {
384
+ const meta = document.querySelector(
385
+ 'meta[name="shopify:product-handle"]'
386
+ );
387
+ if (meta?.content) return meta.content.trim() || null;
388
+ }
389
+ if (typeof window !== "undefined") {
390
+ const match = window.location.pathname.match(/\/products\/([^/?#]+)/);
391
+ if (match?.[1]) return decodeURIComponent(match[1]);
392
+ }
393
+ return null;
394
+ }
369
395
  var LimeBundleElement = class extends HTMLElement {
370
396
  static observedAttributes = [
371
397
  "shop-domain",
372
398
  "storefront-token",
373
399
  "bundle-gid",
374
- "cart-id",
400
+ "product-handle",
375
401
  "app-url",
376
402
  "analytics",
377
403
  "locale"
378
404
  ];
379
405
  shadow;
380
- bundle = null;
406
+ bundles = [];
381
407
  abortController = null;
382
- impressionCleanup = null;
408
+ impressionCleanups = [];
383
409
  constructor() {
384
410
  super();
385
411
  this.shadow = this.attachShadow({ mode: "open" });
@@ -390,17 +416,16 @@ var LimeBundleElement = class extends HTMLElement {
390
416
  }
391
417
  disconnectedCallback() {
392
418
  this.abortController?.abort();
393
- this.teardownImpression();
419
+ this.teardownImpressions();
394
420
  }
395
- /** Clean up stale bundle state and active impression observer. */
396
- teardownImpression() {
397
- this.impressionCleanup?.();
398
- this.impressionCleanup = null;
421
+ teardownImpressions() {
422
+ for (const cleanup of this.impressionCleanups) cleanup();
423
+ this.impressionCleanups = [];
399
424
  }
400
425
  attributeChangedCallback(name, oldValue, newValue) {
401
426
  if (oldValue === newValue || !this.isConnected) return;
402
- if (name === "bundle-gid" || name === "shop-domain" || name === "storefront-token") {
403
- if (this.shopDomain && this.storefrontToken && this.bundleGid) {
427
+ if (name === "bundle-gid" || name === "product-handle" || name === "shop-domain" || name === "storefront-token") {
428
+ if (this.shopDomain && this.storefrontToken) {
404
429
  this.fetchBundle();
405
430
  }
406
431
  }
@@ -414,8 +439,8 @@ var LimeBundleElement = class extends HTMLElement {
414
439
  get bundleGid() {
415
440
  return this.getAttribute("bundle-gid") ?? "";
416
441
  }
417
- get cartId() {
418
- return this.getAttribute("cart-id") ?? void 0;
442
+ get productHandleAttr() {
443
+ return this.getAttribute("product-handle") ?? "";
419
444
  }
420
445
  get appUrl() {
421
446
  return this.getAttribute("app-url") ?? "";
@@ -424,147 +449,265 @@ var LimeBundleElement = class extends HTMLElement {
424
449
  return this.getAttribute("analytics") !== "false";
425
450
  }
426
451
  async fetchBundle() {
427
- if (!this.shopDomain || !this.storefrontToken || !this.bundleGid) {
428
- this.renderError("Missing required attributes: shop-domain, storefront-token, bundle-gid");
452
+ if (!this.shopDomain || !this.storefrontToken) {
453
+ this.renderError(
454
+ "Missing required attributes: shop-domain, storefront-token"
455
+ );
429
456
  return;
430
457
  }
431
458
  this.abortController?.abort();
432
459
  const controller = new AbortController();
433
460
  this.abortController = controller;
434
461
  this.renderLoading();
462
+ const client = createStorefrontClient({
463
+ shopDomain: this.shopDomain,
464
+ accessToken: this.storefrontToken
465
+ });
435
466
  try {
436
- const client = createStorefrontClient({
437
- shopDomain: this.shopDomain,
438
- accessToken: this.storefrontToken
439
- });
440
- const data = await client.query(
441
- BUNDLE_METAOBJECT_QUERY,
442
- { id: this.bundleGid }
443
- );
467
+ let bundlePromise;
468
+ let singleBundleMode = false;
469
+ if (this.bundleGid) {
470
+ singleBundleMode = true;
471
+ bundlePromise = this.fetchSingleBundle(client, controller.signal);
472
+ } else {
473
+ const handle = resolveProductHandle(this.productHandleAttr);
474
+ if (!handle) {
475
+ this.teardownImpressions();
476
+ this.renderError(
477
+ "No bundle-gid or product-handle provided, and the current URL doesn't match /products/<handle>."
478
+ );
479
+ return;
480
+ }
481
+ bundlePromise = this.fetchProductBundles(
482
+ client,
483
+ controller.signal,
484
+ handle
485
+ );
486
+ }
487
+ const cssPromise = client.query(SHOP_CUSTOM_CSS_QUERY, void 0, {
488
+ signal: controller.signal
489
+ }).catch(() => null);
490
+ await bundlePromise;
444
491
  if (controller.signal.aborted) return;
445
- if (!data.metaobject) {
446
- this.bundle = null;
447
- this.teardownImpression();
492
+ if (singleBundleMode && this.bundles.length === 0) {
493
+ this.teardownImpressions();
448
494
  this.renderError("Bundle not found");
449
495
  return;
450
496
  }
451
- this.bundle = parseMetaobjectBundle(
452
- data.metaobject.id,
453
- data.metaobject.fields
454
- );
455
- if (!this.bundle) {
456
- this.teardownImpression();
457
- this.renderError("Bundle is not active or has expired");
458
- return;
497
+ const css = await cssPromise;
498
+ if (css?.shop?.metafield?.value) {
499
+ injectCustomCss(this.shopDomain, css.shop.metafield.value);
459
500
  }
460
- this.renderBundle();
461
- this.setupImpression();
501
+ this.renderBundles();
462
502
  } catch (err) {
463
503
  if (controller.signal.aborted) return;
464
- this.bundle = null;
465
- this.teardownImpression();
504
+ this.bundles = [];
505
+ this.teardownImpressions();
466
506
  this.renderError(
467
507
  err instanceof Error ? err.message : "Failed to load bundle"
468
508
  );
469
509
  }
470
510
  }
471
- renderBundle() {
472
- if (!this.bundle) return;
473
- const container = document.createElement("div");
474
- container.className = "lb-bundle";
475
- container.setAttribute("role", "region");
476
- container.setAttribute("aria-label", this.bundle.title);
477
- const addToCart = async (items) => {
478
- const apiType = detectCartApi();
479
- const cart = apiType === "ajax" ? createAjaxCartApi(this.bundleGid, this.bundle.bundleType) : createStorefrontCartApi(
480
- createStorefrontClient({
481
- shopDomain: this.shopDomain,
482
- accessToken: this.storefrontToken
483
- }),
484
- this.bundleGid,
485
- this.bundle.bundleType,
486
- this.cartId
487
- );
488
- let result;
489
- try {
490
- result = await cart.addLines(items);
491
- } catch (err) {
492
- result = {
493
- success: false,
494
- error: err instanceof Error ? err.message : "Cart add failed"
495
- };
511
+ async fetchSingleBundle(client, signal) {
512
+ const data = await client.query(
513
+ BUNDLE_METAOBJECT_QUERY,
514
+ { id: this.bundleGid },
515
+ { signal }
516
+ );
517
+ if (!data.metaobject) {
518
+ this.bundles = [];
519
+ return;
520
+ }
521
+ const parsed = parseMetaobjectBundle(
522
+ data.metaobject.id,
523
+ data.metaobject.fields
524
+ );
525
+ this.bundles = parsed ? [parsed] : [];
526
+ }
527
+ async fetchProductBundles(client, signal, productHandle) {
528
+ const data = await client.query(
529
+ BUNDLES_FOR_PRODUCT_QUERY,
530
+ { handle: productHandle },
531
+ { signal }
532
+ );
533
+ if (!data.product) {
534
+ this.bundles = [];
535
+ return;
536
+ }
537
+ const refs = data.product.metafield?.references?.nodes ?? [];
538
+ const bundles = [];
539
+ for (const ref of refs) {
540
+ const parsed = parseMetaobjectBundle(ref.id, ref.fields);
541
+ if (parsed) bundles.push(parsed);
542
+ }
543
+ this.bundles = bundles;
544
+ }
545
+ /**
546
+ * Dispatch add-to-cart with a cancelable event, then — unless a listener
547
+ * called preventDefault — execute the default cart-and-checkout flow.
548
+ *
549
+ * `fire-and-forget` against `reportAddToCart` runs regardless so merchants
550
+ * with BYO cart still get analytics.
551
+ */
552
+ handleAddToCart = async (bundle, lines) => {
553
+ const ev = new CustomEvent("lime-bundle:add-to-cart", {
554
+ detail: { lines },
555
+ bubbles: true,
556
+ composed: true,
557
+ cancelable: true
558
+ });
559
+ const allowDefault = this.dispatchEvent(ev);
560
+ this.reportAddToCartEvent(bundle, lines);
561
+ if (allowDefault) {
562
+ await this.defaultAddToCart(lines);
563
+ }
564
+ };
565
+ /**
566
+ * Default cart flow: Shopify's Storefront Cart API is tokenless, so we
567
+ * don't need any additional scopes. Persist the cart ID in localStorage
568
+ * so subsequent adds on the same browser session join the existing cart
569
+ * instead of creating a new one every click.
570
+ */
571
+ async defaultAddToCart(lines) {
572
+ if (typeof window === "undefined") return;
573
+ const client = createStorefrontClient({
574
+ shopDomain: this.shopDomain,
575
+ accessToken: this.storefrontToken
576
+ });
577
+ const storage = window.localStorage;
578
+ const key = cartStorageKey(this.shopDomain);
579
+ const existingCartId = storage?.getItem(key) ?? null;
580
+ try {
581
+ let checkoutUrl = null;
582
+ if (existingCartId) {
583
+ const res = await client.query(
584
+ CART_LINES_ADD_MUTATION,
585
+ { cartId: existingCartId, lines }
586
+ );
587
+ const payload = res.cartLinesAdd;
588
+ if (payload?.userErrors?.length) {
589
+ storage?.removeItem(key);
590
+ } else if (payload?.cart) {
591
+ checkoutUrl = payload.cart.checkoutUrl;
592
+ }
496
593
  }
497
- if (result.success) {
594
+ if (!checkoutUrl) {
595
+ const res = await client.query(
596
+ CART_CREATE_MUTATION,
597
+ { input: { lines } }
598
+ );
599
+ const payload = res.cartCreate;
600
+ if (payload?.cart) {
601
+ storage?.setItem(key, payload.cart.id);
602
+ checkoutUrl = payload.cart.checkoutUrl;
603
+ }
604
+ }
605
+ if (checkoutUrl) {
606
+ window.location.assign(checkoutUrl);
607
+ } else {
498
608
  this.dispatchEvent(
499
- new CustomEvent("lime-bundle:add-to-cart", {
500
- detail: { items, cartId: result.cartId },
609
+ new CustomEvent("lime-bundle:error", {
610
+ detail: { message: "Cart creation failed", code: "CART_ERROR" },
501
611
  bubbles: true,
502
612
  composed: true
503
613
  })
504
614
  );
505
- if (this.analyticsEnabled && this.appUrl) {
506
- const quantity = items.reduce((sum, i) => sum + i.quantity, 0);
507
- const totalPrice = items.reduce((sum, item) => {
508
- const product = this.bundle.products.find(
509
- (p) => p.variants.nodes.some((v) => v.id === item.variantId)
510
- );
511
- const variant = product?.variants.nodes.find((v) => v.id === item.variantId);
512
- const price = variant ? parseFloat(variant.price.amount) : 0;
513
- return sum + price * item.quantity;
514
- }, 0);
515
- reportAddToCart(
516
- { shopDomain: this.shopDomain, appUrl: this.appUrl },
517
- {
518
- bundleGid: this.bundleGid,
519
- bundleType: this.bundle.bundleType,
520
- productId: this.bundle.products[0]?.id ?? "",
521
- quantity,
522
- totalPrice: Math.round(totalPrice * 100) / 100
523
- }
524
- );
525
- }
526
615
  }
527
- return result;
528
- };
529
- switch (this.bundle.bundleType) {
530
- case "fixed":
531
- renderFixedBundle(container, this.bundle, addToCart);
532
- break;
533
- case "mix_match":
534
- renderMixMatchBundle(container, this.bundle, addToCart);
535
- break;
536
- case "volume":
537
- renderVolumeBundle(container, this.bundle, addToCart);
538
- break;
616
+ } catch (err) {
617
+ this.dispatchEvent(
618
+ new CustomEvent("lime-bundle:error", {
619
+ detail: {
620
+ message: err instanceof Error ? err.message : "Cart mutation failed",
621
+ code: "CART_ERROR"
622
+ },
623
+ bubbles: true,
624
+ composed: true
625
+ })
626
+ );
539
627
  }
628
+ }
629
+ reportAddToCartEvent(bundle, lines) {
630
+ if (!this.analyticsEnabled || !this.appUrl) return;
631
+ const quantity = lines.reduce((sum, l) => sum + l.quantity, 0);
632
+ const totalPrice = lines.reduce((sum, line) => {
633
+ const product = bundle.products.find(
634
+ (p) => p.variants.nodes.some((v) => v.id === line.merchandiseId)
635
+ );
636
+ const variant = product?.variants.nodes.find(
637
+ (v) => v.id === line.merchandiseId
638
+ );
639
+ const price = variant ? parseFloat(variant.price.amount) : 0;
640
+ return sum + price * line.quantity;
641
+ }, 0);
642
+ reportAddToCart(
643
+ { shopDomain: this.shopDomain, appUrl: this.appUrl },
644
+ {
645
+ bundleGid: bundle.id,
646
+ bundleType: bundle.bundleType,
647
+ productId: bundle.products[0]?.id ?? "",
648
+ quantity,
649
+ totalPrice: Math.round(totalPrice * 100) / 100
650
+ }
651
+ );
652
+ }
653
+ renderBundles() {
654
+ this.teardownImpressions();
540
655
  this.shadow.innerHTML = "";
541
656
  const style = document.createElement("style");
542
657
  style.textContent = WIDGET_STYLES;
543
658
  this.shadow.appendChild(style);
544
- this.shadow.appendChild(container);
659
+ for (const bundle of this.bundles) {
660
+ const container = document.createElement("div");
661
+ container.className = "lb-bundle";
662
+ container.setAttribute("role", "region");
663
+ container.setAttribute("aria-label", bundle.title);
664
+ const dispatch = (lines) => this.handleAddToCart(bundle, lines);
665
+ switch (bundle.bundleType) {
666
+ case "fixed":
667
+ renderFixedBundle(container, bundle, dispatch);
668
+ break;
669
+ case "mix_match":
670
+ renderMixMatchBundle(
671
+ container,
672
+ bundle,
673
+ dispatch
674
+ );
675
+ break;
676
+ case "volume":
677
+ renderVolumeBundle(container, bundle, dispatch);
678
+ break;
679
+ }
680
+ this.shadow.appendChild(container);
681
+ this.setupImpressionFor(bundle, container);
682
+ }
683
+ const first = this.bundles[0];
545
684
  this.dispatchEvent(
546
685
  new CustomEvent("lime-bundle:loaded", {
547
686
  detail: {
548
- bundleType: this.bundle.bundleType,
549
- title: this.bundle.title
687
+ bundleCount: this.bundles.length,
688
+ bundleTypes: this.bundles.map((b) => b.bundleType),
689
+ // Legacy fields — meaningful only in single-bundle mode. Preserved
690
+ // for merchants who attached listeners against the pre-1.0 shape.
691
+ bundleType: first?.bundleType,
692
+ title: first?.title
550
693
  },
551
694
  bubbles: true,
552
695
  composed: true
553
696
  })
554
697
  );
555
698
  }
556
- setupImpression() {
557
- if (!this.analyticsEnabled || !this.bundle || !this.appUrl) return;
558
- this.impressionCleanup?.();
559
- this.impressionCleanup = observeImpression(this, () => {
699
+ setupImpressionFor(bundle, element) {
700
+ if (!this.analyticsEnabled || !this.appUrl) return;
701
+ const cleanup = observeImpression(element, () => {
560
702
  reportImpression(
561
703
  { shopDomain: this.shopDomain, appUrl: this.appUrl },
562
704
  {
563
- bundleGid: this.bundleGid,
564
- bundleType: this.bundle.bundleType
705
+ bundleGid: bundle.id,
706
+ bundleType: bundle.bundleType
565
707
  }
566
708
  );
567
709
  });
710
+ this.impressionCleanups.push(cleanup);
568
711
  }
569
712
  renderLoading() {
570
713
  this.shadow.innerHTML = `
@@ -590,65 +733,11 @@ var LimeBundleElement = class extends HTMLElement {
590
733
  }
591
734
  };
592
735
 
593
- // src/thankyou/index.ts
594
- var reportedPurchases = /* @__PURE__ */ new Set();
595
- function trackPurchase(input) {
596
- const appUrl = input.appUrl ?? `https://${input.shopDomain}`;
597
- const bundleMap = /* @__PURE__ */ new Map();
598
- for (const item of input.lineItems) {
599
- if (!item.bundleGid) continue;
600
- const existing = bundleMap.get(item.bundleGid);
601
- if (existing) {
602
- existing.revenue += item.price * item.quantity;
603
- existing.lineItemCount += 1;
604
- } else {
605
- bundleMap.set(item.bundleGid, {
606
- bundleType: item.bundleType,
607
- revenue: item.price * item.quantity,
608
- lineItemCount: 1
609
- });
610
- }
611
- }
612
- for (const [bundleGid, data] of bundleMap) {
613
- const dedupKey = `${input.orderId}:${bundleGid}`;
614
- if (reportedPurchases.has(dedupKey)) continue;
615
- reportedPurchases.add(dedupKey);
616
- const payload = {
617
- shopDomain: input.shopDomain,
618
- eventType: "bundle_purchased",
619
- bundleGid,
620
- bundleType: data.bundleType,
621
- orderId: input.orderId,
622
- revenue: Math.round(data.revenue * 100) / 100,
623
- lineItemCount: data.lineItemCount,
624
- occurredAt: (/* @__PURE__ */ new Date()).toISOString()
625
- };
626
- const url = `${appUrl}/api/analytics`;
627
- const body = JSON.stringify(payload);
628
- try {
629
- fetch(url, {
630
- method: "POST",
631
- headers: { "Content-Type": "application/json" },
632
- body
633
- }).catch(() => {
634
- if (typeof navigator !== "undefined" && navigator.sendBeacon) {
635
- navigator.sendBeacon(url, body);
636
- }
637
- });
638
- } catch {
639
- if (typeof navigator !== "undefined" && navigator.sendBeacon) {
640
- navigator.sendBeacon(url, body);
641
- }
642
- }
643
- }
644
- }
645
-
646
736
  // src/index.ts
647
737
  if (typeof customElements !== "undefined" && !customElements.get("lime-bundle")) {
648
738
  customElements.define("lime-bundle", LimeBundleElement);
649
739
  }
650
740
  export {
651
- LimeBundleElement,
652
- trackPurchase
741
+ LimeBundleElement
653
742
  };
654
743
  //# sourceMappingURL=index.js.map