@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/README.md
CHANGED
|
@@ -129,7 +129,7 @@ The widget renders its own inline fallback; listen for this event if you want to
|
|
|
129
129
|
- **Countdown timer.** When a bundle has `endsAt`, the widget ticks live and hides on expiry.
|
|
130
130
|
- **Variant dropdowns.** Products with multiple eligible variants render a `<select>`; switching live-updates the row price and bundle total.
|
|
131
131
|
- **Mix-match picker modal.** Full modal with search, quantity stepper, progress bar, focus trap, and keyboard navigation.
|
|
132
|
-
- **Out-of-stock handling.**
|
|
132
|
+
- **Out-of-stock handling.** Sold-out products render greyed out with the CTA locked; a bundle that can't be completed doesn't render.
|
|
133
133
|
- **A/B testing.** Reads the merchant's A/B config, buckets the visitor via a first-party cookie, applies Variant B overrides.
|
|
134
134
|
- **Analytics.** Impression and add-to-cart events fire regardless of whether you override the cart.
|
|
135
135
|
|
package/dist/index.cjs
CHANGED
|
@@ -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++) {
|
|
@@ -1177,12 +1188,11 @@ function bindDropdowns(root) {
|
|
|
1177
1188
|
}
|
|
1178
1189
|
|
|
1179
1190
|
// src/renderers/fixed.ts
|
|
1180
|
-
function renderFixedBundle(container, bundle,
|
|
1191
|
+
function renderFixedBundle(container, bundle, onAddToCart, onCleanup) {
|
|
1181
1192
|
const wc = bundle.widgetConfig;
|
|
1182
1193
|
const products = adaptProducts(bundle);
|
|
1183
1194
|
if (!products.length) return;
|
|
1184
1195
|
const oosProducts = products.filter((p) => !p.variants.some((v) => v.available));
|
|
1185
|
-
if (outOfStockBehavior === "hide" && oosProducts.length > 0) return;
|
|
1186
1196
|
const currency = bundle.products[0]?.priceRange.minVariantPrice.currencyCode ?? "USD";
|
|
1187
1197
|
const formatMoney = intlFormatMoney(currency);
|
|
1188
1198
|
const t = DEFAULT_STRINGS;
|
|
@@ -1207,7 +1217,9 @@ function renderFixedBundle(container, bundle, outOfStockBehavior, onAddToCart, o
|
|
|
1207
1217
|
});
|
|
1208
1218
|
shell.products.appendChild(row);
|
|
1209
1219
|
if (isOos) continue;
|
|
1210
|
-
const
|
|
1220
|
+
const sellable = resolveSellableVariant(product);
|
|
1221
|
+
if (sellable) product.selectedVariantId = sellable.id;
|
|
1222
|
+
const selected = sellable ?? findVariant(product, product.selectedVariantId);
|
|
1211
1223
|
hydrateRowControls(row, product, selected);
|
|
1212
1224
|
applyVariant(row, selected, product, formatMoney);
|
|
1213
1225
|
bindVariantSelects(
|
|
@@ -1802,6 +1814,17 @@ function unlock() {
|
|
|
1802
1814
|
window.scrollTo(0, savedY);
|
|
1803
1815
|
}
|
|
1804
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
|
+
|
|
1805
1828
|
// ../render/src/mix-match/modal.ts
|
|
1806
1829
|
var ALL_TYPES = "__all__";
|
|
1807
1830
|
var CLOSE_TRANSITION_MS = 300;
|
|
@@ -2026,12 +2049,22 @@ function createPickerModal(deps) {
|
|
|
2026
2049
|
syncActiveFilterPill();
|
|
2027
2050
|
if (searchInput) searchInput.value = "";
|
|
2028
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
|
+
}
|
|
2029
2062
|
overlay.style.display = "";
|
|
2030
2063
|
void overlay.offsetHeight;
|
|
2031
2064
|
overlay.classList.add("lb-mix-match__modal-overlay--open");
|
|
2032
2065
|
lock();
|
|
2033
2066
|
setTimeout(() => {
|
|
2034
|
-
if (
|
|
2067
|
+
if (closeBtn) closeBtn.focus();
|
|
2035
2068
|
else modalDialog?.focus();
|
|
2036
2069
|
}, 50);
|
|
2037
2070
|
}
|
|
@@ -2588,7 +2621,7 @@ function buildPickerModal(opts) {
|
|
|
2588
2621
|
}
|
|
2589
2622
|
|
|
2590
2623
|
// src/renderers/mix-match.ts
|
|
2591
|
-
function renderMixMatchBundle(container, bundle,
|
|
2624
|
+
function renderMixMatchBundle(container, bundle, onAddToCart, onCleanup, currentProductHandle) {
|
|
2592
2625
|
const wc = bundle.widgetConfig;
|
|
2593
2626
|
const t = DEFAULT_STRINGS;
|
|
2594
2627
|
const requiredQty = bundle.minQuantity || 1;
|
|
@@ -2611,12 +2644,12 @@ function renderMixMatchBundle(container, bundle, outOfStockBehavior, onAddToCart
|
|
|
2611
2644
|
compareAtPrice: v.compareAtPrice,
|
|
2612
2645
|
unitPrice: v.unitPrice,
|
|
2613
2646
|
image: v.image,
|
|
2614
|
-
inventoryQuantity: null
|
|
2647
|
+
inventoryQuantity: v.inventoryQuantity ?? null
|
|
2615
2648
|
}))
|
|
2616
2649
|
}));
|
|
2617
2650
|
if (!eligibleProducts.length) return;
|
|
2618
2651
|
const poolUnits = eligibleProducts.filter((p) => p.available).length;
|
|
2619
|
-
if (
|
|
2652
|
+
if (poolUnits === 0) return;
|
|
2620
2653
|
const formatMoney = intlFormatMoney(
|
|
2621
2654
|
bundle.products[0]?.priceRange.minVariantPrice.currencyCode ?? "USD"
|
|
2622
2655
|
);
|
|
@@ -2779,6 +2812,44 @@ function renderMixMatchBundle(container, bundle, outOfStockBehavior, onAddToCart
|
|
|
2779
2812
|
// src/renderers/volume.ts
|
|
2780
2813
|
var import_core4 = require("@lime-bundles/core");
|
|
2781
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
|
+
|
|
2782
2853
|
// ../render/src/volume/pricing.ts
|
|
2783
2854
|
function calcTierPrice(basePrice, discountType, tierEl) {
|
|
2784
2855
|
if (discountType === "fixed_amount") {
|
|
@@ -2868,15 +2939,16 @@ function selectTier(container, allTiers, basePrice, discountType, index, transla
|
|
|
2868
2939
|
}
|
|
2869
2940
|
|
|
2870
2941
|
// src/renderers/volume.ts
|
|
2871
|
-
function renderVolumeBundle(container, bundle,
|
|
2942
|
+
function renderVolumeBundle(container, bundle, onAddToCart) {
|
|
2872
2943
|
const wc = bundle.widgetConfig;
|
|
2873
2944
|
const product = bundle.products[0];
|
|
2874
2945
|
if (!product) return;
|
|
2875
2946
|
const variants = product.variants.nodes;
|
|
2876
|
-
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];
|
|
2877
2951
|
if (!pricingVariant) return;
|
|
2878
|
-
const anyAvailable = variants.some((v) => v.availableForSale);
|
|
2879
|
-
if (outOfStockBehavior === "hide" && !anyAvailable) return;
|
|
2880
2952
|
const basePrice = toCents(pricingVariant.price.amount);
|
|
2881
2953
|
const discountType = bundle.discountConfig.discountType;
|
|
2882
2954
|
const formatMoney = intlFormatMoney(product.priceRange.minVariantPrice.currencyCode);
|
|
@@ -2927,28 +2999,31 @@ function renderVolumeBundle(container, bundle, outOfStockBehavior, onAddToCart)
|
|
|
2927
2999
|
shell.products.replaceWith(group);
|
|
2928
3000
|
const tierEls = group.querySelectorAll("[data-tier-index]");
|
|
2929
3001
|
updateAllTierPrices(tierEls, basePrice, discountType, formatMoney);
|
|
2930
|
-
|
|
3002
|
+
applyTierStockState(tierEls, (0, import_core4.stockCapForVariant)(pricingVariant));
|
|
3003
|
+
let selectedIndex = resolveAvailableTierIndex(tierEls, defaultIdx);
|
|
3004
|
+
const noTierAvailable = selectedIndex === -1;
|
|
3005
|
+
if (noTierAvailable) selectedIndex = defaultIdx;
|
|
2931
3006
|
const apply = () => selectTier(shell.root, tierEls, basePrice, discountType, selectedIndex, t, formatMoney);
|
|
2932
3007
|
group.addEventListener("click", (e) => {
|
|
2933
3008
|
const el2 = e.target.closest("[data-tier-index]");
|
|
2934
|
-
if (!el2) return;
|
|
3009
|
+
if (!el2 || tierUnavailable(el2)) return;
|
|
2935
3010
|
selectedIndex = parseInt(el2.getAttribute("data-tier-index") ?? "", 10) || 0;
|
|
2936
3011
|
apply();
|
|
2937
3012
|
});
|
|
2938
3013
|
group.addEventListener("keydown", (e) => {
|
|
2939
3014
|
if (e.key === "ArrowDown" || e.key === "ArrowRight") {
|
|
2940
3015
|
e.preventDefault();
|
|
2941
|
-
selectedIndex =
|
|
3016
|
+
selectedIndex = stepToAvailableTier(tierEls, selectedIndex, 1);
|
|
2942
3017
|
apply();
|
|
2943
3018
|
tierEls[selectedIndex].focus();
|
|
2944
3019
|
} else if (e.key === "ArrowUp" || e.key === "ArrowLeft") {
|
|
2945
3020
|
e.preventDefault();
|
|
2946
|
-
selectedIndex =
|
|
3021
|
+
selectedIndex = stepToAvailableTier(tierEls, selectedIndex, -1);
|
|
2947
3022
|
apply();
|
|
2948
3023
|
tierEls[selectedIndex].focus();
|
|
2949
3024
|
}
|
|
2950
3025
|
});
|
|
2951
|
-
if (
|
|
3026
|
+
if (noTierAvailable) {
|
|
2952
3027
|
shell.cta.disabled = true;
|
|
2953
3028
|
const label = shell.cta.querySelector("[data-cta-label]");
|
|
2954
3029
|
if (label) label.textContent = t.outOfStock || "Out of stock";
|
|
@@ -3083,7 +3158,7 @@ function buildBogoPlus() {
|
|
|
3083
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>';
|
|
3084
3159
|
return plus;
|
|
3085
3160
|
}
|
|
3086
|
-
function renderBogoBundle(container, bundle,
|
|
3161
|
+
function renderBogoBundle(container, bundle, onAddToCart, onCleanup) {
|
|
3087
3162
|
const wc = bundle.widgetConfig;
|
|
3088
3163
|
const buyProduct = bundle.products.find((p) => p.id === bundle.buyProductId);
|
|
3089
3164
|
const getProduct = bundle.products.find((p) => p.id === bundle.getProductId);
|
|
@@ -3112,7 +3187,6 @@ function renderBogoBundle(container, bundle, outOfStockBehavior, onAddToCart, on
|
|
|
3112
3187
|
}
|
|
3113
3188
|
];
|
|
3114
3189
|
const oosCount = sides.filter((s) => !s.variants.some((v) => v.available)).length;
|
|
3115
|
-
if (outOfStockBehavior === "hide" && oosCount > 0) return;
|
|
3116
3190
|
const formatMoney = intlFormatMoney(
|
|
3117
3191
|
buyProduct.priceRange.minVariantPrice.currencyCode
|
|
3118
3192
|
);
|
|
@@ -3545,6 +3619,14 @@ function createWizard(deps) {
|
|
|
3545
3619
|
rows.push(row);
|
|
3546
3620
|
modalList.appendChild(row.el);
|
|
3547
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
|
+
);
|
|
3548
3630
|
deps.dropdown?.bindAll(modalList);
|
|
3549
3631
|
}
|
|
3550
3632
|
function onListClick(e) {
|
|
@@ -3707,6 +3789,7 @@ function createWizard(deps) {
|
|
|
3707
3789
|
syncActiveFilterPill();
|
|
3708
3790
|
if (searchInput) searchInput.value = "";
|
|
3709
3791
|
filterProducts("");
|
|
3792
|
+
if (modalList) modalList.scrollTop = 0;
|
|
3710
3793
|
buildStepSegments(stepIndex);
|
|
3711
3794
|
refresh();
|
|
3712
3795
|
if (modalLive) {
|
|
@@ -3730,7 +3813,7 @@ function createWizard(deps) {
|
|
|
3730
3813
|
overlay.classList.add("lb-mix-match__modal-overlay--open");
|
|
3731
3814
|
lock();
|
|
3732
3815
|
setTimeout(() => {
|
|
3733
|
-
if (
|
|
3816
|
+
if (closeBtn) closeBtn.focus();
|
|
3734
3817
|
else modalDialog?.focus();
|
|
3735
3818
|
}, 50);
|
|
3736
3819
|
}
|
|
@@ -3798,7 +3881,7 @@ function createWizard(deps) {
|
|
|
3798
3881
|
}
|
|
3799
3882
|
|
|
3800
3883
|
// src/renderers/multi-step.ts
|
|
3801
|
-
function renderMultiStepBundle(container, bundle,
|
|
3884
|
+
function renderMultiStepBundle(container, bundle, onAddToCart, onCleanup) {
|
|
3802
3885
|
const wc = bundle.widgetConfig;
|
|
3803
3886
|
const t = DEFAULT_STRINGS;
|
|
3804
3887
|
const rulesMap = mergeRuleMaps(bundle.productRules, bundle.variantRules);
|
|
@@ -3831,13 +3914,13 @@ function renderMultiStepBundle(container, bundle, outOfStockBehavior, onAddToCar
|
|
|
3831
3914
|
compareAtPrice: v.compareAtPrice,
|
|
3832
3915
|
unitPrice: v.unitPrice,
|
|
3833
3916
|
image: v.image,
|
|
3834
|
-
inventoryQuantity: null
|
|
3917
|
+
inventoryQuantity: v.inventoryQuantity ?? null
|
|
3835
3918
|
}))
|
|
3836
3919
|
};
|
|
3837
3920
|
})
|
|
3838
3921
|
);
|
|
3839
|
-
const
|
|
3840
|
-
if (
|
|
3922
|
+
const everyStepPickable = pools.length > 0 && pools.every((pool) => pool.some((p) => p.available));
|
|
3923
|
+
if (!everyStepPickable) return;
|
|
3841
3924
|
const formatMoney = intlFormatMoney(
|
|
3842
3925
|
bundle.products[0]?.priceRange.minVariantPrice.currencyCode ?? "USD"
|
|
3843
3926
|
);
|
|
@@ -5649,7 +5732,7 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
5649
5732
|
transition: opacity 0.25s ease, background 0.25s ease, color 0.25s ease, border-color 0.25s ease;
|
|
5650
5733
|
}
|
|
5651
5734
|
|
|
5652
|
-
.lb-mix-match__modal-add:hover {
|
|
5735
|
+
.lb-mix-match__modal-add:hover:not(:disabled) {
|
|
5653
5736
|
opacity: 0.9;
|
|
5654
5737
|
}
|
|
5655
5738
|
|
|
@@ -5764,8 +5847,15 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
5764
5847
|
color: color-mix(in srgb, var(--lb-picker-text) 70%, transparent);
|
|
5765
5848
|
}
|
|
5766
5849
|
|
|
5767
|
-
/* "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. */
|
|
5768
5854
|
.lb-mix-match__modal-done {
|
|
5855
|
+
display: inline-flex;
|
|
5856
|
+
align-items: center;
|
|
5857
|
+
justify-content: center;
|
|
5858
|
+
min-height: 40px;
|
|
5769
5859
|
padding: 10px 24px;
|
|
5770
5860
|
transition: opacity 0.25s ease;
|
|
5771
5861
|
background: var(--lb-picker-add-bg);
|
|
@@ -5779,7 +5869,7 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
5779
5869
|
cursor: pointer;
|
|
5780
5870
|
}
|
|
5781
5871
|
|
|
5782
|
-
.lb-mix-match__modal-done:hover {
|
|
5872
|
+
.lb-mix-match__modal-done:hover:not(:disabled) {
|
|
5783
5873
|
opacity: 0.9;
|
|
5784
5874
|
}
|
|
5785
5875
|
|
|
@@ -5789,6 +5879,16 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
5789
5879
|
box-shadow: none;
|
|
5790
5880
|
}
|
|
5791
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
|
+
|
|
5792
5892
|
/* Hidden utility for search filtering */
|
|
5793
5893
|
.lb-hidden {
|
|
5794
5894
|
display: none !important;
|
|
@@ -6045,6 +6145,13 @@ var BUNDLE_VOLUME_CSS = `/* Lime Bundles \u2014 Volume / Quantity Breaks styles
|
|
|
6045
6145
|
white-space: nowrap;
|
|
6046
6146
|
font-variant-numeric: tabular-nums;
|
|
6047
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
|
+
}
|
|
6048
6155
|
`;
|
|
6049
6156
|
var BUNDLE_BOGO_CSS = `/* Lime Bundles \u2014 BOGO (Buy X Get Y) bundle styles */
|
|
6050
6157
|
|
|
@@ -6213,14 +6320,23 @@ var BUNDLE_MULTI_STEP_CSS = `/**
|
|
|
6213
6320
|
justify-self: end;
|
|
6214
6321
|
}
|
|
6215
6322
|
|
|
6216
|
-
/* Back \u2014 secondary treatment beside the primary Next/Done
|
|
6217
|
-
.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. */
|
|
6218
6329
|
.lb-multi-step__modal-back {
|
|
6219
|
-
|
|
6330
|
+
display: inline-flex;
|
|
6331
|
+
align-items: center;
|
|
6332
|
+
justify-content: center;
|
|
6333
|
+
min-height: 40px;
|
|
6334
|
+
padding: 10px 24px;
|
|
6220
6335
|
background: none;
|
|
6221
|
-
border: var(--lb-border-width) solid var(--lb-border-color);
|
|
6222
|
-
border-radius: var(--lb-radius-sm);
|
|
6223
|
-
|
|
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);
|
|
6224
6340
|
font-family: inherit;
|
|
6225
6341
|
font-size: 14px;
|
|
6226
6342
|
font-weight: 600;
|
|
@@ -6229,7 +6345,7 @@ var BUNDLE_MULTI_STEP_CSS = `/**
|
|
|
6229
6345
|
}
|
|
6230
6346
|
|
|
6231
6347
|
.lb-multi-step__modal-back:hover {
|
|
6232
|
-
background: color-mix(in srgb, var(--lb-text) 5%, transparent);
|
|
6348
|
+
background: color-mix(in srgb, var(--lb-picker-text) 5%, transparent);
|
|
6233
6349
|
}
|
|
6234
6350
|
|
|
6235
6351
|
.lb-multi-step__modal-back:focus-visible {
|
|
@@ -6659,12 +6775,6 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
6659
6775
|
* `.lb-bundle-widget { ... }` reach the widget's DOM.
|
|
6660
6776
|
*/
|
|
6661
6777
|
shopCustomCss = null;
|
|
6662
|
-
/**
|
|
6663
|
-
* Shop-wide out-of-stock behaviour, resolved from the `$app` shop metafield
|
|
6664
|
-
* before the first render. Defaults to `show_greyed_out` so a failed or
|
|
6665
|
-
* missing fetch degrades to "bundles still render".
|
|
6666
|
-
*/
|
|
6667
|
-
outOfStockBehavior = import_core6.DEFAULT_OUT_OF_STOCK_BEHAVIOR;
|
|
6668
6778
|
constructor() {
|
|
6669
6779
|
super();
|
|
6670
6780
|
this.shadow = this.attachShadow({ mode: "open" });
|
|
@@ -6800,9 +6910,6 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
6800
6910
|
const sanitized = (0, import_core6.sanitizeCustomCss)(customCss);
|
|
6801
6911
|
if (sanitized.ok) this.shopCustomCss = sanitized.css;
|
|
6802
6912
|
}
|
|
6803
|
-
this.outOfStockBehavior = (0, import_core6.parseOutOfStockBehavior)(
|
|
6804
|
-
shopSettings?.shop?.outOfStockBehavior?.value
|
|
6805
|
-
);
|
|
6806
6913
|
this.renderBundles();
|
|
6807
6914
|
} catch (err) {
|
|
6808
6915
|
if (controller.signal.aborted) return;
|
|
@@ -6882,6 +6989,17 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
6882
6989
|
const storage = window.localStorage;
|
|
6883
6990
|
const key = cartStorageKey(this.shopDomain);
|
|
6884
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
|
+
};
|
|
6885
7003
|
try {
|
|
6886
7004
|
let checkoutUrl = null;
|
|
6887
7005
|
if (existingCartId) {
|
|
@@ -6891,7 +7009,14 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
6891
7009
|
);
|
|
6892
7010
|
const payload = res.cartLinesAdd;
|
|
6893
7011
|
if (payload?.userErrors?.length) {
|
|
7012
|
+
if (!isStaleCartError(payload.userErrors)) {
|
|
7013
|
+
fail(payload.userErrors[0].message);
|
|
7014
|
+
return;
|
|
7015
|
+
}
|
|
6894
7016
|
storage?.removeItem(key);
|
|
7017
|
+
} else if (stockCapped(payload?.warnings)) {
|
|
7018
|
+
fail("Some items in this bundle are out of stock.");
|
|
7019
|
+
return;
|
|
6895
7020
|
} else if (payload?.cart) {
|
|
6896
7021
|
checkoutUrl = payload.cart.checkoutUrl;
|
|
6897
7022
|
}
|
|
@@ -6902,6 +7027,14 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
6902
7027
|
{ input: { lines } }
|
|
6903
7028
|
);
|
|
6904
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
|
+
}
|
|
6905
7038
|
if (payload?.cart) {
|
|
6906
7039
|
storage?.setItem(key, payload.cart.id);
|
|
6907
7040
|
checkoutUrl = payload.cart.checkoutUrl;
|
|
@@ -6910,13 +7043,7 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
6910
7043
|
if (checkoutUrl) {
|
|
6911
7044
|
window.location.assign(checkoutUrl);
|
|
6912
7045
|
} else {
|
|
6913
|
-
|
|
6914
|
-
new CustomEvent("lime-bundle:error", {
|
|
6915
|
-
detail: { message: "Cart creation failed", code: "CART_ERROR" },
|
|
6916
|
-
bubbles: true,
|
|
6917
|
-
composed: true
|
|
6918
|
-
})
|
|
6919
|
-
);
|
|
7046
|
+
fail("Cart creation failed");
|
|
6920
7047
|
}
|
|
6921
7048
|
} catch (err) {
|
|
6922
7049
|
this.dispatchEvent(
|
|
@@ -6990,44 +7117,25 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
6990
7117
|
const registerCleanup = (fn) => this.renderCleanups.push(fn);
|
|
6991
7118
|
switch (bundle.bundleType) {
|
|
6992
7119
|
case "fixed":
|
|
6993
|
-
renderFixedBundle(
|
|
6994
|
-
container,
|
|
6995
|
-
bundle,
|
|
6996
|
-
this.outOfStockBehavior,
|
|
6997
|
-
dispatch,
|
|
6998
|
-
registerCleanup
|
|
6999
|
-
);
|
|
7120
|
+
renderFixedBundle(container, bundle, dispatch, registerCleanup);
|
|
7000
7121
|
break;
|
|
7001
7122
|
case "mix_match":
|
|
7002
7123
|
renderMixMatchBundle(
|
|
7003
7124
|
container,
|
|
7004
7125
|
bundle,
|
|
7005
|
-
this.outOfStockBehavior,
|
|
7006
7126
|
dispatch,
|
|
7007
7127
|
registerCleanup,
|
|
7008
7128
|
this.currentProductHandle
|
|
7009
7129
|
);
|
|
7010
7130
|
break;
|
|
7011
7131
|
case "volume":
|
|
7012
|
-
renderVolumeBundle(container, bundle,
|
|
7132
|
+
renderVolumeBundle(container, bundle, dispatch);
|
|
7013
7133
|
break;
|
|
7014
7134
|
case "bogo":
|
|
7015
|
-
renderBogoBundle(
|
|
7016
|
-
container,
|
|
7017
|
-
bundle,
|
|
7018
|
-
this.outOfStockBehavior,
|
|
7019
|
-
dispatch,
|
|
7020
|
-
registerCleanup
|
|
7021
|
-
);
|
|
7135
|
+
renderBogoBundle(container, bundle, dispatch, registerCleanup);
|
|
7022
7136
|
break;
|
|
7023
7137
|
case "multi_step":
|
|
7024
|
-
renderMultiStepBundle(
|
|
7025
|
-
container,
|
|
7026
|
-
bundle,
|
|
7027
|
-
this.outOfStockBehavior,
|
|
7028
|
-
dispatch,
|
|
7029
|
-
registerCleanup
|
|
7030
|
-
);
|
|
7138
|
+
renderMultiStepBundle(container, bundle, dispatch, registerCleanup);
|
|
7031
7139
|
break;
|
|
7032
7140
|
}
|
|
7033
7141
|
this.shadow.appendChild(container);
|
package/dist/index.d.cts
CHANGED
|
@@ -82,12 +82,6 @@ declare class LimeBundleElement extends HTMLElement {
|
|
|
82
82
|
* `.lb-bundle-widget { ... }` reach the widget's DOM.
|
|
83
83
|
*/
|
|
84
84
|
private shopCustomCss;
|
|
85
|
-
/**
|
|
86
|
-
* Shop-wide out-of-stock behaviour, resolved from the `$app` shop metafield
|
|
87
|
-
* before the first render. Defaults to `show_greyed_out` so a failed or
|
|
88
|
-
* missing fetch degrades to "bundles still render".
|
|
89
|
-
*/
|
|
90
|
-
private outOfStockBehavior;
|
|
91
85
|
constructor();
|
|
92
86
|
connectedCallback(): void;
|
|
93
87
|
disconnectedCallback(): void;
|
package/dist/index.d.ts
CHANGED
|
@@ -82,12 +82,6 @@ declare class LimeBundleElement extends HTMLElement {
|
|
|
82
82
|
* `.lb-bundle-widget { ... }` reach the widget's DOM.
|
|
83
83
|
*/
|
|
84
84
|
private shopCustomCss;
|
|
85
|
-
/**
|
|
86
|
-
* Shop-wide out-of-stock behaviour, resolved from the `$app` shop metafield
|
|
87
|
-
* before the first render. Defaults to `show_greyed_out` so a failed or
|
|
88
|
-
* missing fetch degrades to "bundles still render".
|
|
89
|
-
*/
|
|
90
|
-
private outOfStockBehavior;
|
|
91
85
|
constructor();
|
|
92
86
|
connectedCallback(): void;
|
|
93
87
|
disconnectedCallback(): void;
|