@lime-bundles/widget 4.1.1 → 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 +181 -73
- package/dist/index.d.cts +0 -6
- package/dist/index.d.ts +0 -6
- package/dist/index.js +186 -77
- package/dist/lime-bundle.global.js +65 -33
- package/dist/lime-bundle.global.js.map +1 -1
- 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++) {
|
|
@@ -1174,12 +1184,11 @@ function bindDropdowns(root) {
|
|
|
1174
1184
|
}
|
|
1175
1185
|
|
|
1176
1186
|
// src/renderers/fixed.ts
|
|
1177
|
-
function renderFixedBundle(container, bundle,
|
|
1187
|
+
function renderFixedBundle(container, bundle, onAddToCart, onCleanup) {
|
|
1178
1188
|
const wc = bundle.widgetConfig;
|
|
1179
1189
|
const products = adaptProducts(bundle);
|
|
1180
1190
|
if (!products.length) return;
|
|
1181
1191
|
const oosProducts = products.filter((p) => !p.variants.some((v) => v.available));
|
|
1182
|
-
if (outOfStockBehavior === "hide" && oosProducts.length > 0) return;
|
|
1183
1192
|
const currency = bundle.products[0]?.priceRange.minVariantPrice.currencyCode ?? "USD";
|
|
1184
1193
|
const formatMoney = intlFormatMoney(currency);
|
|
1185
1194
|
const t = DEFAULT_STRINGS;
|
|
@@ -1204,7 +1213,9 @@ function renderFixedBundle(container, bundle, outOfStockBehavior, onAddToCart, o
|
|
|
1204
1213
|
});
|
|
1205
1214
|
shell.products.appendChild(row);
|
|
1206
1215
|
if (isOos) continue;
|
|
1207
|
-
const
|
|
1216
|
+
const sellable = resolveSellableVariant(product);
|
|
1217
|
+
if (sellable) product.selectedVariantId = sellable.id;
|
|
1218
|
+
const selected = sellable ?? findVariant(product, product.selectedVariantId);
|
|
1208
1219
|
hydrateRowControls(row, product, selected);
|
|
1209
1220
|
applyVariant(row, selected, product, formatMoney);
|
|
1210
1221
|
bindVariantSelects(
|
|
@@ -1799,6 +1810,17 @@ function unlock() {
|
|
|
1799
1810
|
window.scrollTo(0, savedY);
|
|
1800
1811
|
}
|
|
1801
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
|
+
|
|
1802
1824
|
// ../render/src/mix-match/modal.ts
|
|
1803
1825
|
var ALL_TYPES = "__all__";
|
|
1804
1826
|
var CLOSE_TRANSITION_MS = 300;
|
|
@@ -2023,12 +2045,22 @@ function createPickerModal(deps) {
|
|
|
2023
2045
|
syncActiveFilterPill();
|
|
2024
2046
|
if (searchInput) searchInput.value = "";
|
|
2025
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
|
+
}
|
|
2026
2058
|
overlay.style.display = "";
|
|
2027
2059
|
void overlay.offsetHeight;
|
|
2028
2060
|
overlay.classList.add("lb-mix-match__modal-overlay--open");
|
|
2029
2061
|
lock();
|
|
2030
2062
|
setTimeout(() => {
|
|
2031
|
-
if (
|
|
2063
|
+
if (closeBtn) closeBtn.focus();
|
|
2032
2064
|
else modalDialog?.focus();
|
|
2033
2065
|
}, 50);
|
|
2034
2066
|
}
|
|
@@ -2585,7 +2617,7 @@ function buildPickerModal(opts) {
|
|
|
2585
2617
|
}
|
|
2586
2618
|
|
|
2587
2619
|
// src/renderers/mix-match.ts
|
|
2588
|
-
function renderMixMatchBundle(container, bundle,
|
|
2620
|
+
function renderMixMatchBundle(container, bundle, onAddToCart, onCleanup, currentProductHandle) {
|
|
2589
2621
|
const wc = bundle.widgetConfig;
|
|
2590
2622
|
const t = DEFAULT_STRINGS;
|
|
2591
2623
|
const requiredQty = bundle.minQuantity || 1;
|
|
@@ -2608,12 +2640,12 @@ function renderMixMatchBundle(container, bundle, outOfStockBehavior, onAddToCart
|
|
|
2608
2640
|
compareAtPrice: v.compareAtPrice,
|
|
2609
2641
|
unitPrice: v.unitPrice,
|
|
2610
2642
|
image: v.image,
|
|
2611
|
-
inventoryQuantity: null
|
|
2643
|
+
inventoryQuantity: v.inventoryQuantity ?? null
|
|
2612
2644
|
}))
|
|
2613
2645
|
}));
|
|
2614
2646
|
if (!eligibleProducts.length) return;
|
|
2615
2647
|
const poolUnits = eligibleProducts.filter((p) => p.available).length;
|
|
2616
|
-
if (
|
|
2648
|
+
if (poolUnits === 0) return;
|
|
2617
2649
|
const formatMoney = intlFormatMoney(
|
|
2618
2650
|
bundle.products[0]?.priceRange.minVariantPrice.currencyCode ?? "USD"
|
|
2619
2651
|
);
|
|
@@ -2776,10 +2808,50 @@ function renderMixMatchBundle(container, bundle, outOfStockBehavior, onAddToCart
|
|
|
2776
2808
|
// src/renderers/volume.ts
|
|
2777
2809
|
import {
|
|
2778
2810
|
bestValueTierIndex,
|
|
2811
|
+
isVariantFulfillable as isVariantFulfillable2,
|
|
2779
2812
|
resolveDefaultTierIndex,
|
|
2780
|
-
resolvePopularTierIndex
|
|
2813
|
+
resolvePopularTierIndex,
|
|
2814
|
+
stockCapForVariant as stockCapForVariant2
|
|
2781
2815
|
} from "@lime-bundles/core";
|
|
2782
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
|
+
|
|
2783
2855
|
// ../render/src/volume/pricing.ts
|
|
2784
2856
|
function calcTierPrice(basePrice, discountType, tierEl) {
|
|
2785
2857
|
if (discountType === "fixed_amount") {
|
|
@@ -2869,15 +2941,16 @@ function selectTier(container, allTiers, basePrice, discountType, index, transla
|
|
|
2869
2941
|
}
|
|
2870
2942
|
|
|
2871
2943
|
// src/renderers/volume.ts
|
|
2872
|
-
function renderVolumeBundle(container, bundle,
|
|
2944
|
+
function renderVolumeBundle(container, bundle, onAddToCart) {
|
|
2873
2945
|
const wc = bundle.widgetConfig;
|
|
2874
2946
|
const product = bundle.products[0];
|
|
2875
2947
|
if (!product) return;
|
|
2876
2948
|
const variants = product.variants.nodes;
|
|
2877
|
-
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];
|
|
2878
2953
|
if (!pricingVariant) return;
|
|
2879
|
-
const anyAvailable = variants.some((v) => v.availableForSale);
|
|
2880
|
-
if (outOfStockBehavior === "hide" && !anyAvailable) return;
|
|
2881
2954
|
const basePrice = toCents(pricingVariant.price.amount);
|
|
2882
2955
|
const discountType = bundle.discountConfig.discountType;
|
|
2883
2956
|
const formatMoney = intlFormatMoney(product.priceRange.minVariantPrice.currencyCode);
|
|
@@ -2928,28 +3001,31 @@ function renderVolumeBundle(container, bundle, outOfStockBehavior, onAddToCart)
|
|
|
2928
3001
|
shell.products.replaceWith(group);
|
|
2929
3002
|
const tierEls = group.querySelectorAll("[data-tier-index]");
|
|
2930
3003
|
updateAllTierPrices(tierEls, basePrice, discountType, formatMoney);
|
|
2931
|
-
|
|
3004
|
+
applyTierStockState(tierEls, stockCapForVariant2(pricingVariant));
|
|
3005
|
+
let selectedIndex = resolveAvailableTierIndex(tierEls, defaultIdx);
|
|
3006
|
+
const noTierAvailable = selectedIndex === -1;
|
|
3007
|
+
if (noTierAvailable) selectedIndex = defaultIdx;
|
|
2932
3008
|
const apply = () => selectTier(shell.root, tierEls, basePrice, discountType, selectedIndex, t, formatMoney);
|
|
2933
3009
|
group.addEventListener("click", (e) => {
|
|
2934
3010
|
const el2 = e.target.closest("[data-tier-index]");
|
|
2935
|
-
if (!el2) return;
|
|
3011
|
+
if (!el2 || tierUnavailable(el2)) return;
|
|
2936
3012
|
selectedIndex = parseInt(el2.getAttribute("data-tier-index") ?? "", 10) || 0;
|
|
2937
3013
|
apply();
|
|
2938
3014
|
});
|
|
2939
3015
|
group.addEventListener("keydown", (e) => {
|
|
2940
3016
|
if (e.key === "ArrowDown" || e.key === "ArrowRight") {
|
|
2941
3017
|
e.preventDefault();
|
|
2942
|
-
selectedIndex =
|
|
3018
|
+
selectedIndex = stepToAvailableTier(tierEls, selectedIndex, 1);
|
|
2943
3019
|
apply();
|
|
2944
3020
|
tierEls[selectedIndex].focus();
|
|
2945
3021
|
} else if (e.key === "ArrowUp" || e.key === "ArrowLeft") {
|
|
2946
3022
|
e.preventDefault();
|
|
2947
|
-
selectedIndex =
|
|
3023
|
+
selectedIndex = stepToAvailableTier(tierEls, selectedIndex, -1);
|
|
2948
3024
|
apply();
|
|
2949
3025
|
tierEls[selectedIndex].focus();
|
|
2950
3026
|
}
|
|
2951
3027
|
});
|
|
2952
|
-
if (
|
|
3028
|
+
if (noTierAvailable) {
|
|
2953
3029
|
shell.cta.disabled = true;
|
|
2954
3030
|
const label = shell.cta.querySelector("[data-cta-label]");
|
|
2955
3031
|
if (label) label.textContent = t.outOfStock || "Out of stock";
|
|
@@ -3084,7 +3160,7 @@ function buildBogoPlus() {
|
|
|
3084
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>';
|
|
3085
3161
|
return plus;
|
|
3086
3162
|
}
|
|
3087
|
-
function renderBogoBundle(container, bundle,
|
|
3163
|
+
function renderBogoBundle(container, bundle, onAddToCart, onCleanup) {
|
|
3088
3164
|
const wc = bundle.widgetConfig;
|
|
3089
3165
|
const buyProduct = bundle.products.find((p) => p.id === bundle.buyProductId);
|
|
3090
3166
|
const getProduct = bundle.products.find((p) => p.id === bundle.getProductId);
|
|
@@ -3113,7 +3189,6 @@ function renderBogoBundle(container, bundle, outOfStockBehavior, onAddToCart, on
|
|
|
3113
3189
|
}
|
|
3114
3190
|
];
|
|
3115
3191
|
const oosCount = sides.filter((s) => !s.variants.some((v) => v.available)).length;
|
|
3116
|
-
if (outOfStockBehavior === "hide" && oosCount > 0) return;
|
|
3117
3192
|
const formatMoney = intlFormatMoney(
|
|
3118
3193
|
buyProduct.priceRange.minVariantPrice.currencyCode
|
|
3119
3194
|
);
|
|
@@ -3549,6 +3624,14 @@ function createWizard(deps) {
|
|
|
3549
3624
|
rows.push(row);
|
|
3550
3625
|
modalList.appendChild(row.el);
|
|
3551
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
|
+
);
|
|
3552
3635
|
deps.dropdown?.bindAll(modalList);
|
|
3553
3636
|
}
|
|
3554
3637
|
function onListClick(e) {
|
|
@@ -3711,6 +3794,7 @@ function createWizard(deps) {
|
|
|
3711
3794
|
syncActiveFilterPill();
|
|
3712
3795
|
if (searchInput) searchInput.value = "";
|
|
3713
3796
|
filterProducts("");
|
|
3797
|
+
if (modalList) modalList.scrollTop = 0;
|
|
3714
3798
|
buildStepSegments(stepIndex);
|
|
3715
3799
|
refresh();
|
|
3716
3800
|
if (modalLive) {
|
|
@@ -3734,7 +3818,7 @@ function createWizard(deps) {
|
|
|
3734
3818
|
overlay.classList.add("lb-mix-match__modal-overlay--open");
|
|
3735
3819
|
lock();
|
|
3736
3820
|
setTimeout(() => {
|
|
3737
|
-
if (
|
|
3821
|
+
if (closeBtn) closeBtn.focus();
|
|
3738
3822
|
else modalDialog?.focus();
|
|
3739
3823
|
}, 50);
|
|
3740
3824
|
}
|
|
@@ -3802,7 +3886,7 @@ function createWizard(deps) {
|
|
|
3802
3886
|
}
|
|
3803
3887
|
|
|
3804
3888
|
// src/renderers/multi-step.ts
|
|
3805
|
-
function renderMultiStepBundle(container, bundle,
|
|
3889
|
+
function renderMultiStepBundle(container, bundle, onAddToCart, onCleanup) {
|
|
3806
3890
|
const wc = bundle.widgetConfig;
|
|
3807
3891
|
const t = DEFAULT_STRINGS;
|
|
3808
3892
|
const rulesMap = mergeRuleMaps(bundle.productRules, bundle.variantRules);
|
|
@@ -3835,13 +3919,13 @@ function renderMultiStepBundle(container, bundle, outOfStockBehavior, onAddToCar
|
|
|
3835
3919
|
compareAtPrice: v.compareAtPrice,
|
|
3836
3920
|
unitPrice: v.unitPrice,
|
|
3837
3921
|
image: v.image,
|
|
3838
|
-
inventoryQuantity: null
|
|
3922
|
+
inventoryQuantity: v.inventoryQuantity ?? null
|
|
3839
3923
|
}))
|
|
3840
3924
|
};
|
|
3841
3925
|
})
|
|
3842
3926
|
);
|
|
3843
|
-
const
|
|
3844
|
-
if (
|
|
3927
|
+
const everyStepPickable = pools.length > 0 && pools.every((pool) => pool.some((p) => p.available));
|
|
3928
|
+
if (!everyStepPickable) return;
|
|
3845
3929
|
const formatMoney = intlFormatMoney(
|
|
3846
3930
|
bundle.products[0]?.priceRange.minVariantPrice.currencyCode ?? "USD"
|
|
3847
3931
|
);
|
|
@@ -5653,7 +5737,7 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
5653
5737
|
transition: opacity 0.25s ease, background 0.25s ease, color 0.25s ease, border-color 0.25s ease;
|
|
5654
5738
|
}
|
|
5655
5739
|
|
|
5656
|
-
.lb-mix-match__modal-add:hover {
|
|
5740
|
+
.lb-mix-match__modal-add:hover:not(:disabled) {
|
|
5657
5741
|
opacity: 0.9;
|
|
5658
5742
|
}
|
|
5659
5743
|
|
|
@@ -5768,8 +5852,15 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
5768
5852
|
color: color-mix(in srgb, var(--lb-picker-text) 70%, transparent);
|
|
5769
5853
|
}
|
|
5770
5854
|
|
|
5771
|
-
/* "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. */
|
|
5772
5859
|
.lb-mix-match__modal-done {
|
|
5860
|
+
display: inline-flex;
|
|
5861
|
+
align-items: center;
|
|
5862
|
+
justify-content: center;
|
|
5863
|
+
min-height: 40px;
|
|
5773
5864
|
padding: 10px 24px;
|
|
5774
5865
|
transition: opacity 0.25s ease;
|
|
5775
5866
|
background: var(--lb-picker-add-bg);
|
|
@@ -5783,7 +5874,7 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
5783
5874
|
cursor: pointer;
|
|
5784
5875
|
}
|
|
5785
5876
|
|
|
5786
|
-
.lb-mix-match__modal-done:hover {
|
|
5877
|
+
.lb-mix-match__modal-done:hover:not(:disabled) {
|
|
5787
5878
|
opacity: 0.9;
|
|
5788
5879
|
}
|
|
5789
5880
|
|
|
@@ -5793,6 +5884,16 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
5793
5884
|
box-shadow: none;
|
|
5794
5885
|
}
|
|
5795
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
|
+
|
|
5796
5897
|
/* Hidden utility for search filtering */
|
|
5797
5898
|
.lb-hidden {
|
|
5798
5899
|
display: none !important;
|
|
@@ -6049,6 +6150,13 @@ var BUNDLE_VOLUME_CSS = `/* Lime Bundles \u2014 Volume / Quantity Breaks styles
|
|
|
6049
6150
|
white-space: nowrap;
|
|
6050
6151
|
font-variant-numeric: tabular-nums;
|
|
6051
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
|
+
}
|
|
6052
6160
|
`;
|
|
6053
6161
|
var BUNDLE_BOGO_CSS = `/* Lime Bundles \u2014 BOGO (Buy X Get Y) bundle styles */
|
|
6054
6162
|
|
|
@@ -6217,14 +6325,23 @@ var BUNDLE_MULTI_STEP_CSS = `/**
|
|
|
6217
6325
|
justify-self: end;
|
|
6218
6326
|
}
|
|
6219
6327
|
|
|
6220
|
-
/* Back \u2014 secondary treatment beside the primary Next/Done
|
|
6221
|
-
.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. */
|
|
6222
6334
|
.lb-multi-step__modal-back {
|
|
6223
|
-
|
|
6335
|
+
display: inline-flex;
|
|
6336
|
+
align-items: center;
|
|
6337
|
+
justify-content: center;
|
|
6338
|
+
min-height: 40px;
|
|
6339
|
+
padding: 10px 24px;
|
|
6224
6340
|
background: none;
|
|
6225
|
-
border: var(--lb-border-width) solid var(--lb-border-color);
|
|
6226
|
-
border-radius: var(--lb-radius-sm);
|
|
6227
|
-
|
|
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);
|
|
6228
6345
|
font-family: inherit;
|
|
6229
6346
|
font-size: 14px;
|
|
6230
6347
|
font-weight: 600;
|
|
@@ -6233,7 +6350,7 @@ var BUNDLE_MULTI_STEP_CSS = `/**
|
|
|
6233
6350
|
}
|
|
6234
6351
|
|
|
6235
6352
|
.lb-multi-step__modal-back:hover {
|
|
6236
|
-
background: color-mix(in srgb, var(--lb-text) 5%, transparent);
|
|
6353
|
+
background: color-mix(in srgb, var(--lb-picker-text) 5%, transparent);
|
|
6237
6354
|
}
|
|
6238
6355
|
|
|
6239
6356
|
.lb-multi-step__modal-back:focus-visible {
|
|
@@ -6663,12 +6780,6 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
6663
6780
|
* `.lb-bundle-widget { ... }` reach the widget's DOM.
|
|
6664
6781
|
*/
|
|
6665
6782
|
shopCustomCss = null;
|
|
6666
|
-
/**
|
|
6667
|
-
* Shop-wide out-of-stock behaviour, resolved from the `$app` shop metafield
|
|
6668
|
-
* before the first render. Defaults to `show_greyed_out` so a failed or
|
|
6669
|
-
* missing fetch degrades to "bundles still render".
|
|
6670
|
-
*/
|
|
6671
|
-
outOfStockBehavior = DEFAULT_OUT_OF_STOCK_BEHAVIOR;
|
|
6672
6783
|
constructor() {
|
|
6673
6784
|
super();
|
|
6674
6785
|
this.shadow = this.attachShadow({ mode: "open" });
|
|
@@ -6804,9 +6915,6 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
6804
6915
|
const sanitized = sanitizeCustomCss(customCss);
|
|
6805
6916
|
if (sanitized.ok) this.shopCustomCss = sanitized.css;
|
|
6806
6917
|
}
|
|
6807
|
-
this.outOfStockBehavior = parseOutOfStockBehavior(
|
|
6808
|
-
shopSettings?.shop?.outOfStockBehavior?.value
|
|
6809
|
-
);
|
|
6810
6918
|
this.renderBundles();
|
|
6811
6919
|
} catch (err) {
|
|
6812
6920
|
if (controller.signal.aborted) return;
|
|
@@ -6886,6 +6994,17 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
6886
6994
|
const storage = window.localStorage;
|
|
6887
6995
|
const key = cartStorageKey(this.shopDomain);
|
|
6888
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
|
+
};
|
|
6889
7008
|
try {
|
|
6890
7009
|
let checkoutUrl = null;
|
|
6891
7010
|
if (existingCartId) {
|
|
@@ -6895,7 +7014,14 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
6895
7014
|
);
|
|
6896
7015
|
const payload = res.cartLinesAdd;
|
|
6897
7016
|
if (payload?.userErrors?.length) {
|
|
7017
|
+
if (!isStaleCartError(payload.userErrors)) {
|
|
7018
|
+
fail(payload.userErrors[0].message);
|
|
7019
|
+
return;
|
|
7020
|
+
}
|
|
6898
7021
|
storage?.removeItem(key);
|
|
7022
|
+
} else if (stockCapped(payload?.warnings)) {
|
|
7023
|
+
fail("Some items in this bundle are out of stock.");
|
|
7024
|
+
return;
|
|
6899
7025
|
} else if (payload?.cart) {
|
|
6900
7026
|
checkoutUrl = payload.cart.checkoutUrl;
|
|
6901
7027
|
}
|
|
@@ -6906,6 +7032,14 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
6906
7032
|
{ input: { lines } }
|
|
6907
7033
|
);
|
|
6908
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
|
+
}
|
|
6909
7043
|
if (payload?.cart) {
|
|
6910
7044
|
storage?.setItem(key, payload.cart.id);
|
|
6911
7045
|
checkoutUrl = payload.cart.checkoutUrl;
|
|
@@ -6914,13 +7048,7 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
6914
7048
|
if (checkoutUrl) {
|
|
6915
7049
|
window.location.assign(checkoutUrl);
|
|
6916
7050
|
} else {
|
|
6917
|
-
|
|
6918
|
-
new CustomEvent("lime-bundle:error", {
|
|
6919
|
-
detail: { message: "Cart creation failed", code: "CART_ERROR" },
|
|
6920
|
-
bubbles: true,
|
|
6921
|
-
composed: true
|
|
6922
|
-
})
|
|
6923
|
-
);
|
|
7051
|
+
fail("Cart creation failed");
|
|
6924
7052
|
}
|
|
6925
7053
|
} catch (err) {
|
|
6926
7054
|
this.dispatchEvent(
|
|
@@ -6994,44 +7122,25 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
6994
7122
|
const registerCleanup = (fn) => this.renderCleanups.push(fn);
|
|
6995
7123
|
switch (bundle.bundleType) {
|
|
6996
7124
|
case "fixed":
|
|
6997
|
-
renderFixedBundle(
|
|
6998
|
-
container,
|
|
6999
|
-
bundle,
|
|
7000
|
-
this.outOfStockBehavior,
|
|
7001
|
-
dispatch,
|
|
7002
|
-
registerCleanup
|
|
7003
|
-
);
|
|
7125
|
+
renderFixedBundle(container, bundle, dispatch, registerCleanup);
|
|
7004
7126
|
break;
|
|
7005
7127
|
case "mix_match":
|
|
7006
7128
|
renderMixMatchBundle(
|
|
7007
7129
|
container,
|
|
7008
7130
|
bundle,
|
|
7009
|
-
this.outOfStockBehavior,
|
|
7010
7131
|
dispatch,
|
|
7011
7132
|
registerCleanup,
|
|
7012
7133
|
this.currentProductHandle
|
|
7013
7134
|
);
|
|
7014
7135
|
break;
|
|
7015
7136
|
case "volume":
|
|
7016
|
-
renderVolumeBundle(container, bundle,
|
|
7137
|
+
renderVolumeBundle(container, bundle, dispatch);
|
|
7017
7138
|
break;
|
|
7018
7139
|
case "bogo":
|
|
7019
|
-
renderBogoBundle(
|
|
7020
|
-
container,
|
|
7021
|
-
bundle,
|
|
7022
|
-
this.outOfStockBehavior,
|
|
7023
|
-
dispatch,
|
|
7024
|
-
registerCleanup
|
|
7025
|
-
);
|
|
7140
|
+
renderBogoBundle(container, bundle, dispatch, registerCleanup);
|
|
7026
7141
|
break;
|
|
7027
7142
|
case "multi_step":
|
|
7028
|
-
renderMultiStepBundle(
|
|
7029
|
-
container,
|
|
7030
|
-
bundle,
|
|
7031
|
-
this.outOfStockBehavior,
|
|
7032
|
-
dispatch,
|
|
7033
|
-
registerCleanup
|
|
7034
|
-
);
|
|
7143
|
+
renderMultiStepBundle(container, bundle, dispatch, registerCleanup);
|
|
7035
7144
|
break;
|
|
7036
7145
|
}
|
|
7037
7146
|
this.shadow.appendChild(container);
|