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