@lime-bundles/widget 4.1.0 → 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.cjs +326 -123
- package/dist/index.d.cts +7 -6
- package/dist/index.d.ts +7 -6
- package/dist/index.js +310 -102
- package/dist/lime-bundle.global.js +65 -33
- package/dist/lime-bundle.global.js.map +1 -1
- package/docs/hydrogen.md +28 -3
- package/docs/react-nextjs.md +23 -3
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -26,7 +26,7 @@ __export(index_exports, {
|
|
|
26
26
|
module.exports = __toCommonJS(index_exports);
|
|
27
27
|
|
|
28
28
|
// src/lime-bundle.ts
|
|
29
|
-
var
|
|
29
|
+
var import_core6 = require("@lime-bundles/core");
|
|
30
30
|
|
|
31
31
|
// ../render/src/adapt.ts
|
|
32
32
|
var import_core = require("@lime-bundles/core");
|
|
@@ -52,7 +52,10 @@ function adaptVariant(v, requiredQty, fallbackCurrency) {
|
|
|
52
52
|
price: toCents(v.price?.amount),
|
|
53
53
|
compareAtPrice: toCents(v.compareAtPrice?.amount),
|
|
54
54
|
unitPrice: (0, import_core.formatUnitPrice)(v.unitPrice, v.unitPriceMeasurement, fallbackCurrency),
|
|
55
|
-
image: v.image?.url ?? null
|
|
55
|
+
image: v.image?.url ?? null,
|
|
56
|
+
// Carried so the picker's stepper cap and cross-slot stock guard can
|
|
57
|
+
// bind on the headless surface; dropping it here left them capless.
|
|
58
|
+
inventoryQuantity: (0, import_core.stockCapForVariant)(v)
|
|
56
59
|
};
|
|
57
60
|
}
|
|
58
61
|
function adaptProduct(product, bundleId, opts) {
|
|
@@ -107,6 +110,14 @@ function findVariant(product, variantId) {
|
|
|
107
110
|
}
|
|
108
111
|
return product.variants[0];
|
|
109
112
|
}
|
|
113
|
+
function resolveSellableVariant(product) {
|
|
114
|
+
const seeded = findVariant(product, product.selectedVariantId);
|
|
115
|
+
if (seeded && seeded.available !== false) return seeded;
|
|
116
|
+
for (let i = 0; i < product.variants.length; i++) {
|
|
117
|
+
if (product.variants[i].available !== false) return product.variants[i];
|
|
118
|
+
}
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
110
121
|
function findVariantByOptions(product, optionValues) {
|
|
111
122
|
if (!product.variants || !optionValues) return null;
|
|
112
123
|
for (let i = 0; i < product.variants.length; i++) {
|
|
@@ -511,7 +522,7 @@ function buildWidgetShell(typeClass, opts) {
|
|
|
511
522
|
"data-countdown": ""
|
|
512
523
|
});
|
|
513
524
|
const label2 = box("span", "lb-bundle-countdown-label");
|
|
514
|
-
label2.textContent = opts.countdownLabel ?? "
|
|
525
|
+
label2.textContent = opts.countdownLabel ?? "Limited time offer";
|
|
515
526
|
countdown.appendChild(label2);
|
|
516
527
|
countdown.appendChild(
|
|
517
528
|
box("span", "lb-bundle-countdown-timer", {
|
|
@@ -523,10 +534,12 @@ function buildWidgetShell(typeClass, opts) {
|
|
|
523
534
|
const products = box("div", typeClass + "__products lb-edge-fade");
|
|
524
535
|
root.appendChild(products);
|
|
525
536
|
root.appendChild(box("div", "lb-bundle-divider"));
|
|
526
|
-
const summary = box("div", "lb-bundle-summary"
|
|
537
|
+
const summary = box("div", "lb-bundle-summary", {
|
|
538
|
+
"data-pricing-section": ""
|
|
539
|
+
});
|
|
527
540
|
const text = box("div", "lb-bundle-summary__text");
|
|
528
541
|
const label = box("span", "lb-bundle-summary__label");
|
|
529
|
-
label.textContent = opts.summaryLabel ?? "Bundle
|
|
542
|
+
label.textContent = opts.summaryLabel ?? "Bundle total";
|
|
530
543
|
text.appendChild(label);
|
|
531
544
|
if (opts.withItemCount) {
|
|
532
545
|
label.appendChild(box("span", "", { "data-item-count": "" }));
|
|
@@ -557,6 +570,7 @@ function buildWidgetShell(typeClass, opts) {
|
|
|
557
570
|
summary.appendChild(prices);
|
|
558
571
|
root.appendChild(summary);
|
|
559
572
|
const cta = box("button", "lb-bundle-cta", {
|
|
573
|
+
"data-cta-text": opts.ctaText,
|
|
560
574
|
type: "button",
|
|
561
575
|
"data-add-bundle": ""
|
|
562
576
|
});
|
|
@@ -575,7 +589,9 @@ function buildWidgetShell(typeClass, opts) {
|
|
|
575
589
|
);
|
|
576
590
|
return { root, products, cta };
|
|
577
591
|
}
|
|
578
|
-
function buildVolumeTiers(tiers, groupLabel) {
|
|
592
|
+
function buildVolumeTiers(tiers, groupLabel, opts = {}) {
|
|
593
|
+
const showCompare = opts.showCompare !== false;
|
|
594
|
+
const showPriceEach = opts.showPriceEach !== false;
|
|
579
595
|
const group = box("div", "lb-volume__tiers lb-edge-fade", {
|
|
580
596
|
role: "radiogroup",
|
|
581
597
|
"aria-label": groupLabel,
|
|
@@ -600,8 +616,17 @@ function buildVolumeTiers(tiers, groupLabel) {
|
|
|
600
616
|
label.textContent = tier.label;
|
|
601
617
|
info.appendChild(label);
|
|
602
618
|
const price = box("span", "lb-volume__tier-price");
|
|
603
|
-
|
|
604
|
-
|
|
619
|
+
if (showCompare && (tier.percent > 0 || tier.amountCents > 0)) {
|
|
620
|
+
price.appendChild(box("span", "lb-volume__tier-compare"));
|
|
621
|
+
}
|
|
622
|
+
if (showPriceEach) {
|
|
623
|
+
price.appendChild(box("span", "", { "data-tier-price-each": "" }));
|
|
624
|
+
if (opts.unitLabel) {
|
|
625
|
+
const unit = box("span", "lb-volume__tier-unit");
|
|
626
|
+
unit.textContent = opts.unitLabel;
|
|
627
|
+
price.appendChild(unit);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
605
630
|
info.appendChild(price);
|
|
606
631
|
el2.appendChild(info);
|
|
607
632
|
if (tier.badgeLabel || tier.savingsLabel) {
|
|
@@ -710,7 +735,10 @@ var DEFAULT_STRINGS = {
|
|
|
710
735
|
required: "Required",
|
|
711
736
|
soldOut: "Sold out",
|
|
712
737
|
outOfStock: "Out of stock",
|
|
713
|
-
itemsOutOfStock:
|
|
738
|
+
itemsOutOfStock: {
|
|
739
|
+
one: "__COUNT__ item out of stock",
|
|
740
|
+
other: "__COUNT__ items out of stock"
|
|
741
|
+
},
|
|
714
742
|
quantity: "Quantity",
|
|
715
743
|
increaseQuantity: "Increase quantity",
|
|
716
744
|
decreaseQuantity: "Decrease quantity",
|
|
@@ -730,11 +758,26 @@ var DEFAULT_STRINGS = {
|
|
|
730
758
|
stepsCompleted: "__COUNT__ of __TOTAL__ steps completed",
|
|
731
759
|
each: " each",
|
|
732
760
|
buyQty: "Buy __COUNT__",
|
|
761
|
+
itemCount: {
|
|
762
|
+
one: "__COUNT__ item",
|
|
763
|
+
other: "__COUNT__ items"
|
|
764
|
+
},
|
|
733
765
|
needsSpots: {
|
|
734
766
|
one: "Needs __COUNT__ spot",
|
|
735
767
|
other: "Needs __COUNT__ spots"
|
|
736
768
|
}
|
|
737
769
|
};
|
|
770
|
+
function formatItemsOutOfStock(t, count) {
|
|
771
|
+
const raw = t.itemsOutOfStock;
|
|
772
|
+
let tpl;
|
|
773
|
+
if (raw && typeof raw === "object") {
|
|
774
|
+
const form = new Intl.PluralRules(t.locale || "en").select(count);
|
|
775
|
+
tpl = raw[form] || raw.other;
|
|
776
|
+
} else {
|
|
777
|
+
tpl = raw;
|
|
778
|
+
}
|
|
779
|
+
return (tpl || "__COUNT__ items out of stock").split("__COUNT__").join(String(count));
|
|
780
|
+
}
|
|
738
781
|
|
|
739
782
|
// ../render/src/dropdown/bind.ts
|
|
740
783
|
var import_core2 = require("@lime-bundles/core");
|
|
@@ -1145,12 +1188,11 @@ function bindDropdowns(root) {
|
|
|
1145
1188
|
}
|
|
1146
1189
|
|
|
1147
1190
|
// src/renderers/fixed.ts
|
|
1148
|
-
function renderFixedBundle(container, bundle,
|
|
1191
|
+
function renderFixedBundle(container, bundle, onAddToCart, onCleanup) {
|
|
1149
1192
|
const wc = bundle.widgetConfig;
|
|
1150
1193
|
const products = adaptProducts(bundle);
|
|
1151
1194
|
if (!products.length) return;
|
|
1152
1195
|
const oosProducts = products.filter((p) => !p.variants.some((v) => v.available));
|
|
1153
|
-
if (outOfStockBehavior === "hide" && oosProducts.length > 0) return;
|
|
1154
1196
|
const currency = bundle.products[0]?.priceRange.minVariantPrice.currencyCode ?? "USD";
|
|
1155
1197
|
const formatMoney = intlFormatMoney(currency);
|
|
1156
1198
|
const t = DEFAULT_STRINGS;
|
|
@@ -1175,7 +1217,9 @@ function renderFixedBundle(container, bundle, outOfStockBehavior, onAddToCart, o
|
|
|
1175
1217
|
});
|
|
1176
1218
|
shell.products.appendChild(row);
|
|
1177
1219
|
if (isOos) continue;
|
|
1178
|
-
const
|
|
1220
|
+
const sellable = resolveSellableVariant(product);
|
|
1221
|
+
if (sellable) product.selectedVariantId = sellable.id;
|
|
1222
|
+
const selected = sellable ?? findVariant(product, product.selectedVariantId);
|
|
1179
1223
|
hydrateRowControls(row, product, selected);
|
|
1180
1224
|
applyVariant(row, selected, product, formatMoney);
|
|
1181
1225
|
bindVariantSelects(
|
|
@@ -1191,7 +1235,7 @@ function renderFixedBundle(container, bundle, outOfStockBehavior, onAddToCart, o
|
|
|
1191
1235
|
shell.cta.disabled = true;
|
|
1192
1236
|
const label = shell.cta.querySelector("[data-cta-label]");
|
|
1193
1237
|
if (label) {
|
|
1194
|
-
label.textContent = (t
|
|
1238
|
+
label.textContent = formatItemsOutOfStock(t, oosProducts.length);
|
|
1195
1239
|
}
|
|
1196
1240
|
} else {
|
|
1197
1241
|
shell.cta.addEventListener("click", () => {
|
|
@@ -1770,6 +1814,17 @@ function unlock() {
|
|
|
1770
1814
|
window.scrollTo(0, savedY);
|
|
1771
1815
|
}
|
|
1772
1816
|
|
|
1817
|
+
// ../render/src/picker/sort.ts
|
|
1818
|
+
function sinkAddedRows(list, rowEls, isAdded) {
|
|
1819
|
+
const front = [];
|
|
1820
|
+
const back = [];
|
|
1821
|
+
for (let i = 0; i < rowEls.length; i++) {
|
|
1822
|
+
(isAdded(i) ? back : front).push(rowEls[i]);
|
|
1823
|
+
}
|
|
1824
|
+
for (const el2 of front) list.appendChild(el2);
|
|
1825
|
+
for (const el2 of back) list.appendChild(el2);
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1773
1828
|
// ../render/src/mix-match/modal.ts
|
|
1774
1829
|
var ALL_TYPES = "__all__";
|
|
1775
1830
|
var CLOSE_TRANSITION_MS = 300;
|
|
@@ -1994,12 +2049,22 @@ function createPickerModal(deps) {
|
|
|
1994
2049
|
syncActiveFilterPill();
|
|
1995
2050
|
if (searchInput) searchInput.value = "";
|
|
1996
2051
|
filterProducts(searchInput ? searchInput.value : "");
|
|
2052
|
+
if (modalList) {
|
|
2053
|
+
const selectedProductIds = /* @__PURE__ */ Object.create(null);
|
|
2054
|
+
for (const it of items) selectedProductIds[String(it.productId)] = true;
|
|
2055
|
+
sinkAddedRows(
|
|
2056
|
+
modalList,
|
|
2057
|
+
rows.map((r) => r.el),
|
|
2058
|
+
(i) => selectedProductIds[String(eligibleProducts[i].id)] === true
|
|
2059
|
+
);
|
|
2060
|
+
modalList.scrollTop = 0;
|
|
2061
|
+
}
|
|
1997
2062
|
overlay.style.display = "";
|
|
1998
2063
|
void overlay.offsetHeight;
|
|
1999
2064
|
overlay.classList.add("lb-mix-match__modal-overlay--open");
|
|
2000
2065
|
lock();
|
|
2001
2066
|
setTimeout(() => {
|
|
2002
|
-
if (
|
|
2067
|
+
if (closeBtn) closeBtn.focus();
|
|
2003
2068
|
else modalDialog?.focus();
|
|
2004
2069
|
}, 50);
|
|
2005
2070
|
}
|
|
@@ -2556,17 +2621,17 @@ function buildPickerModal(opts) {
|
|
|
2556
2621
|
}
|
|
2557
2622
|
|
|
2558
2623
|
// src/renderers/mix-match.ts
|
|
2559
|
-
function renderMixMatchBundle(container, bundle,
|
|
2624
|
+
function renderMixMatchBundle(container, bundle, onAddToCart, onCleanup, currentProductHandle) {
|
|
2560
2625
|
const wc = bundle.widgetConfig;
|
|
2561
2626
|
const t = DEFAULT_STRINGS;
|
|
2562
2627
|
const requiredQty = bundle.minQuantity || 1;
|
|
2563
2628
|
const rulesMap = mergeRuleMaps(bundle.productRules, bundle.variantRules);
|
|
2564
2629
|
const adapted = adaptProducts(bundle);
|
|
2565
|
-
const eligibleProducts = adapted.map((p) => ({
|
|
2630
|
+
const eligibleProducts = adapted.map((p, i) => ({
|
|
2566
2631
|
id: Number(p.productId),
|
|
2567
2632
|
title: p.title ?? "",
|
|
2568
2633
|
url: p.url ?? null,
|
|
2569
|
-
type: "",
|
|
2634
|
+
type: bundle.products[i]?.productType ?? "",
|
|
2570
2635
|
featuredImage: p.featuredImage ?? null,
|
|
2571
2636
|
available: p.variants.some((v) => v.available),
|
|
2572
2637
|
optionNames: p.optionNames,
|
|
@@ -2579,12 +2644,12 @@ function renderMixMatchBundle(container, bundle, outOfStockBehavior, onAddToCart
|
|
|
2579
2644
|
compareAtPrice: v.compareAtPrice,
|
|
2580
2645
|
unitPrice: v.unitPrice,
|
|
2581
2646
|
image: v.image,
|
|
2582
|
-
inventoryQuantity: null
|
|
2647
|
+
inventoryQuantity: v.inventoryQuantity ?? null
|
|
2583
2648
|
}))
|
|
2584
2649
|
}));
|
|
2585
2650
|
if (!eligibleProducts.length) return;
|
|
2586
2651
|
const poolUnits = eligibleProducts.filter((p) => p.available).length;
|
|
2587
|
-
if (
|
|
2652
|
+
if (poolUnits === 0) return;
|
|
2588
2653
|
const formatMoney = intlFormatMoney(
|
|
2589
2654
|
bundle.products[0]?.priceRange.minVariantPrice.currencyCode ?? "USD"
|
|
2590
2655
|
);
|
|
@@ -2611,16 +2676,21 @@ function renderMixMatchBundle(container, bundle, outOfStockBehavior, onAddToCart
|
|
|
2611
2676
|
const overlay = buildPickerModal({
|
|
2612
2677
|
bundleGid: bundle.id,
|
|
2613
2678
|
domId: bundle.id.replace(/\D/g, ""),
|
|
2614
|
-
showSearch:
|
|
2615
|
-
showTypeFilters: false,
|
|
2679
|
+
showSearch: wc.showSearch !== false,
|
|
2680
|
+
showTypeFilters: wc.mixMatchShowTypeFilters !== false,
|
|
2616
2681
|
segmentCount: requiredQty,
|
|
2617
2682
|
wizard: false
|
|
2618
2683
|
});
|
|
2619
2684
|
shell.root.appendChild(overlay);
|
|
2685
|
+
const currentIdx = currentProductHandle ? bundle.products.findIndex((p) => p.handle === currentProductHandle) : -1;
|
|
2620
2686
|
const selectedItems = seedSelection(
|
|
2621
2687
|
eligibleProducts,
|
|
2622
2688
|
rulesMap,
|
|
2623
|
-
requiredQty
|
|
2689
|
+
requiredQty,
|
|
2690
|
+
{
|
|
2691
|
+
enabled: !!currentProductHandle,
|
|
2692
|
+
currentProductId: currentIdx !== -1 ? eligibleProducts[currentIdx]?.id ?? null : null
|
|
2693
|
+
}
|
|
2624
2694
|
);
|
|
2625
2695
|
const rowCapacity = {
|
|
2626
2696
|
remainingSpots: () => remainingUnits(selectedItems, requiredQty),
|
|
@@ -2657,7 +2727,13 @@ function renderMixMatchBundle(container, bundle, outOfStockBehavior, onAddToCart
|
|
|
2657
2727
|
shell.root,
|
|
2658
2728
|
selectedItems,
|
|
2659
2729
|
requiredQty,
|
|
2660
|
-
|
|
2730
|
+
// The shared updater reads the theme data-block shape: discount fields
|
|
2731
|
+
// flat, not nested under discountConfig. Passing the bundle itself left
|
|
2732
|
+
// `discountType` undefined, so the summary never painted.
|
|
2733
|
+
{
|
|
2734
|
+
discountType: bundle.discountConfig.discountType,
|
|
2735
|
+
discountValue: bundle.discountConfig.discountValue
|
|
2736
|
+
},
|
|
2661
2737
|
(total, type, value) => (0, import_core3.calculateDiscount)(total, type, value),
|
|
2662
2738
|
updatePricing2(formatMoney)
|
|
2663
2739
|
);
|
|
@@ -2670,7 +2746,7 @@ function renderMixMatchBundle(container, bundle, outOfStockBehavior, onAddToCart
|
|
|
2670
2746
|
rulesMap,
|
|
2671
2747
|
eligibleProducts,
|
|
2672
2748
|
requiredQty,
|
|
2673
|
-
showQtySelector:
|
|
2749
|
+
showQtySelector: wc.mixMatchShowQuantitySelector !== false,
|
|
2674
2750
|
items: selectedItems,
|
|
2675
2751
|
formatMoney,
|
|
2676
2752
|
rowCapacity,
|
|
@@ -2733,6 +2809,47 @@ function renderMixMatchBundle(container, bundle, outOfStockBehavior, onAddToCart
|
|
|
2733
2809
|
onCleanup?.(bindDropdowns(shell.root));
|
|
2734
2810
|
}
|
|
2735
2811
|
|
|
2812
|
+
// src/renderers/volume.ts
|
|
2813
|
+
var import_core4 = require("@lime-bundles/core");
|
|
2814
|
+
|
|
2815
|
+
// ../render/src/volume/stock.ts
|
|
2816
|
+
var TIER_OOS_CLASS = "lb-volume__tier--oos";
|
|
2817
|
+
function tierQty(el2) {
|
|
2818
|
+
return parseInt(el2.getAttribute("data-tier-qty") ?? "", 10) || 0;
|
|
2819
|
+
}
|
|
2820
|
+
function tierUnavailable(el2) {
|
|
2821
|
+
return el2.getAttribute("aria-disabled") === "true";
|
|
2822
|
+
}
|
|
2823
|
+
function applyTierStockState(tierEls, cap) {
|
|
2824
|
+
for (let i = 0; i < tierEls.length; i++) {
|
|
2825
|
+
const el2 = tierEls[i];
|
|
2826
|
+
const unavailable = cap !== null && tierQty(el2) > cap;
|
|
2827
|
+
if (unavailable) {
|
|
2828
|
+
el2.classList.add(TIER_OOS_CLASS);
|
|
2829
|
+
el2.setAttribute("aria-disabled", "true");
|
|
2830
|
+
} else {
|
|
2831
|
+
el2.classList.remove(TIER_OOS_CLASS);
|
|
2832
|
+
el2.removeAttribute("aria-disabled");
|
|
2833
|
+
}
|
|
2834
|
+
}
|
|
2835
|
+
}
|
|
2836
|
+
function resolveAvailableTierIndex(tierEls, preferred) {
|
|
2837
|
+
const p = tierEls[preferred];
|
|
2838
|
+
if (p && !tierUnavailable(p)) return preferred;
|
|
2839
|
+
for (let i = 0; i < tierEls.length; i++) {
|
|
2840
|
+
if (!tierUnavailable(tierEls[i])) return i;
|
|
2841
|
+
}
|
|
2842
|
+
return -1;
|
|
2843
|
+
}
|
|
2844
|
+
function stepToAvailableTier(tierEls, from, direction) {
|
|
2845
|
+
let i = from + direction;
|
|
2846
|
+
while (i >= 0 && i < tierEls.length) {
|
|
2847
|
+
if (!tierUnavailable(tierEls[i])) return i;
|
|
2848
|
+
i += direction;
|
|
2849
|
+
}
|
|
2850
|
+
return from;
|
|
2851
|
+
}
|
|
2852
|
+
|
|
2736
2853
|
// ../render/src/volume/pricing.ts
|
|
2737
2854
|
function calcTierPrice(basePrice, discountType, tierEl) {
|
|
2738
2855
|
if (discountType === "fixed_amount") {
|
|
@@ -2761,6 +2878,23 @@ function formatItemCount(qty, translations) {
|
|
|
2761
2878
|
const tpl = translations.itemCount[form] || translations.itemCount.other || "__COUNT__";
|
|
2762
2879
|
return " (" + tpl.split("__COUNT__").join(String(qty)) + ")";
|
|
2763
2880
|
}
|
|
2881
|
+
function updateAllTierPrices(allTiers, basePrice, discountType, formatMoney) {
|
|
2882
|
+
for (let i = 0; i < allTiers.length; i++) {
|
|
2883
|
+
const tierEl = allTiers[i];
|
|
2884
|
+
const priceEachEl = tierEl.querySelector(
|
|
2885
|
+
"[data-tier-price-each]"
|
|
2886
|
+
);
|
|
2887
|
+
if (priceEachEl) {
|
|
2888
|
+
priceEachEl.textContent = formatMoney(
|
|
2889
|
+
calcTierPrice(basePrice, discountType, tierEl)
|
|
2890
|
+
);
|
|
2891
|
+
}
|
|
2892
|
+
const compareEl = tierEl.querySelector(
|
|
2893
|
+
".lb-volume__tier-compare"
|
|
2894
|
+
);
|
|
2895
|
+
if (compareEl) compareEl.textContent = formatMoney(basePrice);
|
|
2896
|
+
}
|
|
2897
|
+
}
|
|
2764
2898
|
function selectTier(container, allTiers, basePrice, discountType, index, translations, formatMoney) {
|
|
2765
2899
|
for (let i = 0; i < allTiers.length; i++) {
|
|
2766
2900
|
allTiers[i].setAttribute("aria-checked", i === index ? "true" : "false");
|
|
@@ -2805,21 +2939,31 @@ function selectTier(container, allTiers, basePrice, discountType, index, transla
|
|
|
2805
2939
|
}
|
|
2806
2940
|
|
|
2807
2941
|
// src/renderers/volume.ts
|
|
2808
|
-
function renderVolumeBundle(container, bundle,
|
|
2942
|
+
function renderVolumeBundle(container, bundle, onAddToCart) {
|
|
2809
2943
|
const wc = bundle.widgetConfig;
|
|
2810
2944
|
const product = bundle.products[0];
|
|
2811
2945
|
if (!product) return;
|
|
2812
2946
|
const variants = product.variants.nodes;
|
|
2813
|
-
const
|
|
2947
|
+
const minTierQty = Math.min(
|
|
2948
|
+
...bundle.volumeTiers.map((tier) => tier.minQuantity)
|
|
2949
|
+
);
|
|
2950
|
+
const pricingVariant = variants.find((v) => (0, import_core4.isVariantFulfillable)(v, minTierQty)) ?? variants.find((v) => v.availableForSale) ?? variants[0];
|
|
2814
2951
|
if (!pricingVariant) return;
|
|
2815
|
-
const anyAvailable = variants.some((v) => v.availableForSale);
|
|
2816
|
-
if (outOfStockBehavior === "hide" && !anyAvailable) return;
|
|
2817
2952
|
const basePrice = toCents(pricingVariant.price.amount);
|
|
2818
2953
|
const discountType = bundle.discountConfig.discountType;
|
|
2819
2954
|
const formatMoney = intlFormatMoney(product.priceRange.minVariantPrice.currencyCode);
|
|
2820
2955
|
const t = DEFAULT_STRINGS;
|
|
2821
|
-
const
|
|
2822
|
-
const defaultIdx = 0
|
|
2956
|
+
const bestIdx = (0, import_core4.bestValueTierIndex)(bundle.volumeTiers, discountType);
|
|
2957
|
+
const defaultIdx = (0, import_core4.resolveDefaultTierIndex)(
|
|
2958
|
+
wc.defaultTier,
|
|
2959
|
+
bundle.volumeTiers.length,
|
|
2960
|
+
bestIdx
|
|
2961
|
+
);
|
|
2962
|
+
const popularIdx = (0, import_core4.resolvePopularTierIndex)(
|
|
2963
|
+
wc.popularBadge?.tierIndex,
|
|
2964
|
+
wc.popularBadge?.visible !== false,
|
|
2965
|
+
bestIdx
|
|
2966
|
+
);
|
|
2823
2967
|
const tiers = bundle.volumeTiers.map((tier, i) => {
|
|
2824
2968
|
const percent = discountType === "fixed_amount" ? 0 : Math.round(tier.percentage ?? 0);
|
|
2825
2969
|
const amountCents = discountType === "fixed_amount" ? Math.round((tier.amount ?? 0) * 100) : 0;
|
|
@@ -2840,38 +2984,46 @@ function renderVolumeBundle(container, bundle, outOfStockBehavior, onAddToCart)
|
|
|
2840
2984
|
title: bundle.title,
|
|
2841
2985
|
subtitle: bundle.description,
|
|
2842
2986
|
summaryLabel: "Total",
|
|
2843
|
-
withItemCount:
|
|
2987
|
+
withItemCount: wc.pricing?.showItemCount !== false,
|
|
2844
2988
|
totalPriceSlot: true,
|
|
2845
2989
|
showSavingsBar: wc.savingsBar?.visible !== false,
|
|
2846
2990
|
showComparePrice: wc.pricing?.showCompareAtPrice !== false,
|
|
2847
2991
|
ctaText: wc.cta?.ctaText || t.addToCart || "Add to cart",
|
|
2848
2992
|
endsAt: wc.countdown?.showCountdown === false ? null : bundle.endsAt
|
|
2849
2993
|
});
|
|
2850
|
-
const group = buildVolumeTiers(tiers, "Select quantity"
|
|
2994
|
+
const group = buildVolumeTiers(tiers, "Select quantity", {
|
|
2995
|
+
showCompare: wc.pricing?.showComparePrice !== false,
|
|
2996
|
+
showPriceEach: wc.pricing?.showPerUnitPrice !== false,
|
|
2997
|
+
unitLabel: (t.each || "each").trim()
|
|
2998
|
+
});
|
|
2851
2999
|
shell.products.replaceWith(group);
|
|
2852
3000
|
const tierEls = group.querySelectorAll("[data-tier-index]");
|
|
2853
|
-
|
|
3001
|
+
updateAllTierPrices(tierEls, basePrice, discountType, formatMoney);
|
|
3002
|
+
applyTierStockState(tierEls, (0, import_core4.stockCapForVariant)(pricingVariant));
|
|
3003
|
+
let selectedIndex = resolveAvailableTierIndex(tierEls, defaultIdx);
|
|
3004
|
+
const noTierAvailable = selectedIndex === -1;
|
|
3005
|
+
if (noTierAvailable) selectedIndex = defaultIdx;
|
|
2854
3006
|
const apply = () => selectTier(shell.root, tierEls, basePrice, discountType, selectedIndex, t, formatMoney);
|
|
2855
3007
|
group.addEventListener("click", (e) => {
|
|
2856
3008
|
const el2 = e.target.closest("[data-tier-index]");
|
|
2857
|
-
if (!el2) return;
|
|
3009
|
+
if (!el2 || tierUnavailable(el2)) return;
|
|
2858
3010
|
selectedIndex = parseInt(el2.getAttribute("data-tier-index") ?? "", 10) || 0;
|
|
2859
3011
|
apply();
|
|
2860
3012
|
});
|
|
2861
3013
|
group.addEventListener("keydown", (e) => {
|
|
2862
3014
|
if (e.key === "ArrowDown" || e.key === "ArrowRight") {
|
|
2863
3015
|
e.preventDefault();
|
|
2864
|
-
selectedIndex =
|
|
3016
|
+
selectedIndex = stepToAvailableTier(tierEls, selectedIndex, 1);
|
|
2865
3017
|
apply();
|
|
2866
3018
|
tierEls[selectedIndex].focus();
|
|
2867
3019
|
} else if (e.key === "ArrowUp" || e.key === "ArrowLeft") {
|
|
2868
3020
|
e.preventDefault();
|
|
2869
|
-
selectedIndex =
|
|
3021
|
+
selectedIndex = stepToAvailableTier(tierEls, selectedIndex, -1);
|
|
2870
3022
|
apply();
|
|
2871
3023
|
tierEls[selectedIndex].focus();
|
|
2872
3024
|
}
|
|
2873
3025
|
});
|
|
2874
|
-
if (
|
|
3026
|
+
if (noTierAvailable) {
|
|
2875
3027
|
shell.cta.disabled = true;
|
|
2876
3028
|
const label = shell.cta.querySelector("[data-cta-label]");
|
|
2877
3029
|
if (label) label.textContent = t.outOfStock || "Out of stock";
|
|
@@ -2999,7 +3151,14 @@ function buildBogoCartItems(sides, percent, buyQty, getQty) {
|
|
|
2999
3151
|
}
|
|
3000
3152
|
|
|
3001
3153
|
// src/renderers/bogo.ts
|
|
3002
|
-
function
|
|
3154
|
+
function buildBogoPlus() {
|
|
3155
|
+
const plus = document.createElement("div");
|
|
3156
|
+
plus.className = "lb-bogo__plus";
|
|
3157
|
+
plus.setAttribute("aria-hidden", "true");
|
|
3158
|
+
plus.innerHTML = '<svg width="12" height="12" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><line x1="9" y1="3" x2="9" y2="15" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><line x1="3" y1="9" x2="15" y2="9" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>';
|
|
3159
|
+
return plus;
|
|
3160
|
+
}
|
|
3161
|
+
function renderBogoBundle(container, bundle, onAddToCart, onCleanup) {
|
|
3003
3162
|
const wc = bundle.widgetConfig;
|
|
3004
3163
|
const buyProduct = bundle.products.find((p) => p.id === bundle.buyProductId);
|
|
3005
3164
|
const getProduct = bundle.products.find((p) => p.id === bundle.getProductId);
|
|
@@ -3028,7 +3187,6 @@ function renderBogoBundle(container, bundle, outOfStockBehavior, onAddToCart, on
|
|
|
3028
3187
|
}
|
|
3029
3188
|
];
|
|
3030
3189
|
const oosCount = sides.filter((s) => !s.variants.some((v) => v.available)).length;
|
|
3031
|
-
if (outOfStockBehavior === "hide" && oosCount > 0) return;
|
|
3032
3190
|
const formatMoney = intlFormatMoney(
|
|
3033
3191
|
buyProduct.priceRange.minVariantPrice.currencyCode
|
|
3034
3192
|
);
|
|
@@ -3047,6 +3205,7 @@ function renderBogoBundle(container, bundle, outOfStockBehavior, onAddToCart, on
|
|
|
3047
3205
|
const badgeText = percent >= 100 ? "Free" : percent + "% off";
|
|
3048
3206
|
sides.forEach((side, i) => {
|
|
3049
3207
|
const isGet = i === 1;
|
|
3208
|
+
if (isGet) shell.products.appendChild(buildBogoPlus());
|
|
3050
3209
|
const isOos = !side.variants.some((v) => v.available);
|
|
3051
3210
|
const row = buildRowSkeleton(side, t, {
|
|
3052
3211
|
isOos,
|
|
@@ -3075,7 +3234,7 @@ function renderBogoBundle(container, bundle, outOfStockBehavior, onAddToCart, on
|
|
|
3075
3234
|
shell.cta.disabled = true;
|
|
3076
3235
|
const label = shell.cta.querySelector("[data-cta-label]");
|
|
3077
3236
|
if (label) {
|
|
3078
|
-
label.textContent = (t
|
|
3237
|
+
label.textContent = formatItemsOutOfStock(t, oosCount);
|
|
3079
3238
|
}
|
|
3080
3239
|
} else {
|
|
3081
3240
|
shell.cta.addEventListener("click", () => {
|
|
@@ -3095,7 +3254,7 @@ function renderBogoBundle(container, bundle, outOfStockBehavior, onAddToCart, on
|
|
|
3095
3254
|
}
|
|
3096
3255
|
|
|
3097
3256
|
// src/renderers/multi-step.ts
|
|
3098
|
-
var
|
|
3257
|
+
var import_core5 = require("@lime-bundles/core");
|
|
3099
3258
|
|
|
3100
3259
|
// ../render/src/multi-step/i18n.ts
|
|
3101
3260
|
function stepOfLabel(i, total, t) {
|
|
@@ -3460,6 +3619,14 @@ function createWizard(deps) {
|
|
|
3460
3619
|
rows.push(row);
|
|
3461
3620
|
modalList.appendChild(row.el);
|
|
3462
3621
|
}
|
|
3622
|
+
const stepPicks = selections[forStep] || [];
|
|
3623
|
+
const selectedProductIds = /* @__PURE__ */ Object.create(null);
|
|
3624
|
+
for (const it of stepPicks) selectedProductIds[String(it.productId)] = true;
|
|
3625
|
+
sinkAddedRows(
|
|
3626
|
+
modalList,
|
|
3627
|
+
rows.map((r) => r.el),
|
|
3628
|
+
(i) => selectedProductIds[String(pool[i].id)] === true
|
|
3629
|
+
);
|
|
3463
3630
|
deps.dropdown?.bindAll(modalList);
|
|
3464
3631
|
}
|
|
3465
3632
|
function onListClick(e) {
|
|
@@ -3622,6 +3789,7 @@ function createWizard(deps) {
|
|
|
3622
3789
|
syncActiveFilterPill();
|
|
3623
3790
|
if (searchInput) searchInput.value = "";
|
|
3624
3791
|
filterProducts("");
|
|
3792
|
+
if (modalList) modalList.scrollTop = 0;
|
|
3625
3793
|
buildStepSegments(stepIndex);
|
|
3626
3794
|
refresh();
|
|
3627
3795
|
if (modalLive) {
|
|
@@ -3645,7 +3813,7 @@ function createWizard(deps) {
|
|
|
3645
3813
|
overlay.classList.add("lb-mix-match__modal-overlay--open");
|
|
3646
3814
|
lock();
|
|
3647
3815
|
setTimeout(() => {
|
|
3648
|
-
if (
|
|
3816
|
+
if (closeBtn) closeBtn.focus();
|
|
3649
3817
|
else modalDialog?.focus();
|
|
3650
3818
|
}, 50);
|
|
3651
3819
|
}
|
|
@@ -3713,7 +3881,7 @@ function createWizard(deps) {
|
|
|
3713
3881
|
}
|
|
3714
3882
|
|
|
3715
3883
|
// src/renderers/multi-step.ts
|
|
3716
|
-
function renderMultiStepBundle(container, bundle,
|
|
3884
|
+
function renderMultiStepBundle(container, bundle, onAddToCart, onCleanup) {
|
|
3717
3885
|
const wc = bundle.widgetConfig;
|
|
3718
3886
|
const t = DEFAULT_STRINGS;
|
|
3719
3887
|
const rulesMap = mergeRuleMaps(bundle.productRules, bundle.variantRules);
|
|
@@ -3724,7 +3892,7 @@ function renderMultiStepBundle(container, bundle, outOfStockBehavior, onAddToCar
|
|
|
3724
3892
|
variantQuantities: bundle.variantQuantities
|
|
3725
3893
|
};
|
|
3726
3894
|
const pools = steps.map(
|
|
3727
|
-
(_, i) => (0,
|
|
3895
|
+
(_, i) => (0, import_core5.productsForStep)(bundle, i).map((product) => {
|
|
3728
3896
|
const p = adaptProduct(product, bundle.id, {
|
|
3729
3897
|
quantities,
|
|
3730
3898
|
allowedVariantIds: null
|
|
@@ -3746,13 +3914,13 @@ function renderMultiStepBundle(container, bundle, outOfStockBehavior, onAddToCar
|
|
|
3746
3914
|
compareAtPrice: v.compareAtPrice,
|
|
3747
3915
|
unitPrice: v.unitPrice,
|
|
3748
3916
|
image: v.image,
|
|
3749
|
-
inventoryQuantity: null
|
|
3917
|
+
inventoryQuantity: v.inventoryQuantity ?? null
|
|
3750
3918
|
}))
|
|
3751
3919
|
};
|
|
3752
3920
|
})
|
|
3753
3921
|
);
|
|
3754
|
-
const
|
|
3755
|
-
if (
|
|
3922
|
+
const everyStepPickable = pools.length > 0 && pools.every((pool) => pool.some((p) => p.available));
|
|
3923
|
+
if (!everyStepPickable) return;
|
|
3756
3924
|
const formatMoney = intlFormatMoney(
|
|
3757
3925
|
bundle.products[0]?.priceRange.minVariantPrice.currencyCode ?? "USD"
|
|
3758
3926
|
);
|
|
@@ -3798,8 +3966,8 @@ function renderMultiStepBundle(container, bundle, outOfStockBehavior, onAddToCar
|
|
|
3798
3966
|
const overlay = buildPickerModal({
|
|
3799
3967
|
bundleGid: bundle.id,
|
|
3800
3968
|
domId: bundle.id.replace(/\D/g, ""),
|
|
3801
|
-
showSearch:
|
|
3802
|
-
showTypeFilters:
|
|
3969
|
+
showSearch: wc.showSearch !== false,
|
|
3970
|
+
showTypeFilters: wc.mixMatchShowTypeFilters !== false,
|
|
3803
3971
|
segmentCount: null,
|
|
3804
3972
|
wizard: true
|
|
3805
3973
|
});
|
|
@@ -3834,7 +4002,7 @@ function renderMultiStepBundle(container, bundle, outOfStockBehavior, onAddToCar
|
|
|
3834
4002
|
discountType: bundle.discountConfig.discountType,
|
|
3835
4003
|
discountValue: bundle.discountConfig.discountValue
|
|
3836
4004
|
},
|
|
3837
|
-
(total, type, value) => (0,
|
|
4005
|
+
(total, type, value) => (0, import_core5.calculateDiscount)(total, type, value),
|
|
3838
4006
|
updatePricing2(formatMoney)
|
|
3839
4007
|
);
|
|
3840
4008
|
updateProgress2(bodyDeps);
|
|
@@ -3846,8 +4014,8 @@ function renderMultiStepBundle(container, bundle, outOfStockBehavior, onAddToCar
|
|
|
3846
4014
|
selections,
|
|
3847
4015
|
t,
|
|
3848
4016
|
rulesMap,
|
|
3849
|
-
showQtySelector:
|
|
3850
|
-
showTypeFilters:
|
|
4017
|
+
showQtySelector: wc.mixMatchShowQuantitySelector !== false,
|
|
4018
|
+
showTypeFilters: wc.mixMatchShowTypeFilters !== false,
|
|
3851
4019
|
formatMoney,
|
|
3852
4020
|
dropdown: void 0,
|
|
3853
4021
|
portalTarget: null,
|
|
@@ -3897,7 +4065,7 @@ function renderMultiStepBundle(container, bundle, outOfStockBehavior, onAddToCar
|
|
|
3897
4065
|
}
|
|
3898
4066
|
|
|
3899
4067
|
// src/lime-bundle.ts
|
|
3900
|
-
var
|
|
4068
|
+
var import_core7 = require("@lime-bundles/core");
|
|
3901
4069
|
|
|
3902
4070
|
// src/utils/input-mode.ts
|
|
3903
4071
|
var NAV_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -5564,7 +5732,7 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
5564
5732
|
transition: opacity 0.25s ease, background 0.25s ease, color 0.25s ease, border-color 0.25s ease;
|
|
5565
5733
|
}
|
|
5566
5734
|
|
|
5567
|
-
.lb-mix-match__modal-add:hover {
|
|
5735
|
+
.lb-mix-match__modal-add:hover:not(:disabled) {
|
|
5568
5736
|
opacity: 0.9;
|
|
5569
5737
|
}
|
|
5570
5738
|
|
|
@@ -5679,8 +5847,15 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
5679
5847
|
color: color-mix(in srgb, var(--lb-picker-text) 70%, transparent);
|
|
5680
5848
|
}
|
|
5681
5849
|
|
|
5682
|
-
/* "Done" reuses the picker Add-button styling so it matches the Add buttons.
|
|
5850
|
+
/* "Done" reuses the picker Add-button styling so it matches the Add buttons.
|
|
5851
|
+
The flex centring + min-height pin the footer-button silhouette: the wizard
|
|
5852
|
+
Back (bundle-multi-step.css) copies these metrics so the pair stays
|
|
5853
|
+
equal-height whatever border widths the merchant's picker tokens set. */
|
|
5683
5854
|
.lb-mix-match__modal-done {
|
|
5855
|
+
display: inline-flex;
|
|
5856
|
+
align-items: center;
|
|
5857
|
+
justify-content: center;
|
|
5858
|
+
min-height: 40px;
|
|
5684
5859
|
padding: 10px 24px;
|
|
5685
5860
|
transition: opacity 0.25s ease;
|
|
5686
5861
|
background: var(--lb-picker-add-bg);
|
|
@@ -5694,7 +5869,7 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
5694
5869
|
cursor: pointer;
|
|
5695
5870
|
}
|
|
5696
5871
|
|
|
5697
|
-
.lb-mix-match__modal-done:hover {
|
|
5872
|
+
.lb-mix-match__modal-done:hover:not(:disabled) {
|
|
5698
5873
|
opacity: 0.9;
|
|
5699
5874
|
}
|
|
5700
5875
|
|
|
@@ -5704,6 +5879,16 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
5704
5879
|
box-shadow: none;
|
|
5705
5880
|
}
|
|
5706
5881
|
|
|
5882
|
+
/* Disabled Next/Done (wizard step minimum not met) \u2014 same washed-out
|
|
5883
|
+
treatment as a disabled row Add, so an unfinished step reads at a
|
|
5884
|
+
glance. Hover is gated off above; the solid button returns the moment
|
|
5885
|
+
the requirement is met. */
|
|
5886
|
+
.lb-mix-match__modal-done:disabled {
|
|
5887
|
+
background: color-mix(in srgb, var(--lb-picker-add-bg) 35%, var(--lb-picker-bg));
|
|
5888
|
+
color: color-mix(in srgb, var(--lb-picker-add-label) 85%, transparent);
|
|
5889
|
+
cursor: not-allowed;
|
|
5890
|
+
}
|
|
5891
|
+
|
|
5707
5892
|
/* Hidden utility for search filtering */
|
|
5708
5893
|
.lb-hidden {
|
|
5709
5894
|
display: none !important;
|
|
@@ -5960,6 +6145,13 @@ var BUNDLE_VOLUME_CSS = `/* Lime Bundles \u2014 Volume / Quantity Breaks styles
|
|
|
5960
6145
|
white-space: nowrap;
|
|
5961
6146
|
font-variant-numeric: tabular-nums;
|
|
5962
6147
|
}
|
|
6148
|
+
|
|
6149
|
+
/* Tier the selected variant can't cover \u2014 same greyed treatment as an
|
|
6150
|
+
out-of-stock product row, and not selectable. */
|
|
6151
|
+
.lb-volume__tier--oos {
|
|
6152
|
+
opacity: 0.5;
|
|
6153
|
+
cursor: not-allowed;
|
|
6154
|
+
}
|
|
5963
6155
|
`;
|
|
5964
6156
|
var BUNDLE_BOGO_CSS = `/* Lime Bundles \u2014 BOGO (Buy X Get Y) bundle styles */
|
|
5965
6157
|
|
|
@@ -6128,14 +6320,23 @@ var BUNDLE_MULTI_STEP_CSS = `/**
|
|
|
6128
6320
|
justify-self: end;
|
|
6129
6321
|
}
|
|
6130
6322
|
|
|
6131
|
-
/* Back \u2014 secondary treatment beside the primary Next/Done
|
|
6132
|
-
.lb-mix-match__modal-done).
|
|
6323
|
+
/* Back \u2014 secondary (outlined) treatment beside the primary Next/Done
|
|
6324
|
+
(which reuse .lb-mix-match__modal-done). Same silhouette as Next \u2014
|
|
6325
|
+
metrics copied from the modal-done recipe (padding, type, radius,
|
|
6326
|
+
min-height) \u2014 and picker tokens only: the modal is portaled out of the
|
|
6327
|
+
widget, so the widget vars (--lb-text, --lb-border-*) it previously
|
|
6328
|
+
used don't reliably resolve here and break palette isolation. */
|
|
6133
6329
|
.lb-multi-step__modal-back {
|
|
6134
|
-
|
|
6330
|
+
display: inline-flex;
|
|
6331
|
+
align-items: center;
|
|
6332
|
+
justify-content: center;
|
|
6333
|
+
min-height: 40px;
|
|
6334
|
+
padding: 10px 24px;
|
|
6135
6335
|
background: none;
|
|
6136
|
-
border: var(--lb-border-width) solid var(--lb-border-color);
|
|
6137
|
-
border-radius: var(--lb-radius-sm);
|
|
6138
|
-
|
|
6336
|
+
border: var(--lb-picker-border-width) solid var(--lb-picker-border-color);
|
|
6337
|
+
border-radius: var(--lb-picker-radius-sm);
|
|
6338
|
+
box-sizing: border-box;
|
|
6339
|
+
color: var(--lb-picker-text);
|
|
6139
6340
|
font-family: inherit;
|
|
6140
6341
|
font-size: 14px;
|
|
6141
6342
|
font-weight: 600;
|
|
@@ -6144,7 +6345,7 @@ var BUNDLE_MULTI_STEP_CSS = `/**
|
|
|
6144
6345
|
}
|
|
6145
6346
|
|
|
6146
6347
|
.lb-multi-step__modal-back:hover {
|
|
6147
|
-
background: color-mix(in srgb, var(--lb-text) 5%, transparent);
|
|
6348
|
+
background: color-mix(in srgb, var(--lb-picker-text) 5%, transparent);
|
|
6148
6349
|
}
|
|
6149
6350
|
|
|
6150
6351
|
.lb-multi-step__modal-back:focus-visible {
|
|
@@ -6553,6 +6754,13 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
6553
6754
|
buyer = void 0;
|
|
6554
6755
|
shadow;
|
|
6555
6756
|
bundles = [];
|
|
6757
|
+
/**
|
|
6758
|
+
* The product handle this embed is standing on, when one can be resolved
|
|
6759
|
+
* (attribute → meta tag → /products/<handle> path). The mix-and-match
|
|
6760
|
+
* courtesy seed uses it to pre-add the current product the way the theme
|
|
6761
|
+
* widget does; null on pages with no product context.
|
|
6762
|
+
*/
|
|
6763
|
+
currentProductHandle = null;
|
|
6556
6764
|
abortController = null;
|
|
6557
6765
|
impressionCleanups = [];
|
|
6558
6766
|
/**
|
|
@@ -6567,12 +6775,6 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
6567
6775
|
* `.lb-bundle-widget { ... }` reach the widget's DOM.
|
|
6568
6776
|
*/
|
|
6569
6777
|
shopCustomCss = null;
|
|
6570
|
-
/**
|
|
6571
|
-
* Shop-wide out-of-stock behaviour, resolved from the `$app` shop metafield
|
|
6572
|
-
* before the first render. Defaults to `show_greyed_out` so a failed or
|
|
6573
|
-
* missing fetch degrades to "bundles still render".
|
|
6574
|
-
*/
|
|
6575
|
-
outOfStockBehavior = import_core5.DEFAULT_OUT_OF_STOCK_BEHAVIOR;
|
|
6576
6778
|
constructor() {
|
|
6577
6779
|
super();
|
|
6578
6780
|
this.shadow = this.attachShadow({ mode: "open" });
|
|
@@ -6634,13 +6836,13 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
6634
6836
|
* no buyer/country/language is set.
|
|
6635
6837
|
*/
|
|
6636
6838
|
wrapQueryForContext(query) {
|
|
6637
|
-
return (0,
|
|
6839
|
+
return (0, import_core6.hasInContext)({
|
|
6638
6840
|
shopDomain: this.shopDomain,
|
|
6639
6841
|
accessToken: this.storefrontToken,
|
|
6640
6842
|
country: this.country,
|
|
6641
6843
|
language: this.language,
|
|
6642
6844
|
buyer: this.buyer
|
|
6643
|
-
}) ? (0,
|
|
6845
|
+
}) ? (0, import_core6.withInContext)(query) : query;
|
|
6644
6846
|
}
|
|
6645
6847
|
/**
|
|
6646
6848
|
* Returns true if this bundle should be hidden for the current market.
|
|
@@ -6649,7 +6851,7 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
6649
6851
|
* marketIds doesn't include the configured market.
|
|
6650
6852
|
*/
|
|
6651
6853
|
isMarketHidden(bundle) {
|
|
6652
|
-
return !(0,
|
|
6854
|
+
return !(0, import_core6.isVisibleInMarket)(bundle, this.marketId);
|
|
6653
6855
|
}
|
|
6654
6856
|
async fetchBundle() {
|
|
6655
6857
|
if (!this.shopDomain || !this.storefrontToken) {
|
|
@@ -6662,7 +6864,7 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
6662
6864
|
const controller = new AbortController();
|
|
6663
6865
|
this.abortController = controller;
|
|
6664
6866
|
this.renderLoading();
|
|
6665
|
-
const client = (0,
|
|
6867
|
+
const client = (0, import_core6.createStorefrontClient)({
|
|
6666
6868
|
shopDomain: this.shopDomain,
|
|
6667
6869
|
accessToken: this.storefrontToken,
|
|
6668
6870
|
country: this.country,
|
|
@@ -6672,11 +6874,12 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
6672
6874
|
try {
|
|
6673
6875
|
let bundlePromise;
|
|
6674
6876
|
let singleBundleMode = false;
|
|
6877
|
+
this.currentProductHandle = resolveProductHandle(this.productHandleAttr);
|
|
6675
6878
|
if (this.bundleGid) {
|
|
6676
6879
|
singleBundleMode = true;
|
|
6677
6880
|
bundlePromise = this.fetchSingleBundle(client, controller.signal);
|
|
6678
6881
|
} else {
|
|
6679
|
-
const handle =
|
|
6882
|
+
const handle = this.currentProductHandle;
|
|
6680
6883
|
if (!handle) {
|
|
6681
6884
|
this.teardownImpressions();
|
|
6682
6885
|
this.renderError(
|
|
@@ -6690,7 +6893,7 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
6690
6893
|
handle
|
|
6691
6894
|
);
|
|
6692
6895
|
}
|
|
6693
|
-
const shopSettingsPromise = client.query(
|
|
6896
|
+
const shopSettingsPromise = client.query(import_core6.SHOP_SETTINGS_QUERY, void 0, {
|
|
6694
6897
|
signal: controller.signal
|
|
6695
6898
|
}).catch(() => null);
|
|
6696
6899
|
await bundlePromise;
|
|
@@ -6703,13 +6906,10 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
6703
6906
|
const shopSettings = await shopSettingsPromise;
|
|
6704
6907
|
const customCss = shopSettings?.shop?.customCss?.value;
|
|
6705
6908
|
if (customCss) {
|
|
6706
|
-
(0,
|
|
6707
|
-
const sanitized = (0,
|
|
6909
|
+
(0, import_core6.injectCustomCss)(this.shopDomain, customCss);
|
|
6910
|
+
const sanitized = (0, import_core6.sanitizeCustomCss)(customCss);
|
|
6708
6911
|
if (sanitized.ok) this.shopCustomCss = sanitized.css;
|
|
6709
6912
|
}
|
|
6710
|
-
this.outOfStockBehavior = (0, import_core5.parseOutOfStockBehavior)(
|
|
6711
|
-
shopSettings?.shop?.outOfStockBehavior?.value
|
|
6712
|
-
);
|
|
6713
6913
|
this.renderBundles();
|
|
6714
6914
|
} catch (err) {
|
|
6715
6915
|
if (controller.signal.aborted) return;
|
|
@@ -6722,7 +6922,7 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
6722
6922
|
}
|
|
6723
6923
|
async fetchSingleBundle(client, signal) {
|
|
6724
6924
|
const data = await client.query(
|
|
6725
|
-
this.wrapQueryForContext(
|
|
6925
|
+
this.wrapQueryForContext(import_core6.BUNDLE_METAOBJECT_QUERY),
|
|
6726
6926
|
{ id: this.bundleGid },
|
|
6727
6927
|
{ signal }
|
|
6728
6928
|
);
|
|
@@ -6730,7 +6930,7 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
6730
6930
|
this.bundles = [];
|
|
6731
6931
|
return;
|
|
6732
6932
|
}
|
|
6733
|
-
const parsed = (0,
|
|
6933
|
+
const parsed = (0, import_core6.parseMetaobjectBundle)(
|
|
6734
6934
|
data.metaobject.id,
|
|
6735
6935
|
data.metaobject.fields
|
|
6736
6936
|
);
|
|
@@ -6738,7 +6938,7 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
6738
6938
|
}
|
|
6739
6939
|
async fetchProductBundles(client, signal, productHandle) {
|
|
6740
6940
|
const data = await client.query(
|
|
6741
|
-
this.wrapQueryForContext(
|
|
6941
|
+
this.wrapQueryForContext(import_core6.BUNDLES_FOR_PRODUCT_QUERY),
|
|
6742
6942
|
{ handle: productHandle },
|
|
6743
6943
|
{ signal }
|
|
6744
6944
|
);
|
|
@@ -6749,10 +6949,10 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
6749
6949
|
const refs = data.product.metafield?.references?.nodes ?? [];
|
|
6750
6950
|
const bundles = [];
|
|
6751
6951
|
for (const ref of refs) {
|
|
6752
|
-
const parsed = (0,
|
|
6952
|
+
const parsed = (0, import_core6.parseMetaobjectBundle)(ref.id, ref.fields);
|
|
6753
6953
|
if (parsed && !this.isMarketHidden(parsed)) bundles.push(parsed);
|
|
6754
6954
|
}
|
|
6755
|
-
this.bundles = (0,
|
|
6955
|
+
this.bundles = (0, import_core6.resolveAbTests)(bundles, (0, import_core6.getVisitorId)());
|
|
6756
6956
|
}
|
|
6757
6957
|
/**
|
|
6758
6958
|
* Dispatch add-to-cart with a cancelable event, then — unless a listener
|
|
@@ -6782,33 +6982,59 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
6782
6982
|
*/
|
|
6783
6983
|
async defaultAddToCart(lines) {
|
|
6784
6984
|
if (typeof window === "undefined") return;
|
|
6785
|
-
const client = (0,
|
|
6985
|
+
const client = (0, import_core6.createStorefrontClient)({
|
|
6786
6986
|
shopDomain: this.shopDomain,
|
|
6787
6987
|
accessToken: this.storefrontToken
|
|
6788
6988
|
});
|
|
6789
6989
|
const storage = window.localStorage;
|
|
6790
6990
|
const key = cartStorageKey(this.shopDomain);
|
|
6791
6991
|
const existingCartId = storage?.getItem(key) ?? null;
|
|
6992
|
+
const isStaleCartError = (errors) => errors.length > 0 && errors.every((e) => (e.field ?? []).includes("cartId"));
|
|
6993
|
+
const stockCapped = (warnings) => (warnings ?? []).some((w) => w.code === "MERCHANDISE_NOT_ENOUGH_STOCK");
|
|
6994
|
+
const fail = (message) => {
|
|
6995
|
+
this.dispatchEvent(
|
|
6996
|
+
new CustomEvent("lime-bundle:error", {
|
|
6997
|
+
detail: { message, code: "CART_ERROR" },
|
|
6998
|
+
bubbles: true,
|
|
6999
|
+
composed: true
|
|
7000
|
+
})
|
|
7001
|
+
);
|
|
7002
|
+
};
|
|
6792
7003
|
try {
|
|
6793
7004
|
let checkoutUrl = null;
|
|
6794
7005
|
if (existingCartId) {
|
|
6795
7006
|
const res = await client.query(
|
|
6796
|
-
|
|
7007
|
+
import_core6.CART_LINES_ADD_MUTATION,
|
|
6797
7008
|
{ cartId: existingCartId, lines }
|
|
6798
7009
|
);
|
|
6799
7010
|
const payload = res.cartLinesAdd;
|
|
6800
7011
|
if (payload?.userErrors?.length) {
|
|
7012
|
+
if (!isStaleCartError(payload.userErrors)) {
|
|
7013
|
+
fail(payload.userErrors[0].message);
|
|
7014
|
+
return;
|
|
7015
|
+
}
|
|
6801
7016
|
storage?.removeItem(key);
|
|
7017
|
+
} else if (stockCapped(payload?.warnings)) {
|
|
7018
|
+
fail("Some items in this bundle are out of stock.");
|
|
7019
|
+
return;
|
|
6802
7020
|
} else if (payload?.cart) {
|
|
6803
7021
|
checkoutUrl = payload.cart.checkoutUrl;
|
|
6804
7022
|
}
|
|
6805
7023
|
}
|
|
6806
7024
|
if (!checkoutUrl) {
|
|
6807
7025
|
const res = await client.query(
|
|
6808
|
-
|
|
7026
|
+
import_core6.CART_CREATE_MUTATION,
|
|
6809
7027
|
{ input: { lines } }
|
|
6810
7028
|
);
|
|
6811
7029
|
const payload = res.cartCreate;
|
|
7030
|
+
if (payload?.userErrors?.length) {
|
|
7031
|
+
fail(payload.userErrors[0].message);
|
|
7032
|
+
return;
|
|
7033
|
+
}
|
|
7034
|
+
if (stockCapped(payload?.warnings)) {
|
|
7035
|
+
fail("Some items in this bundle are out of stock.");
|
|
7036
|
+
return;
|
|
7037
|
+
}
|
|
6812
7038
|
if (payload?.cart) {
|
|
6813
7039
|
storage?.setItem(key, payload.cart.id);
|
|
6814
7040
|
checkoutUrl = payload.cart.checkoutUrl;
|
|
@@ -6817,13 +7043,7 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
6817
7043
|
if (checkoutUrl) {
|
|
6818
7044
|
window.location.assign(checkoutUrl);
|
|
6819
7045
|
} else {
|
|
6820
|
-
|
|
6821
|
-
new CustomEvent("lime-bundle:error", {
|
|
6822
|
-
detail: { message: "Cart creation failed", code: "CART_ERROR" },
|
|
6823
|
-
bubbles: true,
|
|
6824
|
-
composed: true
|
|
6825
|
-
})
|
|
6826
|
-
);
|
|
7046
|
+
fail("Cart creation failed");
|
|
6827
7047
|
}
|
|
6828
7048
|
} catch (err) {
|
|
6829
7049
|
this.dispatchEvent(
|
|
@@ -6851,7 +7071,7 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
6851
7071
|
const price = variant ? parseFloat(variant.price.amount) : 0;
|
|
6852
7072
|
return sum + price * line.quantity;
|
|
6853
7073
|
}, 0);
|
|
6854
|
-
(0,
|
|
7074
|
+
(0, import_core6.reportAddToCart)(
|
|
6855
7075
|
{ shopDomain: this.shopDomain, appUrl: this.appUrl },
|
|
6856
7076
|
{
|
|
6857
7077
|
bundleGid: bundle.id,
|
|
@@ -6890,49 +7110,32 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
6890
7110
|
container.setAttribute("aria-label", bundle.title);
|
|
6891
7111
|
container.setAttribute("data-bundle-type", bundle.bundleType);
|
|
6892
7112
|
container.setAttribute("data-bundle-gid", bundle.id);
|
|
6893
|
-
(
|
|
7113
|
+
if (bundle.endsAt) container.setAttribute("data-ends-at", bundle.endsAt);
|
|
7114
|
+
(0, import_core7.applyWidgetConfigVars)(container, bundle.widgetConfig);
|
|
6894
7115
|
this.renderCleanups.push(trackInputMode(container));
|
|
6895
7116
|
const dispatch = (lines) => this.handleAddToCart(bundle, lines);
|
|
6896
7117
|
const registerCleanup = (fn) => this.renderCleanups.push(fn);
|
|
6897
7118
|
switch (bundle.bundleType) {
|
|
6898
7119
|
case "fixed":
|
|
6899
|
-
renderFixedBundle(
|
|
6900
|
-
container,
|
|
6901
|
-
bundle,
|
|
6902
|
-
this.outOfStockBehavior,
|
|
6903
|
-
dispatch,
|
|
6904
|
-
registerCleanup
|
|
6905
|
-
);
|
|
7120
|
+
renderFixedBundle(container, bundle, dispatch, registerCleanup);
|
|
6906
7121
|
break;
|
|
6907
7122
|
case "mix_match":
|
|
6908
7123
|
renderMixMatchBundle(
|
|
6909
7124
|
container,
|
|
6910
7125
|
bundle,
|
|
6911
|
-
this.outOfStockBehavior,
|
|
6912
7126
|
dispatch,
|
|
6913
|
-
registerCleanup
|
|
7127
|
+
registerCleanup,
|
|
7128
|
+
this.currentProductHandle
|
|
6914
7129
|
);
|
|
6915
7130
|
break;
|
|
6916
7131
|
case "volume":
|
|
6917
|
-
renderVolumeBundle(container, bundle,
|
|
7132
|
+
renderVolumeBundle(container, bundle, dispatch);
|
|
6918
7133
|
break;
|
|
6919
7134
|
case "bogo":
|
|
6920
|
-
renderBogoBundle(
|
|
6921
|
-
container,
|
|
6922
|
-
bundle,
|
|
6923
|
-
this.outOfStockBehavior,
|
|
6924
|
-
dispatch,
|
|
6925
|
-
registerCleanup
|
|
6926
|
-
);
|
|
7135
|
+
renderBogoBundle(container, bundle, dispatch, registerCleanup);
|
|
6927
7136
|
break;
|
|
6928
7137
|
case "multi_step":
|
|
6929
|
-
renderMultiStepBundle(
|
|
6930
|
-
container,
|
|
6931
|
-
bundle,
|
|
6932
|
-
this.outOfStockBehavior,
|
|
6933
|
-
dispatch,
|
|
6934
|
-
registerCleanup
|
|
6935
|
-
);
|
|
7138
|
+
renderMultiStepBundle(container, bundle, dispatch, registerCleanup);
|
|
6936
7139
|
break;
|
|
6937
7140
|
}
|
|
6938
7141
|
this.shadow.appendChild(container);
|
|
@@ -6956,8 +7159,8 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
6956
7159
|
}
|
|
6957
7160
|
setupImpressionFor(bundle, element) {
|
|
6958
7161
|
if (!this.analyticsEnabled || !this.appUrl) return;
|
|
6959
|
-
const cleanup = (0,
|
|
6960
|
-
(0,
|
|
7162
|
+
const cleanup = (0, import_core6.observeImpression)(element, () => {
|
|
7163
|
+
(0, import_core6.reportImpression)(
|
|
6961
7164
|
{ shopDomain: this.shopDomain, appUrl: this.appUrl },
|
|
6962
7165
|
{
|
|
6963
7166
|
bundleGid: bundle.id,
|