@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.js
CHANGED
|
@@ -15,8 +15,6 @@ import {
|
|
|
15
15
|
hasInContext,
|
|
16
16
|
withInContext,
|
|
17
17
|
isVisibleInMarket,
|
|
18
|
-
parseOutOfStockBehavior,
|
|
19
|
-
DEFAULT_OUT_OF_STOCK_BEHAVIOR,
|
|
20
18
|
getVisitorId,
|
|
21
19
|
resolveAbTests
|
|
22
20
|
} from "@lime-bundles/core";
|
|
@@ -25,7 +23,8 @@ import {
|
|
|
25
23
|
import {
|
|
26
24
|
formatUnitPrice,
|
|
27
25
|
isVariantFulfillable,
|
|
28
|
-
resolveBundleQty
|
|
26
|
+
resolveBundleQty,
|
|
27
|
+
stockCapForVariant
|
|
29
28
|
} from "@lime-bundles/core";
|
|
30
29
|
function numericId(gid) {
|
|
31
30
|
const tail = gid.slice(gid.lastIndexOf("/") + 1);
|
|
@@ -49,7 +48,10 @@ function adaptVariant(v, requiredQty, fallbackCurrency) {
|
|
|
49
48
|
price: toCents(v.price?.amount),
|
|
50
49
|
compareAtPrice: toCents(v.compareAtPrice?.amount),
|
|
51
50
|
unitPrice: formatUnitPrice(v.unitPrice, v.unitPriceMeasurement, fallbackCurrency),
|
|
52
|
-
image: v.image?.url ?? null
|
|
51
|
+
image: v.image?.url ?? null,
|
|
52
|
+
// Carried so the picker's stepper cap and cross-slot stock guard can
|
|
53
|
+
// bind on the headless surface; dropping it here left them capless.
|
|
54
|
+
inventoryQuantity: stockCapForVariant(v)
|
|
53
55
|
};
|
|
54
56
|
}
|
|
55
57
|
function adaptProduct(product, bundleId, opts) {
|
|
@@ -104,6 +106,14 @@ function findVariant(product, variantId) {
|
|
|
104
106
|
}
|
|
105
107
|
return product.variants[0];
|
|
106
108
|
}
|
|
109
|
+
function resolveSellableVariant(product) {
|
|
110
|
+
const seeded = findVariant(product, product.selectedVariantId);
|
|
111
|
+
if (seeded && seeded.available !== false) return seeded;
|
|
112
|
+
for (let i = 0; i < product.variants.length; i++) {
|
|
113
|
+
if (product.variants[i].available !== false) return product.variants[i];
|
|
114
|
+
}
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
107
117
|
function findVariantByOptions(product, optionValues) {
|
|
108
118
|
if (!product.variants || !optionValues) return null;
|
|
109
119
|
for (let i = 0; i < product.variants.length; i++) {
|
|
@@ -508,7 +518,7 @@ function buildWidgetShell(typeClass, opts) {
|
|
|
508
518
|
"data-countdown": ""
|
|
509
519
|
});
|
|
510
520
|
const label2 = box("span", "lb-bundle-countdown-label");
|
|
511
|
-
label2.textContent = opts.countdownLabel ?? "
|
|
521
|
+
label2.textContent = opts.countdownLabel ?? "Limited time offer";
|
|
512
522
|
countdown.appendChild(label2);
|
|
513
523
|
countdown.appendChild(
|
|
514
524
|
box("span", "lb-bundle-countdown-timer", {
|
|
@@ -520,10 +530,12 @@ function buildWidgetShell(typeClass, opts) {
|
|
|
520
530
|
const products = box("div", typeClass + "__products lb-edge-fade");
|
|
521
531
|
root.appendChild(products);
|
|
522
532
|
root.appendChild(box("div", "lb-bundle-divider"));
|
|
523
|
-
const summary = box("div", "lb-bundle-summary"
|
|
533
|
+
const summary = box("div", "lb-bundle-summary", {
|
|
534
|
+
"data-pricing-section": ""
|
|
535
|
+
});
|
|
524
536
|
const text = box("div", "lb-bundle-summary__text");
|
|
525
537
|
const label = box("span", "lb-bundle-summary__label");
|
|
526
|
-
label.textContent = opts.summaryLabel ?? "Bundle
|
|
538
|
+
label.textContent = opts.summaryLabel ?? "Bundle total";
|
|
527
539
|
text.appendChild(label);
|
|
528
540
|
if (opts.withItemCount) {
|
|
529
541
|
label.appendChild(box("span", "", { "data-item-count": "" }));
|
|
@@ -554,6 +566,7 @@ function buildWidgetShell(typeClass, opts) {
|
|
|
554
566
|
summary.appendChild(prices);
|
|
555
567
|
root.appendChild(summary);
|
|
556
568
|
const cta = box("button", "lb-bundle-cta", {
|
|
569
|
+
"data-cta-text": opts.ctaText,
|
|
557
570
|
type: "button",
|
|
558
571
|
"data-add-bundle": ""
|
|
559
572
|
});
|
|
@@ -572,7 +585,9 @@ function buildWidgetShell(typeClass, opts) {
|
|
|
572
585
|
);
|
|
573
586
|
return { root, products, cta };
|
|
574
587
|
}
|
|
575
|
-
function buildVolumeTiers(tiers, groupLabel) {
|
|
588
|
+
function buildVolumeTiers(tiers, groupLabel, opts = {}) {
|
|
589
|
+
const showCompare = opts.showCompare !== false;
|
|
590
|
+
const showPriceEach = opts.showPriceEach !== false;
|
|
576
591
|
const group = box("div", "lb-volume__tiers lb-edge-fade", {
|
|
577
592
|
role: "radiogroup",
|
|
578
593
|
"aria-label": groupLabel,
|
|
@@ -597,8 +612,17 @@ function buildVolumeTiers(tiers, groupLabel) {
|
|
|
597
612
|
label.textContent = tier.label;
|
|
598
613
|
info.appendChild(label);
|
|
599
614
|
const price = box("span", "lb-volume__tier-price");
|
|
600
|
-
|
|
601
|
-
|
|
615
|
+
if (showCompare && (tier.percent > 0 || tier.amountCents > 0)) {
|
|
616
|
+
price.appendChild(box("span", "lb-volume__tier-compare"));
|
|
617
|
+
}
|
|
618
|
+
if (showPriceEach) {
|
|
619
|
+
price.appendChild(box("span", "", { "data-tier-price-each": "" }));
|
|
620
|
+
if (opts.unitLabel) {
|
|
621
|
+
const unit = box("span", "lb-volume__tier-unit");
|
|
622
|
+
unit.textContent = opts.unitLabel;
|
|
623
|
+
price.appendChild(unit);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
602
626
|
info.appendChild(price);
|
|
603
627
|
el2.appendChild(info);
|
|
604
628
|
if (tier.badgeLabel || tier.savingsLabel) {
|
|
@@ -707,7 +731,10 @@ var DEFAULT_STRINGS = {
|
|
|
707
731
|
required: "Required",
|
|
708
732
|
soldOut: "Sold out",
|
|
709
733
|
outOfStock: "Out of stock",
|
|
710
|
-
itemsOutOfStock:
|
|
734
|
+
itemsOutOfStock: {
|
|
735
|
+
one: "__COUNT__ item out of stock",
|
|
736
|
+
other: "__COUNT__ items out of stock"
|
|
737
|
+
},
|
|
711
738
|
quantity: "Quantity",
|
|
712
739
|
increaseQuantity: "Increase quantity",
|
|
713
740
|
decreaseQuantity: "Decrease quantity",
|
|
@@ -727,11 +754,26 @@ var DEFAULT_STRINGS = {
|
|
|
727
754
|
stepsCompleted: "__COUNT__ of __TOTAL__ steps completed",
|
|
728
755
|
each: " each",
|
|
729
756
|
buyQty: "Buy __COUNT__",
|
|
757
|
+
itemCount: {
|
|
758
|
+
one: "__COUNT__ item",
|
|
759
|
+
other: "__COUNT__ items"
|
|
760
|
+
},
|
|
730
761
|
needsSpots: {
|
|
731
762
|
one: "Needs __COUNT__ spot",
|
|
732
763
|
other: "Needs __COUNT__ spots"
|
|
733
764
|
}
|
|
734
765
|
};
|
|
766
|
+
function formatItemsOutOfStock(t, count) {
|
|
767
|
+
const raw = t.itemsOutOfStock;
|
|
768
|
+
let tpl;
|
|
769
|
+
if (raw && typeof raw === "object") {
|
|
770
|
+
const form = new Intl.PluralRules(t.locale || "en").select(count);
|
|
771
|
+
tpl = raw[form] || raw.other;
|
|
772
|
+
} else {
|
|
773
|
+
tpl = raw;
|
|
774
|
+
}
|
|
775
|
+
return (tpl || "__COUNT__ items out of stock").split("__COUNT__").join(String(count));
|
|
776
|
+
}
|
|
735
777
|
|
|
736
778
|
// ../render/src/dropdown/bind.ts
|
|
737
779
|
import { dropdown } from "@lime-bundles/core";
|
|
@@ -1142,12 +1184,11 @@ function bindDropdowns(root) {
|
|
|
1142
1184
|
}
|
|
1143
1185
|
|
|
1144
1186
|
// src/renderers/fixed.ts
|
|
1145
|
-
function renderFixedBundle(container, bundle,
|
|
1187
|
+
function renderFixedBundle(container, bundle, onAddToCart, onCleanup) {
|
|
1146
1188
|
const wc = bundle.widgetConfig;
|
|
1147
1189
|
const products = adaptProducts(bundle);
|
|
1148
1190
|
if (!products.length) return;
|
|
1149
1191
|
const oosProducts = products.filter((p) => !p.variants.some((v) => v.available));
|
|
1150
|
-
if (outOfStockBehavior === "hide" && oosProducts.length > 0) return;
|
|
1151
1192
|
const currency = bundle.products[0]?.priceRange.minVariantPrice.currencyCode ?? "USD";
|
|
1152
1193
|
const formatMoney = intlFormatMoney(currency);
|
|
1153
1194
|
const t = DEFAULT_STRINGS;
|
|
@@ -1172,7 +1213,9 @@ function renderFixedBundle(container, bundle, outOfStockBehavior, onAddToCart, o
|
|
|
1172
1213
|
});
|
|
1173
1214
|
shell.products.appendChild(row);
|
|
1174
1215
|
if (isOos) continue;
|
|
1175
|
-
const
|
|
1216
|
+
const sellable = resolveSellableVariant(product);
|
|
1217
|
+
if (sellable) product.selectedVariantId = sellable.id;
|
|
1218
|
+
const selected = sellable ?? findVariant(product, product.selectedVariantId);
|
|
1176
1219
|
hydrateRowControls(row, product, selected);
|
|
1177
1220
|
applyVariant(row, selected, product, formatMoney);
|
|
1178
1221
|
bindVariantSelects(
|
|
@@ -1188,7 +1231,7 @@ function renderFixedBundle(container, bundle, outOfStockBehavior, onAddToCart, o
|
|
|
1188
1231
|
shell.cta.disabled = true;
|
|
1189
1232
|
const label = shell.cta.querySelector("[data-cta-label]");
|
|
1190
1233
|
if (label) {
|
|
1191
|
-
label.textContent = (t
|
|
1234
|
+
label.textContent = formatItemsOutOfStock(t, oosProducts.length);
|
|
1192
1235
|
}
|
|
1193
1236
|
} else {
|
|
1194
1237
|
shell.cta.addEventListener("click", () => {
|
|
@@ -1767,6 +1810,17 @@ function unlock() {
|
|
|
1767
1810
|
window.scrollTo(0, savedY);
|
|
1768
1811
|
}
|
|
1769
1812
|
|
|
1813
|
+
// ../render/src/picker/sort.ts
|
|
1814
|
+
function sinkAddedRows(list, rowEls, isAdded) {
|
|
1815
|
+
const front = [];
|
|
1816
|
+
const back = [];
|
|
1817
|
+
for (let i = 0; i < rowEls.length; i++) {
|
|
1818
|
+
(isAdded(i) ? back : front).push(rowEls[i]);
|
|
1819
|
+
}
|
|
1820
|
+
for (const el2 of front) list.appendChild(el2);
|
|
1821
|
+
for (const el2 of back) list.appendChild(el2);
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1770
1824
|
// ../render/src/mix-match/modal.ts
|
|
1771
1825
|
var ALL_TYPES = "__all__";
|
|
1772
1826
|
var CLOSE_TRANSITION_MS = 300;
|
|
@@ -1991,12 +2045,22 @@ function createPickerModal(deps) {
|
|
|
1991
2045
|
syncActiveFilterPill();
|
|
1992
2046
|
if (searchInput) searchInput.value = "";
|
|
1993
2047
|
filterProducts(searchInput ? searchInput.value : "");
|
|
2048
|
+
if (modalList) {
|
|
2049
|
+
const selectedProductIds = /* @__PURE__ */ Object.create(null);
|
|
2050
|
+
for (const it of items) selectedProductIds[String(it.productId)] = true;
|
|
2051
|
+
sinkAddedRows(
|
|
2052
|
+
modalList,
|
|
2053
|
+
rows.map((r) => r.el),
|
|
2054
|
+
(i) => selectedProductIds[String(eligibleProducts[i].id)] === true
|
|
2055
|
+
);
|
|
2056
|
+
modalList.scrollTop = 0;
|
|
2057
|
+
}
|
|
1994
2058
|
overlay.style.display = "";
|
|
1995
2059
|
void overlay.offsetHeight;
|
|
1996
2060
|
overlay.classList.add("lb-mix-match__modal-overlay--open");
|
|
1997
2061
|
lock();
|
|
1998
2062
|
setTimeout(() => {
|
|
1999
|
-
if (
|
|
2063
|
+
if (closeBtn) closeBtn.focus();
|
|
2000
2064
|
else modalDialog?.focus();
|
|
2001
2065
|
}, 50);
|
|
2002
2066
|
}
|
|
@@ -2553,17 +2617,17 @@ function buildPickerModal(opts) {
|
|
|
2553
2617
|
}
|
|
2554
2618
|
|
|
2555
2619
|
// src/renderers/mix-match.ts
|
|
2556
|
-
function renderMixMatchBundle(container, bundle,
|
|
2620
|
+
function renderMixMatchBundle(container, bundle, onAddToCart, onCleanup, currentProductHandle) {
|
|
2557
2621
|
const wc = bundle.widgetConfig;
|
|
2558
2622
|
const t = DEFAULT_STRINGS;
|
|
2559
2623
|
const requiredQty = bundle.minQuantity || 1;
|
|
2560
2624
|
const rulesMap = mergeRuleMaps(bundle.productRules, bundle.variantRules);
|
|
2561
2625
|
const adapted = adaptProducts(bundle);
|
|
2562
|
-
const eligibleProducts = adapted.map((p) => ({
|
|
2626
|
+
const eligibleProducts = adapted.map((p, i) => ({
|
|
2563
2627
|
id: Number(p.productId),
|
|
2564
2628
|
title: p.title ?? "",
|
|
2565
2629
|
url: p.url ?? null,
|
|
2566
|
-
type: "",
|
|
2630
|
+
type: bundle.products[i]?.productType ?? "",
|
|
2567
2631
|
featuredImage: p.featuredImage ?? null,
|
|
2568
2632
|
available: p.variants.some((v) => v.available),
|
|
2569
2633
|
optionNames: p.optionNames,
|
|
@@ -2576,12 +2640,12 @@ function renderMixMatchBundle(container, bundle, outOfStockBehavior, onAddToCart
|
|
|
2576
2640
|
compareAtPrice: v.compareAtPrice,
|
|
2577
2641
|
unitPrice: v.unitPrice,
|
|
2578
2642
|
image: v.image,
|
|
2579
|
-
inventoryQuantity: null
|
|
2643
|
+
inventoryQuantity: v.inventoryQuantity ?? null
|
|
2580
2644
|
}))
|
|
2581
2645
|
}));
|
|
2582
2646
|
if (!eligibleProducts.length) return;
|
|
2583
2647
|
const poolUnits = eligibleProducts.filter((p) => p.available).length;
|
|
2584
|
-
if (
|
|
2648
|
+
if (poolUnits === 0) return;
|
|
2585
2649
|
const formatMoney = intlFormatMoney(
|
|
2586
2650
|
bundle.products[0]?.priceRange.minVariantPrice.currencyCode ?? "USD"
|
|
2587
2651
|
);
|
|
@@ -2608,16 +2672,21 @@ function renderMixMatchBundle(container, bundle, outOfStockBehavior, onAddToCart
|
|
|
2608
2672
|
const overlay = buildPickerModal({
|
|
2609
2673
|
bundleGid: bundle.id,
|
|
2610
2674
|
domId: bundle.id.replace(/\D/g, ""),
|
|
2611
|
-
showSearch:
|
|
2612
|
-
showTypeFilters: false,
|
|
2675
|
+
showSearch: wc.showSearch !== false,
|
|
2676
|
+
showTypeFilters: wc.mixMatchShowTypeFilters !== false,
|
|
2613
2677
|
segmentCount: requiredQty,
|
|
2614
2678
|
wizard: false
|
|
2615
2679
|
});
|
|
2616
2680
|
shell.root.appendChild(overlay);
|
|
2681
|
+
const currentIdx = currentProductHandle ? bundle.products.findIndex((p) => p.handle === currentProductHandle) : -1;
|
|
2617
2682
|
const selectedItems = seedSelection(
|
|
2618
2683
|
eligibleProducts,
|
|
2619
2684
|
rulesMap,
|
|
2620
|
-
requiredQty
|
|
2685
|
+
requiredQty,
|
|
2686
|
+
{
|
|
2687
|
+
enabled: !!currentProductHandle,
|
|
2688
|
+
currentProductId: currentIdx !== -1 ? eligibleProducts[currentIdx]?.id ?? null : null
|
|
2689
|
+
}
|
|
2621
2690
|
);
|
|
2622
2691
|
const rowCapacity = {
|
|
2623
2692
|
remainingSpots: () => remainingUnits(selectedItems, requiredQty),
|
|
@@ -2654,7 +2723,13 @@ function renderMixMatchBundle(container, bundle, outOfStockBehavior, onAddToCart
|
|
|
2654
2723
|
shell.root,
|
|
2655
2724
|
selectedItems,
|
|
2656
2725
|
requiredQty,
|
|
2657
|
-
|
|
2726
|
+
// The shared updater reads the theme data-block shape: discount fields
|
|
2727
|
+
// flat, not nested under discountConfig. Passing the bundle itself left
|
|
2728
|
+
// `discountType` undefined, so the summary never painted.
|
|
2729
|
+
{
|
|
2730
|
+
discountType: bundle.discountConfig.discountType,
|
|
2731
|
+
discountValue: bundle.discountConfig.discountValue
|
|
2732
|
+
},
|
|
2658
2733
|
(total, type, value) => calculateDiscount(total, type, value),
|
|
2659
2734
|
updatePricing2(formatMoney)
|
|
2660
2735
|
);
|
|
@@ -2667,7 +2742,7 @@ function renderMixMatchBundle(container, bundle, outOfStockBehavior, onAddToCart
|
|
|
2667
2742
|
rulesMap,
|
|
2668
2743
|
eligibleProducts,
|
|
2669
2744
|
requiredQty,
|
|
2670
|
-
showQtySelector:
|
|
2745
|
+
showQtySelector: wc.mixMatchShowQuantitySelector !== false,
|
|
2671
2746
|
items: selectedItems,
|
|
2672
2747
|
formatMoney,
|
|
2673
2748
|
rowCapacity,
|
|
@@ -2730,6 +2805,53 @@ function renderMixMatchBundle(container, bundle, outOfStockBehavior, onAddToCart
|
|
|
2730
2805
|
onCleanup?.(bindDropdowns(shell.root));
|
|
2731
2806
|
}
|
|
2732
2807
|
|
|
2808
|
+
// src/renderers/volume.ts
|
|
2809
|
+
import {
|
|
2810
|
+
bestValueTierIndex,
|
|
2811
|
+
isVariantFulfillable as isVariantFulfillable2,
|
|
2812
|
+
resolveDefaultTierIndex,
|
|
2813
|
+
resolvePopularTierIndex,
|
|
2814
|
+
stockCapForVariant as stockCapForVariant2
|
|
2815
|
+
} from "@lime-bundles/core";
|
|
2816
|
+
|
|
2817
|
+
// ../render/src/volume/stock.ts
|
|
2818
|
+
var TIER_OOS_CLASS = "lb-volume__tier--oos";
|
|
2819
|
+
function tierQty(el2) {
|
|
2820
|
+
return parseInt(el2.getAttribute("data-tier-qty") ?? "", 10) || 0;
|
|
2821
|
+
}
|
|
2822
|
+
function tierUnavailable(el2) {
|
|
2823
|
+
return el2.getAttribute("aria-disabled") === "true";
|
|
2824
|
+
}
|
|
2825
|
+
function applyTierStockState(tierEls, cap) {
|
|
2826
|
+
for (let i = 0; i < tierEls.length; i++) {
|
|
2827
|
+
const el2 = tierEls[i];
|
|
2828
|
+
const unavailable = cap !== null && tierQty(el2) > cap;
|
|
2829
|
+
if (unavailable) {
|
|
2830
|
+
el2.classList.add(TIER_OOS_CLASS);
|
|
2831
|
+
el2.setAttribute("aria-disabled", "true");
|
|
2832
|
+
} else {
|
|
2833
|
+
el2.classList.remove(TIER_OOS_CLASS);
|
|
2834
|
+
el2.removeAttribute("aria-disabled");
|
|
2835
|
+
}
|
|
2836
|
+
}
|
|
2837
|
+
}
|
|
2838
|
+
function resolveAvailableTierIndex(tierEls, preferred) {
|
|
2839
|
+
const p = tierEls[preferred];
|
|
2840
|
+
if (p && !tierUnavailable(p)) return preferred;
|
|
2841
|
+
for (let i = 0; i < tierEls.length; i++) {
|
|
2842
|
+
if (!tierUnavailable(tierEls[i])) return i;
|
|
2843
|
+
}
|
|
2844
|
+
return -1;
|
|
2845
|
+
}
|
|
2846
|
+
function stepToAvailableTier(tierEls, from, direction) {
|
|
2847
|
+
let i = from + direction;
|
|
2848
|
+
while (i >= 0 && i < tierEls.length) {
|
|
2849
|
+
if (!tierUnavailable(tierEls[i])) return i;
|
|
2850
|
+
i += direction;
|
|
2851
|
+
}
|
|
2852
|
+
return from;
|
|
2853
|
+
}
|
|
2854
|
+
|
|
2733
2855
|
// ../render/src/volume/pricing.ts
|
|
2734
2856
|
function calcTierPrice(basePrice, discountType, tierEl) {
|
|
2735
2857
|
if (discountType === "fixed_amount") {
|
|
@@ -2758,6 +2880,23 @@ function formatItemCount(qty, translations) {
|
|
|
2758
2880
|
const tpl = translations.itemCount[form] || translations.itemCount.other || "__COUNT__";
|
|
2759
2881
|
return " (" + tpl.split("__COUNT__").join(String(qty)) + ")";
|
|
2760
2882
|
}
|
|
2883
|
+
function updateAllTierPrices(allTiers, basePrice, discountType, formatMoney) {
|
|
2884
|
+
for (let i = 0; i < allTiers.length; i++) {
|
|
2885
|
+
const tierEl = allTiers[i];
|
|
2886
|
+
const priceEachEl = tierEl.querySelector(
|
|
2887
|
+
"[data-tier-price-each]"
|
|
2888
|
+
);
|
|
2889
|
+
if (priceEachEl) {
|
|
2890
|
+
priceEachEl.textContent = formatMoney(
|
|
2891
|
+
calcTierPrice(basePrice, discountType, tierEl)
|
|
2892
|
+
);
|
|
2893
|
+
}
|
|
2894
|
+
const compareEl = tierEl.querySelector(
|
|
2895
|
+
".lb-volume__tier-compare"
|
|
2896
|
+
);
|
|
2897
|
+
if (compareEl) compareEl.textContent = formatMoney(basePrice);
|
|
2898
|
+
}
|
|
2899
|
+
}
|
|
2761
2900
|
function selectTier(container, allTiers, basePrice, discountType, index, translations, formatMoney) {
|
|
2762
2901
|
for (let i = 0; i < allTiers.length; i++) {
|
|
2763
2902
|
allTiers[i].setAttribute("aria-checked", i === index ? "true" : "false");
|
|
@@ -2802,21 +2941,31 @@ function selectTier(container, allTiers, basePrice, discountType, index, transla
|
|
|
2802
2941
|
}
|
|
2803
2942
|
|
|
2804
2943
|
// src/renderers/volume.ts
|
|
2805
|
-
function renderVolumeBundle(container, bundle,
|
|
2944
|
+
function renderVolumeBundle(container, bundle, onAddToCart) {
|
|
2806
2945
|
const wc = bundle.widgetConfig;
|
|
2807
2946
|
const product = bundle.products[0];
|
|
2808
2947
|
if (!product) return;
|
|
2809
2948
|
const variants = product.variants.nodes;
|
|
2810
|
-
const
|
|
2949
|
+
const minTierQty = Math.min(
|
|
2950
|
+
...bundle.volumeTiers.map((tier) => tier.minQuantity)
|
|
2951
|
+
);
|
|
2952
|
+
const pricingVariant = variants.find((v) => isVariantFulfillable2(v, minTierQty)) ?? variants.find((v) => v.availableForSale) ?? variants[0];
|
|
2811
2953
|
if (!pricingVariant) return;
|
|
2812
|
-
const anyAvailable = variants.some((v) => v.availableForSale);
|
|
2813
|
-
if (outOfStockBehavior === "hide" && !anyAvailable) return;
|
|
2814
2954
|
const basePrice = toCents(pricingVariant.price.amount);
|
|
2815
2955
|
const discountType = bundle.discountConfig.discountType;
|
|
2816
2956
|
const formatMoney = intlFormatMoney(product.priceRange.minVariantPrice.currencyCode);
|
|
2817
2957
|
const t = DEFAULT_STRINGS;
|
|
2818
|
-
const
|
|
2819
|
-
const defaultIdx =
|
|
2958
|
+
const bestIdx = bestValueTierIndex(bundle.volumeTiers, discountType);
|
|
2959
|
+
const defaultIdx = resolveDefaultTierIndex(
|
|
2960
|
+
wc.defaultTier,
|
|
2961
|
+
bundle.volumeTiers.length,
|
|
2962
|
+
bestIdx
|
|
2963
|
+
);
|
|
2964
|
+
const popularIdx = resolvePopularTierIndex(
|
|
2965
|
+
wc.popularBadge?.tierIndex,
|
|
2966
|
+
wc.popularBadge?.visible !== false,
|
|
2967
|
+
bestIdx
|
|
2968
|
+
);
|
|
2820
2969
|
const tiers = bundle.volumeTiers.map((tier, i) => {
|
|
2821
2970
|
const percent = discountType === "fixed_amount" ? 0 : Math.round(tier.percentage ?? 0);
|
|
2822
2971
|
const amountCents = discountType === "fixed_amount" ? Math.round((tier.amount ?? 0) * 100) : 0;
|
|
@@ -2837,38 +2986,46 @@ function renderVolumeBundle(container, bundle, outOfStockBehavior, onAddToCart)
|
|
|
2837
2986
|
title: bundle.title,
|
|
2838
2987
|
subtitle: bundle.description,
|
|
2839
2988
|
summaryLabel: "Total",
|
|
2840
|
-
withItemCount:
|
|
2989
|
+
withItemCount: wc.pricing?.showItemCount !== false,
|
|
2841
2990
|
totalPriceSlot: true,
|
|
2842
2991
|
showSavingsBar: wc.savingsBar?.visible !== false,
|
|
2843
2992
|
showComparePrice: wc.pricing?.showCompareAtPrice !== false,
|
|
2844
2993
|
ctaText: wc.cta?.ctaText || t.addToCart || "Add to cart",
|
|
2845
2994
|
endsAt: wc.countdown?.showCountdown === false ? null : bundle.endsAt
|
|
2846
2995
|
});
|
|
2847
|
-
const group = buildVolumeTiers(tiers, "Select quantity"
|
|
2996
|
+
const group = buildVolumeTiers(tiers, "Select quantity", {
|
|
2997
|
+
showCompare: wc.pricing?.showComparePrice !== false,
|
|
2998
|
+
showPriceEach: wc.pricing?.showPerUnitPrice !== false,
|
|
2999
|
+
unitLabel: (t.each || "each").trim()
|
|
3000
|
+
});
|
|
2848
3001
|
shell.products.replaceWith(group);
|
|
2849
3002
|
const tierEls = group.querySelectorAll("[data-tier-index]");
|
|
2850
|
-
|
|
3003
|
+
updateAllTierPrices(tierEls, basePrice, discountType, formatMoney);
|
|
3004
|
+
applyTierStockState(tierEls, stockCapForVariant2(pricingVariant));
|
|
3005
|
+
let selectedIndex = resolveAvailableTierIndex(tierEls, defaultIdx);
|
|
3006
|
+
const noTierAvailable = selectedIndex === -1;
|
|
3007
|
+
if (noTierAvailable) selectedIndex = defaultIdx;
|
|
2851
3008
|
const apply = () => selectTier(shell.root, tierEls, basePrice, discountType, selectedIndex, t, formatMoney);
|
|
2852
3009
|
group.addEventListener("click", (e) => {
|
|
2853
3010
|
const el2 = e.target.closest("[data-tier-index]");
|
|
2854
|
-
if (!el2) return;
|
|
3011
|
+
if (!el2 || tierUnavailable(el2)) return;
|
|
2855
3012
|
selectedIndex = parseInt(el2.getAttribute("data-tier-index") ?? "", 10) || 0;
|
|
2856
3013
|
apply();
|
|
2857
3014
|
});
|
|
2858
3015
|
group.addEventListener("keydown", (e) => {
|
|
2859
3016
|
if (e.key === "ArrowDown" || e.key === "ArrowRight") {
|
|
2860
3017
|
e.preventDefault();
|
|
2861
|
-
selectedIndex =
|
|
3018
|
+
selectedIndex = stepToAvailableTier(tierEls, selectedIndex, 1);
|
|
2862
3019
|
apply();
|
|
2863
3020
|
tierEls[selectedIndex].focus();
|
|
2864
3021
|
} else if (e.key === "ArrowUp" || e.key === "ArrowLeft") {
|
|
2865
3022
|
e.preventDefault();
|
|
2866
|
-
selectedIndex =
|
|
3023
|
+
selectedIndex = stepToAvailableTier(tierEls, selectedIndex, -1);
|
|
2867
3024
|
apply();
|
|
2868
3025
|
tierEls[selectedIndex].focus();
|
|
2869
3026
|
}
|
|
2870
3027
|
});
|
|
2871
|
-
if (
|
|
3028
|
+
if (noTierAvailable) {
|
|
2872
3029
|
shell.cta.disabled = true;
|
|
2873
3030
|
const label = shell.cta.querySelector("[data-cta-label]");
|
|
2874
3031
|
if (label) label.textContent = t.outOfStock || "Out of stock";
|
|
@@ -2996,7 +3153,14 @@ function buildBogoCartItems(sides, percent, buyQty, getQty) {
|
|
|
2996
3153
|
}
|
|
2997
3154
|
|
|
2998
3155
|
// src/renderers/bogo.ts
|
|
2999
|
-
function
|
|
3156
|
+
function buildBogoPlus() {
|
|
3157
|
+
const plus = document.createElement("div");
|
|
3158
|
+
plus.className = "lb-bogo__plus";
|
|
3159
|
+
plus.setAttribute("aria-hidden", "true");
|
|
3160
|
+
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>';
|
|
3161
|
+
return plus;
|
|
3162
|
+
}
|
|
3163
|
+
function renderBogoBundle(container, bundle, onAddToCart, onCleanup) {
|
|
3000
3164
|
const wc = bundle.widgetConfig;
|
|
3001
3165
|
const buyProduct = bundle.products.find((p) => p.id === bundle.buyProductId);
|
|
3002
3166
|
const getProduct = bundle.products.find((p) => p.id === bundle.getProductId);
|
|
@@ -3025,7 +3189,6 @@ function renderBogoBundle(container, bundle, outOfStockBehavior, onAddToCart, on
|
|
|
3025
3189
|
}
|
|
3026
3190
|
];
|
|
3027
3191
|
const oosCount = sides.filter((s) => !s.variants.some((v) => v.available)).length;
|
|
3028
|
-
if (outOfStockBehavior === "hide" && oosCount > 0) return;
|
|
3029
3192
|
const formatMoney = intlFormatMoney(
|
|
3030
3193
|
buyProduct.priceRange.minVariantPrice.currencyCode
|
|
3031
3194
|
);
|
|
@@ -3044,6 +3207,7 @@ function renderBogoBundle(container, bundle, outOfStockBehavior, onAddToCart, on
|
|
|
3044
3207
|
const badgeText = percent >= 100 ? "Free" : percent + "% off";
|
|
3045
3208
|
sides.forEach((side, i) => {
|
|
3046
3209
|
const isGet = i === 1;
|
|
3210
|
+
if (isGet) shell.products.appendChild(buildBogoPlus());
|
|
3047
3211
|
const isOos = !side.variants.some((v) => v.available);
|
|
3048
3212
|
const row = buildRowSkeleton(side, t, {
|
|
3049
3213
|
isOos,
|
|
@@ -3072,7 +3236,7 @@ function renderBogoBundle(container, bundle, outOfStockBehavior, onAddToCart, on
|
|
|
3072
3236
|
shell.cta.disabled = true;
|
|
3073
3237
|
const label = shell.cta.querySelector("[data-cta-label]");
|
|
3074
3238
|
if (label) {
|
|
3075
|
-
label.textContent = (t
|
|
3239
|
+
label.textContent = formatItemsOutOfStock(t, oosCount);
|
|
3076
3240
|
}
|
|
3077
3241
|
} else {
|
|
3078
3242
|
shell.cta.addEventListener("click", () => {
|
|
@@ -3460,6 +3624,14 @@ function createWizard(deps) {
|
|
|
3460
3624
|
rows.push(row);
|
|
3461
3625
|
modalList.appendChild(row.el);
|
|
3462
3626
|
}
|
|
3627
|
+
const stepPicks = selections[forStep] || [];
|
|
3628
|
+
const selectedProductIds = /* @__PURE__ */ Object.create(null);
|
|
3629
|
+
for (const it of stepPicks) selectedProductIds[String(it.productId)] = true;
|
|
3630
|
+
sinkAddedRows(
|
|
3631
|
+
modalList,
|
|
3632
|
+
rows.map((r) => r.el),
|
|
3633
|
+
(i) => selectedProductIds[String(pool[i].id)] === true
|
|
3634
|
+
);
|
|
3463
3635
|
deps.dropdown?.bindAll(modalList);
|
|
3464
3636
|
}
|
|
3465
3637
|
function onListClick(e) {
|
|
@@ -3622,6 +3794,7 @@ function createWizard(deps) {
|
|
|
3622
3794
|
syncActiveFilterPill();
|
|
3623
3795
|
if (searchInput) searchInput.value = "";
|
|
3624
3796
|
filterProducts("");
|
|
3797
|
+
if (modalList) modalList.scrollTop = 0;
|
|
3625
3798
|
buildStepSegments(stepIndex);
|
|
3626
3799
|
refresh();
|
|
3627
3800
|
if (modalLive) {
|
|
@@ -3645,7 +3818,7 @@ function createWizard(deps) {
|
|
|
3645
3818
|
overlay.classList.add("lb-mix-match__modal-overlay--open");
|
|
3646
3819
|
lock();
|
|
3647
3820
|
setTimeout(() => {
|
|
3648
|
-
if (
|
|
3821
|
+
if (closeBtn) closeBtn.focus();
|
|
3649
3822
|
else modalDialog?.focus();
|
|
3650
3823
|
}, 50);
|
|
3651
3824
|
}
|
|
@@ -3713,7 +3886,7 @@ function createWizard(deps) {
|
|
|
3713
3886
|
}
|
|
3714
3887
|
|
|
3715
3888
|
// src/renderers/multi-step.ts
|
|
3716
|
-
function renderMultiStepBundle(container, bundle,
|
|
3889
|
+
function renderMultiStepBundle(container, bundle, onAddToCart, onCleanup) {
|
|
3717
3890
|
const wc = bundle.widgetConfig;
|
|
3718
3891
|
const t = DEFAULT_STRINGS;
|
|
3719
3892
|
const rulesMap = mergeRuleMaps(bundle.productRules, bundle.variantRules);
|
|
@@ -3746,13 +3919,13 @@ function renderMultiStepBundle(container, bundle, outOfStockBehavior, onAddToCar
|
|
|
3746
3919
|
compareAtPrice: v.compareAtPrice,
|
|
3747
3920
|
unitPrice: v.unitPrice,
|
|
3748
3921
|
image: v.image,
|
|
3749
|
-
inventoryQuantity: null
|
|
3922
|
+
inventoryQuantity: v.inventoryQuantity ?? null
|
|
3750
3923
|
}))
|
|
3751
3924
|
};
|
|
3752
3925
|
})
|
|
3753
3926
|
);
|
|
3754
|
-
const
|
|
3755
|
-
if (
|
|
3927
|
+
const everyStepPickable = pools.length > 0 && pools.every((pool) => pool.some((p) => p.available));
|
|
3928
|
+
if (!everyStepPickable) return;
|
|
3756
3929
|
const formatMoney = intlFormatMoney(
|
|
3757
3930
|
bundle.products[0]?.priceRange.minVariantPrice.currencyCode ?? "USD"
|
|
3758
3931
|
);
|
|
@@ -3798,8 +3971,8 @@ function renderMultiStepBundle(container, bundle, outOfStockBehavior, onAddToCar
|
|
|
3798
3971
|
const overlay = buildPickerModal({
|
|
3799
3972
|
bundleGid: bundle.id,
|
|
3800
3973
|
domId: bundle.id.replace(/\D/g, ""),
|
|
3801
|
-
showSearch:
|
|
3802
|
-
showTypeFilters:
|
|
3974
|
+
showSearch: wc.showSearch !== false,
|
|
3975
|
+
showTypeFilters: wc.mixMatchShowTypeFilters !== false,
|
|
3803
3976
|
segmentCount: null,
|
|
3804
3977
|
wizard: true
|
|
3805
3978
|
});
|
|
@@ -3846,8 +4019,8 @@ function renderMultiStepBundle(container, bundle, outOfStockBehavior, onAddToCar
|
|
|
3846
4019
|
selections,
|
|
3847
4020
|
t,
|
|
3848
4021
|
rulesMap,
|
|
3849
|
-
showQtySelector:
|
|
3850
|
-
showTypeFilters:
|
|
4022
|
+
showQtySelector: wc.mixMatchShowQuantitySelector !== false,
|
|
4023
|
+
showTypeFilters: wc.mixMatchShowTypeFilters !== false,
|
|
3851
4024
|
formatMoney,
|
|
3852
4025
|
dropdown: void 0,
|
|
3853
4026
|
portalTarget: null,
|
|
@@ -5564,7 +5737,7 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
5564
5737
|
transition: opacity 0.25s ease, background 0.25s ease, color 0.25s ease, border-color 0.25s ease;
|
|
5565
5738
|
}
|
|
5566
5739
|
|
|
5567
|
-
.lb-mix-match__modal-add:hover {
|
|
5740
|
+
.lb-mix-match__modal-add:hover:not(:disabled) {
|
|
5568
5741
|
opacity: 0.9;
|
|
5569
5742
|
}
|
|
5570
5743
|
|
|
@@ -5679,8 +5852,15 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
5679
5852
|
color: color-mix(in srgb, var(--lb-picker-text) 70%, transparent);
|
|
5680
5853
|
}
|
|
5681
5854
|
|
|
5682
|
-
/* "Done" reuses the picker Add-button styling so it matches the Add buttons.
|
|
5855
|
+
/* "Done" reuses the picker Add-button styling so it matches the Add buttons.
|
|
5856
|
+
The flex centring + min-height pin the footer-button silhouette: the wizard
|
|
5857
|
+
Back (bundle-multi-step.css) copies these metrics so the pair stays
|
|
5858
|
+
equal-height whatever border widths the merchant's picker tokens set. */
|
|
5683
5859
|
.lb-mix-match__modal-done {
|
|
5860
|
+
display: inline-flex;
|
|
5861
|
+
align-items: center;
|
|
5862
|
+
justify-content: center;
|
|
5863
|
+
min-height: 40px;
|
|
5684
5864
|
padding: 10px 24px;
|
|
5685
5865
|
transition: opacity 0.25s ease;
|
|
5686
5866
|
background: var(--lb-picker-add-bg);
|
|
@@ -5694,7 +5874,7 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
5694
5874
|
cursor: pointer;
|
|
5695
5875
|
}
|
|
5696
5876
|
|
|
5697
|
-
.lb-mix-match__modal-done:hover {
|
|
5877
|
+
.lb-mix-match__modal-done:hover:not(:disabled) {
|
|
5698
5878
|
opacity: 0.9;
|
|
5699
5879
|
}
|
|
5700
5880
|
|
|
@@ -5704,6 +5884,16 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
5704
5884
|
box-shadow: none;
|
|
5705
5885
|
}
|
|
5706
5886
|
|
|
5887
|
+
/* Disabled Next/Done (wizard step minimum not met) \u2014 same washed-out
|
|
5888
|
+
treatment as a disabled row Add, so an unfinished step reads at a
|
|
5889
|
+
glance. Hover is gated off above; the solid button returns the moment
|
|
5890
|
+
the requirement is met. */
|
|
5891
|
+
.lb-mix-match__modal-done:disabled {
|
|
5892
|
+
background: color-mix(in srgb, var(--lb-picker-add-bg) 35%, var(--lb-picker-bg));
|
|
5893
|
+
color: color-mix(in srgb, var(--lb-picker-add-label) 85%, transparent);
|
|
5894
|
+
cursor: not-allowed;
|
|
5895
|
+
}
|
|
5896
|
+
|
|
5707
5897
|
/* Hidden utility for search filtering */
|
|
5708
5898
|
.lb-hidden {
|
|
5709
5899
|
display: none !important;
|
|
@@ -5960,6 +6150,13 @@ var BUNDLE_VOLUME_CSS = `/* Lime Bundles \u2014 Volume / Quantity Breaks styles
|
|
|
5960
6150
|
white-space: nowrap;
|
|
5961
6151
|
font-variant-numeric: tabular-nums;
|
|
5962
6152
|
}
|
|
6153
|
+
|
|
6154
|
+
/* Tier the selected variant can't cover \u2014 same greyed treatment as an
|
|
6155
|
+
out-of-stock product row, and not selectable. */
|
|
6156
|
+
.lb-volume__tier--oos {
|
|
6157
|
+
opacity: 0.5;
|
|
6158
|
+
cursor: not-allowed;
|
|
6159
|
+
}
|
|
5963
6160
|
`;
|
|
5964
6161
|
var BUNDLE_BOGO_CSS = `/* Lime Bundles \u2014 BOGO (Buy X Get Y) bundle styles */
|
|
5965
6162
|
|
|
@@ -6128,14 +6325,23 @@ var BUNDLE_MULTI_STEP_CSS = `/**
|
|
|
6128
6325
|
justify-self: end;
|
|
6129
6326
|
}
|
|
6130
6327
|
|
|
6131
|
-
/* Back \u2014 secondary treatment beside the primary Next/Done
|
|
6132
|
-
.lb-mix-match__modal-done).
|
|
6328
|
+
/* Back \u2014 secondary (outlined) treatment beside the primary Next/Done
|
|
6329
|
+
(which reuse .lb-mix-match__modal-done). Same silhouette as Next \u2014
|
|
6330
|
+
metrics copied from the modal-done recipe (padding, type, radius,
|
|
6331
|
+
min-height) \u2014 and picker tokens only: the modal is portaled out of the
|
|
6332
|
+
widget, so the widget vars (--lb-text, --lb-border-*) it previously
|
|
6333
|
+
used don't reliably resolve here and break palette isolation. */
|
|
6133
6334
|
.lb-multi-step__modal-back {
|
|
6134
|
-
|
|
6335
|
+
display: inline-flex;
|
|
6336
|
+
align-items: center;
|
|
6337
|
+
justify-content: center;
|
|
6338
|
+
min-height: 40px;
|
|
6339
|
+
padding: 10px 24px;
|
|
6135
6340
|
background: none;
|
|
6136
|
-
border: var(--lb-border-width) solid var(--lb-border-color);
|
|
6137
|
-
border-radius: var(--lb-radius-sm);
|
|
6138
|
-
|
|
6341
|
+
border: var(--lb-picker-border-width) solid var(--lb-picker-border-color);
|
|
6342
|
+
border-radius: var(--lb-picker-radius-sm);
|
|
6343
|
+
box-sizing: border-box;
|
|
6344
|
+
color: var(--lb-picker-text);
|
|
6139
6345
|
font-family: inherit;
|
|
6140
6346
|
font-size: 14px;
|
|
6141
6347
|
font-weight: 600;
|
|
@@ -6144,7 +6350,7 @@ var BUNDLE_MULTI_STEP_CSS = `/**
|
|
|
6144
6350
|
}
|
|
6145
6351
|
|
|
6146
6352
|
.lb-multi-step__modal-back:hover {
|
|
6147
|
-
background: color-mix(in srgb, var(--lb-text) 5%, transparent);
|
|
6353
|
+
background: color-mix(in srgb, var(--lb-picker-text) 5%, transparent);
|
|
6148
6354
|
}
|
|
6149
6355
|
|
|
6150
6356
|
.lb-multi-step__modal-back:focus-visible {
|
|
@@ -6553,6 +6759,13 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
6553
6759
|
buyer = void 0;
|
|
6554
6760
|
shadow;
|
|
6555
6761
|
bundles = [];
|
|
6762
|
+
/**
|
|
6763
|
+
* The product handle this embed is standing on, when one can be resolved
|
|
6764
|
+
* (attribute → meta tag → /products/<handle> path). The mix-and-match
|
|
6765
|
+
* courtesy seed uses it to pre-add the current product the way the theme
|
|
6766
|
+
* widget does; null on pages with no product context.
|
|
6767
|
+
*/
|
|
6768
|
+
currentProductHandle = null;
|
|
6556
6769
|
abortController = null;
|
|
6557
6770
|
impressionCleanups = [];
|
|
6558
6771
|
/**
|
|
@@ -6567,12 +6780,6 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
6567
6780
|
* `.lb-bundle-widget { ... }` reach the widget's DOM.
|
|
6568
6781
|
*/
|
|
6569
6782
|
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 = DEFAULT_OUT_OF_STOCK_BEHAVIOR;
|
|
6576
6783
|
constructor() {
|
|
6577
6784
|
super();
|
|
6578
6785
|
this.shadow = this.attachShadow({ mode: "open" });
|
|
@@ -6672,11 +6879,12 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
6672
6879
|
try {
|
|
6673
6880
|
let bundlePromise;
|
|
6674
6881
|
let singleBundleMode = false;
|
|
6882
|
+
this.currentProductHandle = resolveProductHandle(this.productHandleAttr);
|
|
6675
6883
|
if (this.bundleGid) {
|
|
6676
6884
|
singleBundleMode = true;
|
|
6677
6885
|
bundlePromise = this.fetchSingleBundle(client, controller.signal);
|
|
6678
6886
|
} else {
|
|
6679
|
-
const handle =
|
|
6887
|
+
const handle = this.currentProductHandle;
|
|
6680
6888
|
if (!handle) {
|
|
6681
6889
|
this.teardownImpressions();
|
|
6682
6890
|
this.renderError(
|
|
@@ -6707,9 +6915,6 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
6707
6915
|
const sanitized = sanitizeCustomCss(customCss);
|
|
6708
6916
|
if (sanitized.ok) this.shopCustomCss = sanitized.css;
|
|
6709
6917
|
}
|
|
6710
|
-
this.outOfStockBehavior = parseOutOfStockBehavior(
|
|
6711
|
-
shopSettings?.shop?.outOfStockBehavior?.value
|
|
6712
|
-
);
|
|
6713
6918
|
this.renderBundles();
|
|
6714
6919
|
} catch (err) {
|
|
6715
6920
|
if (controller.signal.aborted) return;
|
|
@@ -6789,6 +6994,17 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
6789
6994
|
const storage = window.localStorage;
|
|
6790
6995
|
const key = cartStorageKey(this.shopDomain);
|
|
6791
6996
|
const existingCartId = storage?.getItem(key) ?? null;
|
|
6997
|
+
const isStaleCartError = (errors) => errors.length > 0 && errors.every((e) => (e.field ?? []).includes("cartId"));
|
|
6998
|
+
const stockCapped = (warnings) => (warnings ?? []).some((w) => w.code === "MERCHANDISE_NOT_ENOUGH_STOCK");
|
|
6999
|
+
const fail = (message) => {
|
|
7000
|
+
this.dispatchEvent(
|
|
7001
|
+
new CustomEvent("lime-bundle:error", {
|
|
7002
|
+
detail: { message, code: "CART_ERROR" },
|
|
7003
|
+
bubbles: true,
|
|
7004
|
+
composed: true
|
|
7005
|
+
})
|
|
7006
|
+
);
|
|
7007
|
+
};
|
|
6792
7008
|
try {
|
|
6793
7009
|
let checkoutUrl = null;
|
|
6794
7010
|
if (existingCartId) {
|
|
@@ -6798,7 +7014,14 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
6798
7014
|
);
|
|
6799
7015
|
const payload = res.cartLinesAdd;
|
|
6800
7016
|
if (payload?.userErrors?.length) {
|
|
7017
|
+
if (!isStaleCartError(payload.userErrors)) {
|
|
7018
|
+
fail(payload.userErrors[0].message);
|
|
7019
|
+
return;
|
|
7020
|
+
}
|
|
6801
7021
|
storage?.removeItem(key);
|
|
7022
|
+
} else if (stockCapped(payload?.warnings)) {
|
|
7023
|
+
fail("Some items in this bundle are out of stock.");
|
|
7024
|
+
return;
|
|
6802
7025
|
} else if (payload?.cart) {
|
|
6803
7026
|
checkoutUrl = payload.cart.checkoutUrl;
|
|
6804
7027
|
}
|
|
@@ -6809,6 +7032,14 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
6809
7032
|
{ input: { lines } }
|
|
6810
7033
|
);
|
|
6811
7034
|
const payload = res.cartCreate;
|
|
7035
|
+
if (payload?.userErrors?.length) {
|
|
7036
|
+
fail(payload.userErrors[0].message);
|
|
7037
|
+
return;
|
|
7038
|
+
}
|
|
7039
|
+
if (stockCapped(payload?.warnings)) {
|
|
7040
|
+
fail("Some items in this bundle are out of stock.");
|
|
7041
|
+
return;
|
|
7042
|
+
}
|
|
6812
7043
|
if (payload?.cart) {
|
|
6813
7044
|
storage?.setItem(key, payload.cart.id);
|
|
6814
7045
|
checkoutUrl = payload.cart.checkoutUrl;
|
|
@@ -6817,13 +7048,7 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
6817
7048
|
if (checkoutUrl) {
|
|
6818
7049
|
window.location.assign(checkoutUrl);
|
|
6819
7050
|
} 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
|
-
);
|
|
7051
|
+
fail("Cart creation failed");
|
|
6827
7052
|
}
|
|
6828
7053
|
} catch (err) {
|
|
6829
7054
|
this.dispatchEvent(
|
|
@@ -6890,49 +7115,32 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
6890
7115
|
container.setAttribute("aria-label", bundle.title);
|
|
6891
7116
|
container.setAttribute("data-bundle-type", bundle.bundleType);
|
|
6892
7117
|
container.setAttribute("data-bundle-gid", bundle.id);
|
|
7118
|
+
if (bundle.endsAt) container.setAttribute("data-ends-at", bundle.endsAt);
|
|
6893
7119
|
applyWidgetConfigVars(container, bundle.widgetConfig);
|
|
6894
7120
|
this.renderCleanups.push(trackInputMode(container));
|
|
6895
7121
|
const dispatch = (lines) => this.handleAddToCart(bundle, lines);
|
|
6896
7122
|
const registerCleanup = (fn) => this.renderCleanups.push(fn);
|
|
6897
7123
|
switch (bundle.bundleType) {
|
|
6898
7124
|
case "fixed":
|
|
6899
|
-
renderFixedBundle(
|
|
6900
|
-
container,
|
|
6901
|
-
bundle,
|
|
6902
|
-
this.outOfStockBehavior,
|
|
6903
|
-
dispatch,
|
|
6904
|
-
registerCleanup
|
|
6905
|
-
);
|
|
7125
|
+
renderFixedBundle(container, bundle, dispatch, registerCleanup);
|
|
6906
7126
|
break;
|
|
6907
7127
|
case "mix_match":
|
|
6908
7128
|
renderMixMatchBundle(
|
|
6909
7129
|
container,
|
|
6910
7130
|
bundle,
|
|
6911
|
-
this.outOfStockBehavior,
|
|
6912
7131
|
dispatch,
|
|
6913
|
-
registerCleanup
|
|
7132
|
+
registerCleanup,
|
|
7133
|
+
this.currentProductHandle
|
|
6914
7134
|
);
|
|
6915
7135
|
break;
|
|
6916
7136
|
case "volume":
|
|
6917
|
-
renderVolumeBundle(container, bundle,
|
|
7137
|
+
renderVolumeBundle(container, bundle, dispatch);
|
|
6918
7138
|
break;
|
|
6919
7139
|
case "bogo":
|
|
6920
|
-
renderBogoBundle(
|
|
6921
|
-
container,
|
|
6922
|
-
bundle,
|
|
6923
|
-
this.outOfStockBehavior,
|
|
6924
|
-
dispatch,
|
|
6925
|
-
registerCleanup
|
|
6926
|
-
);
|
|
7140
|
+
renderBogoBundle(container, bundle, dispatch, registerCleanup);
|
|
6927
7141
|
break;
|
|
6928
7142
|
case "multi_step":
|
|
6929
|
-
renderMultiStepBundle(
|
|
6930
|
-
container,
|
|
6931
|
-
bundle,
|
|
6932
|
-
this.outOfStockBehavior,
|
|
6933
|
-
dispatch,
|
|
6934
|
-
registerCleanup
|
|
6935
|
-
);
|
|
7143
|
+
renderMultiStepBundle(container, bundle, dispatch, registerCleanup);
|
|
6936
7144
|
break;
|
|
6937
7145
|
}
|
|
6938
7146
|
this.shadow.appendChild(container);
|