@lime-bundles/widget 2.5.1 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +420 -127
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +410 -110
- package/dist/index.js.map +1 -1
- package/dist/lime-bundle.global.js +155 -37
- package/dist/lime-bundle.global.js.map +1 -1
- package/docs/css-variables.md +11 -0
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -26,7 +26,7 @@ __export(index_exports, {
|
|
|
26
26
|
module.exports = __toCommonJS(index_exports);
|
|
27
27
|
|
|
28
28
|
// src/lime-bundle.ts
|
|
29
|
-
var
|
|
29
|
+
var import_core8 = require("@lime-bundles/core");
|
|
30
30
|
|
|
31
31
|
// src/renderers/fixed.ts
|
|
32
32
|
var import_core5 = require("@lime-bundles/core");
|
|
@@ -77,6 +77,20 @@ function firstEnabled(opts) {
|
|
|
77
77
|
for (let i = 0; i < opts.length; i++) if (!opts[i].disabled) return i;
|
|
78
78
|
return -1;
|
|
79
79
|
}
|
|
80
|
+
function findScrollableAncestor(el2) {
|
|
81
|
+
const win = el2.ownerDocument?.defaultView;
|
|
82
|
+
if (!win) return null;
|
|
83
|
+
let cur = el2.parentElement;
|
|
84
|
+
while (cur && cur !== el2.ownerDocument.body) {
|
|
85
|
+
const style = win.getComputedStyle(cur);
|
|
86
|
+
const overflowY = style.overflowY;
|
|
87
|
+
if (overflowY === "auto" || overflowY === "scroll" || overflowY === "hidden") {
|
|
88
|
+
return cur;
|
|
89
|
+
}
|
|
90
|
+
cur = cur.parentElement;
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
80
94
|
var VARIANT_SELECT_CLASSES = [
|
|
81
95
|
"lb-bundle-variant-select",
|
|
82
96
|
"lb-mix-match__variant-select"
|
|
@@ -182,6 +196,11 @@ function bindDropdown(select) {
|
|
|
182
196
|
if (rect.width === 0) return false;
|
|
183
197
|
const visibleCount = Math.min(optionEls.length || 1, MAX_VISIBLE_ITEMS);
|
|
184
198
|
const desiredHeight = visibleCount * ITEM_HEIGHT_PX + LIST_PAD_Y;
|
|
199
|
+
const scrollable = listbox.hasAttribute("data-lb-dropdown-portal") ? null : findScrollableAncestor(trigger);
|
|
200
|
+
const clip = scrollable ? (() => {
|
|
201
|
+
const r = scrollable.getBoundingClientRect();
|
|
202
|
+
return { top: r.top, bottom: r.bottom };
|
|
203
|
+
})() : void 0;
|
|
185
204
|
const result = computePosition({
|
|
186
205
|
trigger: {
|
|
187
206
|
top: rect.top,
|
|
@@ -190,7 +209,8 @@ function bindDropdown(select) {
|
|
|
190
209
|
width: rect.width
|
|
191
210
|
},
|
|
192
211
|
viewportHeight: window.innerHeight,
|
|
193
|
-
desiredHeight
|
|
212
|
+
desiredHeight,
|
|
213
|
+
clip
|
|
194
214
|
});
|
|
195
215
|
listbox.setAttribute("data-placement", result.placement);
|
|
196
216
|
listbox.style.maxHeight = `${result.maxHeight}px`;
|
|
@@ -534,9 +554,18 @@ function renderFixedBundle(container, bundle, onAddToCart, onCleanup) {
|
|
|
534
554
|
const list = el("div", "lb-fixed__products");
|
|
535
555
|
const rowHandles = [];
|
|
536
556
|
rows.forEach((rowState) => {
|
|
537
|
-
const handle = renderProductRow(
|
|
538
|
-
|
|
539
|
-
|
|
557
|
+
const handle = renderProductRow(
|
|
558
|
+
rowState,
|
|
559
|
+
currency,
|
|
560
|
+
qtyFor,
|
|
561
|
+
{
|
|
562
|
+
enabled: wc.showLowStockBadge,
|
|
563
|
+
threshold: wc.lowStockThreshold
|
|
564
|
+
},
|
|
565
|
+
() => {
|
|
566
|
+
updatePricing();
|
|
567
|
+
}
|
|
568
|
+
);
|
|
540
569
|
rowHandles.push(handle);
|
|
541
570
|
list.appendChild(handle.el);
|
|
542
571
|
});
|
|
@@ -592,7 +621,9 @@ function renderFixedBundle(container, bundle, onAddToCart, onCleanup) {
|
|
|
592
621
|
function buildRowState(bundle, product, productIndex) {
|
|
593
622
|
const selectedVariantIds = bundle.selectedVariantIds?.[productIndex] ?? null;
|
|
594
623
|
const merchantScoped = selectedVariantIds && selectedVariantIds.length > 0 ? product.variants.nodes.filter((v) => selectedVariantIds.includes(v.id)) : product.variants.nodes;
|
|
595
|
-
const firstInStock = merchantScoped.find(
|
|
624
|
+
const firstInStock = merchantScoped.find(
|
|
625
|
+
(v) => (0, import_core5.isVariantFulfillable)(v, (0, import_core5.resolveBundleQty)(bundle, product.id, v.id))
|
|
626
|
+
) ?? null;
|
|
596
627
|
const eligibleVariants = merchantScoped;
|
|
597
628
|
const isOos = !firstInStock;
|
|
598
629
|
const selected = firstInStock ?? merchantScoped[0] ?? null;
|
|
@@ -656,7 +687,7 @@ function deriveHeaderBadge(bundle, totalCents, saleCents, currency) {
|
|
|
656
687
|
}
|
|
657
688
|
return `-${(0, import_core2.formatCents)(savings, currency)}`;
|
|
658
689
|
}
|
|
659
|
-
function renderProductRow(state, currency, qtyFor, onVariantChange) {
|
|
690
|
+
function renderProductRow(state, currency, qtyFor, lowStock, onVariantChange) {
|
|
660
691
|
const rowEl = el(
|
|
661
692
|
"div",
|
|
662
693
|
state.isOos ? "lb-bundle-product-row lb-bundle-product-row--oos" : "lb-bundle-product-row",
|
|
@@ -703,6 +734,12 @@ function renderProductRow(state, currency, qtyFor, onVariantChange) {
|
|
|
703
734
|
oosLabel.textContent = "Out of stock";
|
|
704
735
|
info.appendChild(oosLabel);
|
|
705
736
|
} else if (state.selected) {
|
|
737
|
+
const isSinglePinnedVariant = state.eligibleVariants.length === 1 && state.product.variants.nodes.length > 1;
|
|
738
|
+
if (isSinglePinnedVariant) {
|
|
739
|
+
const badge = el("span", "lb-bundle-variant-badge");
|
|
740
|
+
badge.textContent = state.eligibleVariants[0].title;
|
|
741
|
+
info.appendChild(badge);
|
|
742
|
+
}
|
|
706
743
|
const prices = el("span", "lb-bundle-product-prices");
|
|
707
744
|
const compare = el("span", "lb-bundle-product-compare-price", {
|
|
708
745
|
"data-product-compare-price": ""
|
|
@@ -718,6 +755,11 @@ function renderProductRow(state, currency, qtyFor, onVariantChange) {
|
|
|
718
755
|
});
|
|
719
756
|
unitPriceEl.setAttribute("hidden", "");
|
|
720
757
|
info.appendChild(unitPriceEl);
|
|
758
|
+
const lowStockEl = el("span", "lb-bundle-low-stock-badge", {
|
|
759
|
+
"data-low-stock-badge": ""
|
|
760
|
+
});
|
|
761
|
+
lowStockEl.setAttribute("hidden", "");
|
|
762
|
+
info.appendChild(lowStockEl);
|
|
721
763
|
const applyVariantToRow = (variant) => {
|
|
722
764
|
const unit = (0, import_core2.parseCents)(variant.price.amount);
|
|
723
765
|
priceEl.textContent = (0, import_core2.formatCents)(unit, currency);
|
|
@@ -752,6 +794,18 @@ function renderProductRow(state, currency, qtyFor, onVariantChange) {
|
|
|
752
794
|
});
|
|
753
795
|
thumbImg.alt = nextImage.altText ?? state.product.title;
|
|
754
796
|
}
|
|
797
|
+
const required = qtyFor(state.product.id, variant.id);
|
|
798
|
+
if ((0, import_core5.shouldShowLowStockBadge)(
|
|
799
|
+
variant,
|
|
800
|
+
required,
|
|
801
|
+
lowStock.threshold,
|
|
802
|
+
lowStock.enabled
|
|
803
|
+
)) {
|
|
804
|
+
lowStockEl.textContent = `Only ${variant.quantityAvailable} left`;
|
|
805
|
+
lowStockEl.removeAttribute("hidden");
|
|
806
|
+
} else {
|
|
807
|
+
lowStockEl.setAttribute("hidden", "");
|
|
808
|
+
}
|
|
755
809
|
};
|
|
756
810
|
applyVariantToRow(state.selected);
|
|
757
811
|
if (state.eligibleVariants.length > 1) {
|
|
@@ -770,7 +824,7 @@ function renderProductRow(state, currency, qtyFor, onVariantChange) {
|
|
|
770
824
|
});
|
|
771
825
|
};
|
|
772
826
|
const isValueAvailable = (optionIndex, value, selected) => state.eligibleVariants.some((v) => {
|
|
773
|
-
if (!v.
|
|
827
|
+
if (!(0, import_core5.isVariantFulfillable)(v, qtyFor(state.product.id, v.id))) return false;
|
|
774
828
|
if (v.selectedOptions[optionIndex]?.value !== value) return false;
|
|
775
829
|
return v.selectedOptions.every(
|
|
776
830
|
(o, i) => i === optionIndex || o.value === selected[i]
|
|
@@ -834,10 +888,6 @@ function renderProductRow(state, currency, qtyFor, onVariantChange) {
|
|
|
834
888
|
if (state.selected) {
|
|
835
889
|
recomputeDisabled(state.selected.selectedOptions.map((o) => o.value));
|
|
836
890
|
}
|
|
837
|
-
} else if (state.eligibleVariants.length === 1 && state.product.variants.nodes.length > 1) {
|
|
838
|
-
const badge = el("span", "lb-bundle-variant-badge");
|
|
839
|
-
badge.textContent = state.eligibleVariants[0].title;
|
|
840
|
-
info.appendChild(badge);
|
|
841
891
|
}
|
|
842
892
|
}
|
|
843
893
|
rowEl.appendChild(info);
|
|
@@ -943,18 +993,36 @@ var SEARCH_CLEAR_ICON_SVG = `
|
|
|
943
993
|
<svg width="16" height="16" viewBox="0 0 20 20" fill="currentColor" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
944
994
|
<path d="M14.348 5.652a.5.5 0 0 0-.707 0L10 9.293 6.36 5.652a.5.5 0 1 0-.708.707L9.293 10l-3.641 3.641a.5.5 0 0 0 .708.707L10 10.707l3.641 3.641a.5.5 0 0 0 .707-.707L10.707 10l3.641-3.641a.5.5 0 0 0 0-.707z"/>
|
|
945
995
|
</svg>`;
|
|
996
|
+
var STEPPER_MINUS_SVG = `
|
|
997
|
+
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
998
|
+
<line x1="3" y1="7" x2="11" y2="7" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
|
999
|
+
</svg>`;
|
|
1000
|
+
var STEPPER_PLUS_SVG = `
|
|
1001
|
+
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
1002
|
+
<line x1="7" y1="3" x2="7" y2="11" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
|
1003
|
+
<line x1="3" y1="7" x2="11" y2="7" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
|
1004
|
+
</svg>`;
|
|
1005
|
+
function ruleFor(bundle, productId) {
|
|
1006
|
+
return bundle.productRules[productId] ?? import_core6.DEFAULT_PRODUCT_RULE;
|
|
1007
|
+
}
|
|
1008
|
+
function alreadyInBundleFor(selections, productId, variantId) {
|
|
1009
|
+
let sum = 0;
|
|
1010
|
+
for (const s of selections) {
|
|
1011
|
+
if (s.productId === productId && s.variantId === variantId) sum += s.quantity;
|
|
1012
|
+
}
|
|
1013
|
+
return sum;
|
|
1014
|
+
}
|
|
946
1015
|
function renderMixMatchBundle(container, bundle, onAddToCart, onCleanup) {
|
|
947
1016
|
const wc = bundle.widgetConfig;
|
|
948
1017
|
const currency = bundle.products[0]?.priceRange.minVariantPrice.currencyCode ?? "USD";
|
|
949
1018
|
const requiredQty = bundle.minQuantity ?? 1;
|
|
950
|
-
const
|
|
1019
|
+
const showQtySelector = wc.mixMatchShowQuantitySelector !== false;
|
|
951
1020
|
const eligible = buildEligibleProducts(bundle, wc.outOfStockBehavior);
|
|
952
1021
|
const inStockCount = eligible.filter((e) => !e.isOos).length;
|
|
953
1022
|
if (inStockCount < requiredQty) return;
|
|
954
1023
|
const selections = [];
|
|
955
1024
|
const root = el("div", "lb-mix-match", {
|
|
956
|
-
"data-required-quantity": String(requiredQty)
|
|
957
|
-
"data-max-quantity": String(maxQty)
|
|
1025
|
+
"data-required-quantity": String(requiredQty)
|
|
958
1026
|
});
|
|
959
1027
|
const header = renderHeader2(bundle);
|
|
960
1028
|
root.appendChild(header);
|
|
@@ -988,24 +1056,17 @@ function renderMixMatchBundle(container, bundle, onAddToCart, onCleanup) {
|
|
|
988
1056
|
root.appendChild(placeholder);
|
|
989
1057
|
const modal = renderModal(bundle, eligible, currency, {
|
|
990
1058
|
showSearch: wc.showSearch,
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
1059
|
+
showQtySelector,
|
|
1060
|
+
selections,
|
|
1061
|
+
onAdd: (product, variant, quantity) => addSelection(product, variant, quantity),
|
|
1062
|
+
isComplete: () => selections.length >= requiredQty
|
|
994
1063
|
});
|
|
995
1064
|
root.appendChild(modal.el);
|
|
996
1065
|
const cta = buildCtaButton(`Select ${requiredQty} items to unlock`);
|
|
997
1066
|
cta.disabled = true;
|
|
998
1067
|
cta.addEventListener("click", () => {
|
|
999
1068
|
if (cta.disabled) return;
|
|
1000
|
-
|
|
1001
|
-
merchandiseId: s.variantId,
|
|
1002
|
-
quantity: s.quantity,
|
|
1003
|
-
attributes: [
|
|
1004
|
-
{ key: "_lime_bundle_gid", value: bundle.id },
|
|
1005
|
-
{ key: "_lime_bundle_type", value: bundle.bundleType }
|
|
1006
|
-
]
|
|
1007
|
-
}));
|
|
1008
|
-
onAddToCart(lines);
|
|
1069
|
+
onAddToCart(buildCartLines(selections, bundle));
|
|
1009
1070
|
});
|
|
1010
1071
|
root.appendChild(cta);
|
|
1011
1072
|
root.appendChild(
|
|
@@ -1019,28 +1080,17 @@ function renderMixMatchBundle(container, bundle, onAddToCart, onCleanup) {
|
|
|
1019
1080
|
);
|
|
1020
1081
|
container.appendChild(root);
|
|
1021
1082
|
onCleanup?.(() => unbindAllDropdowns(root));
|
|
1022
|
-
const firstEligible = eligible.find((ep) => !ep.isOos);
|
|
1023
|
-
const firstVariant = firstEligible?.firstAvailableVariant ?? firstEligible?.variants[0];
|
|
1024
|
-
if (firstEligible && firstVariant) {
|
|
1025
|
-
selections.push({
|
|
1026
|
-
productId: firstEligible.product.id,
|
|
1027
|
-
productTitle: firstEligible.product.title,
|
|
1028
|
-
variantId: firstVariant.id,
|
|
1029
|
-
variantTitle: firstVariant.title,
|
|
1030
|
-
imageUrl: firstVariant.image?.url ?? firstEligible.product.featuredImage?.url ?? null,
|
|
1031
|
-
priceCents: (0, import_core2.parseCents)(firstVariant.price.amount),
|
|
1032
|
-
compareCents: firstVariant.compareAtPrice ? (0, import_core2.parseCents)(firstVariant.compareAtPrice.amount) : null,
|
|
1033
|
-
unitPriceLabel: (0, import_core2.formatUnitPrice)(
|
|
1034
|
-
firstVariant.unitPrice,
|
|
1035
|
-
firstVariant.unitPriceMeasurement,
|
|
1036
|
-
currency
|
|
1037
|
-
),
|
|
1038
|
-
quantity: (0, import_core6.resolveBundleQty)(bundle, firstEligible.product.id, firstVariant.id)
|
|
1039
|
-
});
|
|
1040
|
-
}
|
|
1041
1083
|
afterMutation();
|
|
1042
|
-
function addSelection(product, variant) {
|
|
1043
|
-
if (selections.length >=
|
|
1084
|
+
function addSelection(product, variant, quantity) {
|
|
1085
|
+
if (selections.length >= requiredQty) return;
|
|
1086
|
+
const rule = ruleFor(bundle, product.id);
|
|
1087
|
+
const cap = (0, import_core6.maxAddableQuantity)(
|
|
1088
|
+
variant,
|
|
1089
|
+
rule.max,
|
|
1090
|
+
alreadyInBundleFor(selections, product.id, variant.id)
|
|
1091
|
+
);
|
|
1092
|
+
if (cap < rule.min) return;
|
|
1093
|
+
const clamped = Math.max(rule.min, Math.min(quantity, cap));
|
|
1044
1094
|
selections.push({
|
|
1045
1095
|
productId: product.id,
|
|
1046
1096
|
productTitle: product.title,
|
|
@@ -1054,18 +1104,10 @@ function renderMixMatchBundle(container, bundle, onAddToCart, onCleanup) {
|
|
|
1054
1104
|
variant.unitPriceMeasurement,
|
|
1055
1105
|
currency
|
|
1056
1106
|
),
|
|
1057
|
-
quantity:
|
|
1107
|
+
quantity: clamped
|
|
1058
1108
|
});
|
|
1059
1109
|
afterMutation();
|
|
1060
1110
|
}
|
|
1061
|
-
function removeSelection(productId, variantId) {
|
|
1062
|
-
const idx = selections.findIndex(
|
|
1063
|
-
(s) => s.productId === productId && s.variantId === variantId
|
|
1064
|
-
);
|
|
1065
|
-
if (idx === -1) return;
|
|
1066
|
-
selections.splice(idx, 1);
|
|
1067
|
-
afterMutation();
|
|
1068
|
-
}
|
|
1069
1111
|
function removeSlotAt(index) {
|
|
1070
1112
|
if (index < 0 || index >= selections.length) return;
|
|
1071
1113
|
selections.splice(index, 1);
|
|
@@ -1107,6 +1149,26 @@ function renderMixMatchBundle(container, bundle, onAddToCart, onCleanup) {
|
|
|
1107
1149
|
}
|
|
1108
1150
|
}
|
|
1109
1151
|
}
|
|
1152
|
+
function buildCartLines(selections, bundle) {
|
|
1153
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
1154
|
+
for (const s of selections) {
|
|
1155
|
+
const key = `${s.productId}::${s.variantId}`;
|
|
1156
|
+
const existing = grouped.get(key);
|
|
1157
|
+
if (existing) {
|
|
1158
|
+
existing.quantity += s.quantity;
|
|
1159
|
+
} else {
|
|
1160
|
+
grouped.set(key, { variantId: s.variantId, quantity: s.quantity });
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
return Array.from(grouped.values()).map((line) => ({
|
|
1164
|
+
merchandiseId: line.variantId,
|
|
1165
|
+
quantity: line.quantity,
|
|
1166
|
+
attributes: [
|
|
1167
|
+
{ key: "_lime_bundle_gid", value: bundle.id },
|
|
1168
|
+
{ key: "_lime_bundle_type", value: bundle.bundleType }
|
|
1169
|
+
]
|
|
1170
|
+
}));
|
|
1171
|
+
}
|
|
1110
1172
|
function renderHeader2(bundle) {
|
|
1111
1173
|
const wc = bundle.widgetConfig;
|
|
1112
1174
|
const header = el("div", "lb-bundle-header");
|
|
@@ -1222,7 +1284,7 @@ function renderFilledSlot(selection, index, currency, onRemove) {
|
|
|
1222
1284
|
thumb.insertAdjacentHTML("beforeend", PLACEHOLDER_THUMB_SVG2);
|
|
1223
1285
|
}
|
|
1224
1286
|
const qtyBadge = el("span", "lb-bundle-qty-badge");
|
|
1225
|
-
qtyBadge.textContent =
|
|
1287
|
+
qtyBadge.textContent = `\xD7${selection.quantity}`;
|
|
1226
1288
|
thumb.appendChild(qtyBadge);
|
|
1227
1289
|
slot.appendChild(thumb);
|
|
1228
1290
|
const info = el("div", "lb-mix-match__filled-info");
|
|
@@ -1411,8 +1473,9 @@ function renderModal(bundle, eligible, currency, handlers) {
|
|
|
1411
1473
|
rowsBuilt = true;
|
|
1412
1474
|
list.innerHTML = "";
|
|
1413
1475
|
eligible.forEach((ep) => {
|
|
1476
|
+
const rule = ruleFor(bundle, ep.product.id);
|
|
1414
1477
|
const availableVariants = ep.variants;
|
|
1415
|
-
const firstAvailVariant = ep.variants.find((v) => v.
|
|
1478
|
+
const firstAvailVariant = ep.variants.find((v) => (0, import_core6.isVariantFulfillable)(v, rule.min)) ?? ep.firstAvailableVariant ?? ep.variants[0];
|
|
1416
1479
|
if (!firstAvailVariant) return;
|
|
1417
1480
|
let currentVariant = firstAvailVariant;
|
|
1418
1481
|
const productEl = el(
|
|
@@ -1421,7 +1484,7 @@ function renderModal(bundle, eligible, currency, handlers) {
|
|
|
1421
1484
|
{ "data-product-id": ep.product.id.replace(/^.*\//, "") }
|
|
1422
1485
|
);
|
|
1423
1486
|
const thumb = el("div", "lb-mix-match__modal-product-thumb");
|
|
1424
|
-
const initialThumbVariant = ep.variants.find((v) => v.
|
|
1487
|
+
const initialThumbVariant = ep.variants.find((v) => (0, import_core6.isVariantFulfillable)(v, rule.min)) ?? ep.variants[0] ?? null;
|
|
1425
1488
|
const initialThumbImage = initialThumbVariant?.image ?? ep.product.featuredImage ?? null;
|
|
1426
1489
|
let thumbImg = null;
|
|
1427
1490
|
if (initialThumbImage) {
|
|
@@ -1440,9 +1503,7 @@ function renderModal(bundle, eligible, currency, handlers) {
|
|
|
1440
1503
|
thumb.insertAdjacentHTML("beforeend", PLACEHOLDER_THUMB_SVG2);
|
|
1441
1504
|
}
|
|
1442
1505
|
const countBadge = el("span", "lb-bundle-qty-badge");
|
|
1443
|
-
countBadge.
|
|
1444
|
-
(0, import_core6.resolveBundleQty)(bundle, ep.product.id, currentVariant.id)
|
|
1445
|
-
);
|
|
1506
|
+
countBadge.hidden = true;
|
|
1446
1507
|
thumb.appendChild(countBadge);
|
|
1447
1508
|
productEl.appendChild(thumb);
|
|
1448
1509
|
const info = el("div", "lb-mix-match__modal-product-info");
|
|
@@ -1450,10 +1511,7 @@ function renderModal(bundle, eligible, currency, handlers) {
|
|
|
1450
1511
|
title.textContent = ep.product.title;
|
|
1451
1512
|
info.appendChild(title);
|
|
1452
1513
|
const price = el("p", "lb-mix-match__modal-product-price");
|
|
1453
|
-
price.textContent = (0, import_core2.formatCents)(
|
|
1454
|
-
(0, import_core2.parseCents)(currentVariant.price.amount) * (0, import_core6.resolveBundleQty)(bundle, ep.product.id, currentVariant.id),
|
|
1455
|
-
currency
|
|
1456
|
-
);
|
|
1514
|
+
price.textContent = (0, import_core2.formatCents)((0, import_core2.parseCents)(currentVariant.price.amount), currency);
|
|
1457
1515
|
info.appendChild(price);
|
|
1458
1516
|
const unitPrice = el(
|
|
1459
1517
|
"p",
|
|
@@ -1470,11 +1528,59 @@ function renderModal(bundle, eligible, currency, handlers) {
|
|
|
1470
1528
|
unitPrice.hidden = true;
|
|
1471
1529
|
}
|
|
1472
1530
|
info.appendChild(unitPrice);
|
|
1531
|
+
const lowStockBadge = el("span", "lb-bundle-low-stock-badge", {
|
|
1532
|
+
"data-low-stock-badge": ""
|
|
1533
|
+
});
|
|
1534
|
+
lowStockBadge.hidden = true;
|
|
1535
|
+
info.appendChild(lowStockBadge);
|
|
1536
|
+
const refreshLowStockBadge = (variant) => {
|
|
1537
|
+
if ((0, import_core6.shouldShowLowStockBadge)(
|
|
1538
|
+
variant,
|
|
1539
|
+
rule.min,
|
|
1540
|
+
bundle.widgetConfig.lowStockThreshold,
|
|
1541
|
+
bundle.widgetConfig.showLowStockBadge
|
|
1542
|
+
)) {
|
|
1543
|
+
lowStockBadge.textContent = `Only ${variant.quantityAvailable} left`;
|
|
1544
|
+
lowStockBadge.hidden = false;
|
|
1545
|
+
} else {
|
|
1546
|
+
lowStockBadge.hidden = true;
|
|
1547
|
+
}
|
|
1548
|
+
};
|
|
1549
|
+
refreshLowStockBadge(currentVariant);
|
|
1550
|
+
const computeStepperBounds = () => {
|
|
1551
|
+
const already = alreadyInBundleFor(
|
|
1552
|
+
handlers.selections,
|
|
1553
|
+
ep.product.id,
|
|
1554
|
+
currentVariant.id
|
|
1555
|
+
);
|
|
1556
|
+
const cap = (0, import_core6.maxAddableQuantity)(currentVariant, rule.max, already);
|
|
1557
|
+
const max = Math.max(rule.min, cap);
|
|
1558
|
+
return { min: rule.min, max, cap };
|
|
1559
|
+
};
|
|
1560
|
+
let stepper = null;
|
|
1561
|
+
if (!ep.isOos && handlers.showQtySelector) {
|
|
1562
|
+
const qtyGroup = el(
|
|
1563
|
+
"div",
|
|
1564
|
+
"lb-bundle-variant-option-group lb-mix-match__qty-stepper-group"
|
|
1565
|
+
);
|
|
1566
|
+
const qtyLabel = el("span", "lb-bundle-variant-option-label");
|
|
1567
|
+
qtyLabel.textContent = "Quantity";
|
|
1568
|
+
qtyGroup.appendChild(qtyLabel);
|
|
1569
|
+
stepper = renderQtyStepper({
|
|
1570
|
+
initial: rule.min,
|
|
1571
|
+
getBounds: () => {
|
|
1572
|
+
const { min, max } = computeStepperBounds();
|
|
1573
|
+
return { min, max };
|
|
1574
|
+
}
|
|
1575
|
+
});
|
|
1576
|
+
qtyGroup.appendChild(stepper.el);
|
|
1577
|
+
info.appendChild(qtyGroup);
|
|
1578
|
+
}
|
|
1473
1579
|
const row = {
|
|
1474
1580
|
el: productEl,
|
|
1475
1581
|
product: ep.product,
|
|
1476
1582
|
variant: firstAvailVariant,
|
|
1477
|
-
|
|
1583
|
+
refreshFromSelections: () => {
|
|
1478
1584
|
}
|
|
1479
1585
|
};
|
|
1480
1586
|
if (availableVariants.length > 1) {
|
|
@@ -1493,7 +1599,7 @@ function renderModal(bundle, eligible, currency, handlers) {
|
|
|
1493
1599
|
});
|
|
1494
1600
|
};
|
|
1495
1601
|
const isValueAvailable = (optionIndex, value, selected) => availableVariants.some((v) => {
|
|
1496
|
-
if (!v.
|
|
1602
|
+
if (!(0, import_core6.isVariantFulfillable)(v, rule.min)) return false;
|
|
1497
1603
|
if (v.selectedOptions[optionIndex]?.value !== value) return false;
|
|
1498
1604
|
return v.selectedOptions.every(
|
|
1499
1605
|
(o, i) => i === optionIndex || o.value === selected[i]
|
|
@@ -1518,15 +1624,7 @@ function renderModal(bundle, eligible, currency, handlers) {
|
|
|
1518
1624
|
}
|
|
1519
1625
|
currentVariant = next;
|
|
1520
1626
|
row.variant = next;
|
|
1521
|
-
|
|
1522
|
-
bundle,
|
|
1523
|
-
ep.product.id,
|
|
1524
|
-
currentVariant.id
|
|
1525
|
-
);
|
|
1526
|
-
price.textContent = (0, import_core2.formatCents)(
|
|
1527
|
-
(0, import_core2.parseCents)(currentVariant.price.amount) * nextQty,
|
|
1528
|
-
currency
|
|
1529
|
-
);
|
|
1627
|
+
price.textContent = (0, import_core2.formatCents)((0, import_core2.parseCents)(currentVariant.price.amount), currency);
|
|
1530
1628
|
const nextUnitText = (0, import_core2.formatUnitPrice)(
|
|
1531
1629
|
currentVariant.unitPrice,
|
|
1532
1630
|
currentVariant.unitPriceMeasurement,
|
|
@@ -1549,7 +1647,8 @@ function renderModal(bundle, eligible, currency, handlers) {
|
|
|
1549
1647
|
thumbImg.alt = nextImage.altText ?? ep.product.title;
|
|
1550
1648
|
}
|
|
1551
1649
|
recomputeDisabled(next.selectedOptions.map((o) => o.value));
|
|
1552
|
-
|
|
1650
|
+
refreshLowStockBadge(currentVariant);
|
|
1651
|
+
row.refreshFromSelections();
|
|
1553
1652
|
};
|
|
1554
1653
|
const groupsContainer = el("div", "lb-bundle-variant-option-groups");
|
|
1555
1654
|
optionNames.forEach((name, position) => {
|
|
@@ -1596,25 +1695,45 @@ function renderModal(bundle, eligible, currency, handlers) {
|
|
|
1596
1695
|
info.appendChild(soldOut);
|
|
1597
1696
|
}
|
|
1598
1697
|
productEl.appendChild(info);
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1698
|
+
let addBtn = null;
|
|
1699
|
+
const refreshAddState = () => {
|
|
1700
|
+
if (!addBtn) return;
|
|
1701
|
+
const { cap } = computeStepperBounds();
|
|
1702
|
+
const already = alreadyInBundleFor(
|
|
1703
|
+
handlers.selections,
|
|
1704
|
+
ep.product.id,
|
|
1705
|
+
currentVariant.id
|
|
1602
1706
|
);
|
|
1707
|
+
if (already > 0) {
|
|
1708
|
+
countBadge.textContent = String(already);
|
|
1709
|
+
countBadge.hidden = false;
|
|
1710
|
+
} else {
|
|
1711
|
+
countBadge.hidden = true;
|
|
1712
|
+
}
|
|
1713
|
+
addBtn.disabled = handlers.isComplete() || cap < rule.min;
|
|
1603
1714
|
};
|
|
1604
1715
|
if (!ep.isOos) {
|
|
1605
|
-
const
|
|
1716
|
+
const actions = el("div", "lb-mix-match__modal-product-actions");
|
|
1717
|
+
addBtn = document.createElement("button");
|
|
1606
1718
|
addBtn.type = "button";
|
|
1607
1719
|
addBtn.className = "lb-mix-match__modal-add";
|
|
1608
1720
|
addBtn.textContent = "Add";
|
|
1609
1721
|
addBtn.addEventListener("click", () => {
|
|
1610
|
-
if (
|
|
1611
|
-
handlers.
|
|
1722
|
+
if (!addBtn || addBtn.disabled) return;
|
|
1723
|
+
if (handlers.isComplete()) return;
|
|
1724
|
+
const qty = stepper ? stepper.value() : rule.min;
|
|
1725
|
+
handlers.onAdd(ep.product, currentVariant, qty);
|
|
1726
|
+
if (stepper) stepper.reset(rule.min);
|
|
1612
1727
|
close();
|
|
1613
1728
|
});
|
|
1614
|
-
|
|
1729
|
+
actions.appendChild(addBtn);
|
|
1730
|
+
productEl.appendChild(actions);
|
|
1615
1731
|
}
|
|
1616
|
-
row.
|
|
1617
|
-
|
|
1732
|
+
row.refreshFromSelections = () => {
|
|
1733
|
+
if (stepper) stepper.refresh();
|
|
1734
|
+
refreshAddState();
|
|
1735
|
+
};
|
|
1736
|
+
row.refreshFromSelections();
|
|
1618
1737
|
productRows.push(row);
|
|
1619
1738
|
list.appendChild(productEl);
|
|
1620
1739
|
});
|
|
@@ -1649,7 +1768,7 @@ function renderModal(bundle, eligible, currency, handlers) {
|
|
|
1649
1768
|
let isOpen = false;
|
|
1650
1769
|
function open() {
|
|
1651
1770
|
if (isOpen) return;
|
|
1652
|
-
if (handlers.
|
|
1771
|
+
if (handlers.isComplete()) return;
|
|
1653
1772
|
isOpen = true;
|
|
1654
1773
|
buildRows();
|
|
1655
1774
|
lastFocused = overlay.getRootNode().activeElement;
|
|
@@ -1674,17 +1793,73 @@ function renderModal(bundle, eligible, currency, handlers) {
|
|
|
1674
1793
|
if (e.target === overlay) close();
|
|
1675
1794
|
}
|
|
1676
1795
|
function refreshCounts() {
|
|
1677
|
-
productRows.forEach((r) => r.
|
|
1796
|
+
productRows.forEach((r) => r.refreshFromSelections());
|
|
1678
1797
|
}
|
|
1679
1798
|
return { el: overlay, open, close, refreshCounts };
|
|
1680
1799
|
}
|
|
1800
|
+
function renderQtyStepper(opts) {
|
|
1801
|
+
const wrap = el("div", "lb-mix-match__qty-stepper", {
|
|
1802
|
+
role: "group",
|
|
1803
|
+
"aria-label": "Quantity"
|
|
1804
|
+
});
|
|
1805
|
+
const minus = document.createElement("button");
|
|
1806
|
+
minus.type = "button";
|
|
1807
|
+
minus.className = "lb-mix-match__qty-stepper-button lb-mix-match__qty-stepper-button--minus";
|
|
1808
|
+
minus.setAttribute("aria-label", "Decrease quantity");
|
|
1809
|
+
minus.innerHTML = STEPPER_MINUS_SVG;
|
|
1810
|
+
wrap.appendChild(minus);
|
|
1811
|
+
const valueEl = el("span", "lb-mix-match__qty-stepper-value", {
|
|
1812
|
+
"aria-live": "polite"
|
|
1813
|
+
});
|
|
1814
|
+
wrap.appendChild(valueEl);
|
|
1815
|
+
const plus = document.createElement("button");
|
|
1816
|
+
plus.type = "button";
|
|
1817
|
+
plus.className = "lb-mix-match__qty-stepper-button lb-mix-match__qty-stepper-button--plus";
|
|
1818
|
+
plus.setAttribute("aria-label", "Increase quantity");
|
|
1819
|
+
plus.innerHTML = STEPPER_PLUS_SVG;
|
|
1820
|
+
wrap.appendChild(plus);
|
|
1821
|
+
let current = clamp2(opts.initial, opts.getBounds());
|
|
1822
|
+
function clamp2(n, b) {
|
|
1823
|
+
return Math.max(b.min, Math.min(b.max, n));
|
|
1824
|
+
}
|
|
1825
|
+
function paint() {
|
|
1826
|
+
const b = opts.getBounds();
|
|
1827
|
+
current = clamp2(current, b);
|
|
1828
|
+
valueEl.textContent = String(current);
|
|
1829
|
+
minus.disabled = current <= b.min;
|
|
1830
|
+
plus.disabled = current >= b.max;
|
|
1831
|
+
}
|
|
1832
|
+
minus.addEventListener("click", () => {
|
|
1833
|
+
const b = opts.getBounds();
|
|
1834
|
+
current = clamp2(current - 1, b);
|
|
1835
|
+
paint();
|
|
1836
|
+
});
|
|
1837
|
+
plus.addEventListener("click", () => {
|
|
1838
|
+
const b = opts.getBounds();
|
|
1839
|
+
current = clamp2(current + 1, b);
|
|
1840
|
+
paint();
|
|
1841
|
+
});
|
|
1842
|
+
paint();
|
|
1843
|
+
return {
|
|
1844
|
+
el: wrap,
|
|
1845
|
+
value: () => current,
|
|
1846
|
+
reset(qty) {
|
|
1847
|
+
current = clamp2(qty, opts.getBounds());
|
|
1848
|
+
paint();
|
|
1849
|
+
},
|
|
1850
|
+
refresh: paint
|
|
1851
|
+
};
|
|
1852
|
+
}
|
|
1681
1853
|
function buildEligibleProducts(bundle, oosBehavior) {
|
|
1682
1854
|
const result = [];
|
|
1683
1855
|
const seen = /* @__PURE__ */ new Set();
|
|
1684
1856
|
for (const product of bundle.products) {
|
|
1685
1857
|
if (seen.has(product.id)) continue;
|
|
1686
1858
|
seen.add(product.id);
|
|
1687
|
-
const
|
|
1859
|
+
const rule = ruleFor(bundle, product.id);
|
|
1860
|
+
const available = product.variants.nodes.filter(
|
|
1861
|
+
(v) => (0, import_core6.isVariantFulfillable)(v, rule.min)
|
|
1862
|
+
);
|
|
1688
1863
|
const isOos = available.length === 0;
|
|
1689
1864
|
if (isOos && oosBehavior === "hide") continue;
|
|
1690
1865
|
result.push({
|
|
@@ -1717,10 +1892,14 @@ function sanitizeId(gid) {
|
|
|
1717
1892
|
}
|
|
1718
1893
|
|
|
1719
1894
|
// src/renderers/volume.ts
|
|
1895
|
+
var import_core7 = require("@lime-bundles/core");
|
|
1720
1896
|
function renderVolumeBundle(container, bundle, onAddToCart, onCleanup) {
|
|
1721
1897
|
const wc = bundle.widgetConfig;
|
|
1722
1898
|
const product = bundle.products[0];
|
|
1723
|
-
const
|
|
1899
|
+
const minTierQty = bundle.volumeTiers[0]?.minQuantity ?? 1;
|
|
1900
|
+
const variant = product?.variants.nodes.find(
|
|
1901
|
+
(v) => (0, import_core7.isVariantFulfillable)(v, minTierQty)
|
|
1902
|
+
);
|
|
1724
1903
|
if (!variant && wc.outOfStockBehavior === "hide") return;
|
|
1725
1904
|
const basePriceCents = variant ? (0, import_core2.parseCents)(variant.price.amount) : 0;
|
|
1726
1905
|
const currency = variant?.price.currencyCode ?? "USD";
|
|
@@ -1819,6 +1998,18 @@ function renderVolumeBundle(container, bundle, onAddToCart, onCleanup) {
|
|
|
1819
1998
|
}
|
|
1820
1999
|
]);
|
|
1821
2000
|
});
|
|
2001
|
+
if (variant && (0, import_core7.shouldShowLowStockBadge)(
|
|
2002
|
+
variant,
|
|
2003
|
+
minTierQty,
|
|
2004
|
+
wc.lowStockThreshold,
|
|
2005
|
+
wc.showLowStockBadge
|
|
2006
|
+
)) {
|
|
2007
|
+
const lowStock = el("span", "lb-bundle-low-stock-badge", {
|
|
2008
|
+
"data-low-stock-badge": ""
|
|
2009
|
+
});
|
|
2010
|
+
lowStock.textContent = `Only ${variant.quantityAvailable} left`;
|
|
2011
|
+
root.appendChild(lowStock);
|
|
2012
|
+
}
|
|
1822
2013
|
root.appendChild(cta);
|
|
1823
2014
|
root.appendChild(
|
|
1824
2015
|
el("p", "lb-bundle-error", { "data-error": "", "aria-live": "polite" })
|
|
@@ -1972,7 +2163,10 @@ function renderSavingsBar3(resolved, selectedIndex, currency) {
|
|
|
1972
2163
|
}
|
|
1973
2164
|
function renderCta2(bundle, onClick) {
|
|
1974
2165
|
const product = bundle.products[0];
|
|
1975
|
-
const
|
|
2166
|
+
const minTierQty = bundle.volumeTiers[0]?.minQuantity ?? 1;
|
|
2167
|
+
const isAvailable = product?.variants.nodes.some(
|
|
2168
|
+
(v) => (0, import_core7.isVariantFulfillable)(v, minTierQty)
|
|
2169
|
+
);
|
|
1976
2170
|
const label = isAvailable ? bundle.widgetConfig.cta.ctaText || "Add to cart" : "Sold out";
|
|
1977
2171
|
const button = buildCtaButton(label);
|
|
1978
2172
|
if (!isAvailable) button.disabled = true;
|
|
@@ -2014,7 +2208,7 @@ function clamp(n, min, max) {
|
|
|
2014
2208
|
}
|
|
2015
2209
|
|
|
2016
2210
|
// src/lime-bundle.ts
|
|
2017
|
-
var
|
|
2211
|
+
var import_core9 = require("@lime-bundles/core");
|
|
2018
2212
|
|
|
2019
2213
|
// src/utils/input-mode.ts
|
|
2020
2214
|
var NAV_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -2349,15 +2543,19 @@ var BUNDLE_BASE_CSS = `/* Lime Bundles \u2014 shared base styles for all bundle
|
|
|
2349
2543
|
display: none;
|
|
2350
2544
|
}
|
|
2351
2545
|
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2546
|
+
/* Read-only variant text shown below the product title when only one
|
|
2547
|
+
variant is in scope (single-variant product, or merchant pinned a
|
|
2548
|
+
single variant). Same visual treatment as the mix-match filled
|
|
2549
|
+
slot's variant text \u2014 see .lb-mix-match__filled-variant in
|
|
2550
|
+
bundle-mix-match.css. Both share this rule via the comma selector
|
|
2551
|
+
so the storefront UX stays consistent across bundle types. */
|
|
2552
|
+
.lb-bundle-variant-badge,
|
|
2553
|
+
.lb-mix-match__slot--filled .lb-mix-match__filled-variant {
|
|
2554
|
+
display: block;
|
|
2357
2555
|
font-size: 12px;
|
|
2358
|
-
line-height:
|
|
2556
|
+
line-height: 20px;
|
|
2557
|
+
margin-top: 2px;
|
|
2359
2558
|
color: color-mix(in srgb, var(--lb-text) 60%, transparent);
|
|
2360
|
-
margin-top: 8px;
|
|
2361
2559
|
}
|
|
2362
2560
|
|
|
2363
2561
|
/* Per-option variant pickers. Each option's label + select sit inside a
|
|
@@ -2631,6 +2829,24 @@ var BUNDLE_BASE_CSS = `/* Lime Bundles \u2014 shared base styles for all bundle
|
|
|
2631
2829
|
margin-left: auto;
|
|
2632
2830
|
}
|
|
2633
2831
|
|
|
2832
|
+
/* "Only X left" low-stock badge \u2014 appears alongside product/variant
|
|
2833
|
+
info when ProductVariant.quantityAvailable falls at or below
|
|
2834
|
+
wc.lowStockThreshold. Hidden when quantityAvailable is unknown
|
|
2835
|
+
(Storefront token without read_product_inventory). */
|
|
2836
|
+
.lb-bundle-low-stock-badge {
|
|
2837
|
+
display: inline-flex;
|
|
2838
|
+
align-items: center;
|
|
2839
|
+
gap: 4px;
|
|
2840
|
+
padding: 2px 8px;
|
|
2841
|
+
font-size: 11px;
|
|
2842
|
+
font-weight: 600;
|
|
2843
|
+
line-height: 1.4;
|
|
2844
|
+
color: var(--lb-low-stock-text);
|
|
2845
|
+
background-color: var(--lb-low-stock-bg);
|
|
2846
|
+
border-radius: 4px;
|
|
2847
|
+
white-space: nowrap;
|
|
2848
|
+
}
|
|
2849
|
+
|
|
2634
2850
|
/* A/B test: hide save badge until JS swaps the label (prevents flash of default) */
|
|
2635
2851
|
.lb-ab-pending {
|
|
2636
2852
|
visibility: hidden;
|
|
@@ -2854,12 +3070,9 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
2854
3070
|
text-decoration: underline;
|
|
2855
3071
|
}
|
|
2856
3072
|
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
margin-top: 2px;
|
|
2861
|
-
color: color-mix(in srgb, var(--lb-text) 60%, transparent);
|
|
2862
|
-
}
|
|
3073
|
+
/* .lb-mix-match__filled-variant \u2014 styled jointly with
|
|
3074
|
+
.lb-bundle-variant-badge above to keep the variant text consistent
|
|
3075
|
+
across mix-match and fixed bundle widgets. */
|
|
2863
3076
|
|
|
2864
3077
|
.lb-mix-match__filled-price {
|
|
2865
3078
|
/* --lb-product-price-display fallback is the "on" branch; Liquid sets 'none' when merchant disables. */
|
|
@@ -3067,8 +3280,8 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
3067
3280
|
|
|
3068
3281
|
.lb-mix-match__modal-product-thumb {
|
|
3069
3282
|
position: relative;
|
|
3070
|
-
width:
|
|
3071
|
-
min-width:
|
|
3283
|
+
width: 60px;
|
|
3284
|
+
min-width: 60px;
|
|
3072
3285
|
/* Modal picker thumbs follow the merchant's pickerThumbnailRatio \u2014
|
|
3073
3286
|
independent from the main widget's thumbnailRatio so a merchant can
|
|
3074
3287
|
e.g. show tall picker thumbs with square main thumbs. */
|
|
@@ -3117,7 +3330,7 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
3117
3330
|
font-size: 12px;
|
|
3118
3331
|
line-height: 20px;
|
|
3119
3332
|
color: inherit;
|
|
3120
|
-
margin:
|
|
3333
|
+
margin: 2px 0 0;
|
|
3121
3334
|
}
|
|
3122
3335
|
|
|
3123
3336
|
.lb-mix-match__modal-product-unit-price {
|
|
@@ -3156,6 +3369,86 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
3156
3369
|
outline-offset: 2px;
|
|
3157
3370
|
}
|
|
3158
3371
|
|
|
3372
|
+
/* Per-pick quantity stepper inside the picker modal product row.
|
|
3373
|
+
Three flex cells (\u2212 / number / +) sharing a single outer border \u2014
|
|
3374
|
+
the same picker-variant border vars the variant select uses, so
|
|
3375
|
+
the two pieces visually agree even when the merchant tweaks
|
|
3376
|
+
--lb-picker-variant-border-width / -color / -radius. The cell
|
|
3377
|
+
dividers come from a 1px border on the centre value rather than
|
|
3378
|
+
per-button borders, so the rounded outer corners stay clean.
|
|
3379
|
+
Mirrors extensions/bundle-theme/assets/bundle-mix-match.css so
|
|
3380
|
+
the headless web component matches the Liquid theme widget. */
|
|
3381
|
+
/* Quantity label + stepper sit together in a .lb-bundle-variant-option-group
|
|
3382
|
+
wrapper so the label-to-stepper gap inherits the same 2px the variant
|
|
3383
|
+
pickers use. The wrapper carries the margin-top that spaces the qty
|
|
3384
|
+
group from the unit-price / low-stock-badge above; the stepper itself
|
|
3385
|
+
has no top margin so the group can be repositioned without coupling. */
|
|
3386
|
+
.lb-mix-match__qty-stepper-group {
|
|
3387
|
+
margin-top: 8px;
|
|
3388
|
+
/* Group is a flex column; without an explicit width it stretches to
|
|
3389
|
+
fill the info column so the \u2212/value/+ cells get pulled apart. Pin
|
|
3390
|
+
to fit-content so the wrapper hugs the stepper's natural width. */
|
|
3391
|
+
width: fit-content;
|
|
3392
|
+
}
|
|
3393
|
+
|
|
3394
|
+
.lb-mix-match__qty-stepper {
|
|
3395
|
+
display: inline-flex;
|
|
3396
|
+
align-items: stretch;
|
|
3397
|
+
flex-shrink: 0;
|
|
3398
|
+
height: 32px;
|
|
3399
|
+
border: var(--lb-picker-variant-border-width) solid var(--lb-picker-variant-border-color);
|
|
3400
|
+
border-radius: var(--lb-picker-variant-radius);
|
|
3401
|
+
background-color: var(--lb-picker-bg);
|
|
3402
|
+
overflow: hidden;
|
|
3403
|
+
box-sizing: border-box;
|
|
3404
|
+
}
|
|
3405
|
+
|
|
3406
|
+
.lb-mix-match__qty-stepper-button {
|
|
3407
|
+
appearance: none;
|
|
3408
|
+
-webkit-appearance: none;
|
|
3409
|
+
background: transparent;
|
|
3410
|
+
border: none;
|
|
3411
|
+
margin: 0;
|
|
3412
|
+
padding: 0;
|
|
3413
|
+
width: 28px;
|
|
3414
|
+
font-family: inherit;
|
|
3415
|
+
font-size: 16px;
|
|
3416
|
+
font-weight: 500;
|
|
3417
|
+
line-height: 1;
|
|
3418
|
+
color: var(--lb-picker-text);
|
|
3419
|
+
cursor: pointer;
|
|
3420
|
+
display: inline-flex;
|
|
3421
|
+
align-items: center;
|
|
3422
|
+
justify-content: center;
|
|
3423
|
+
}
|
|
3424
|
+
|
|
3425
|
+
.lb-mix-match__qty-stepper-button:hover:not(:disabled) {
|
|
3426
|
+
background: color-mix(in srgb, var(--lb-picker-text) 6%, transparent);
|
|
3427
|
+
}
|
|
3428
|
+
|
|
3429
|
+
.lb-mix-match__qty-stepper-button:focus-visible {
|
|
3430
|
+
outline: 2px solid var(--lb-primary-color);
|
|
3431
|
+
outline-offset: -2px;
|
|
3432
|
+
}
|
|
3433
|
+
|
|
3434
|
+
.lb-mix-match__qty-stepper-button:disabled {
|
|
3435
|
+
opacity: 0.4;
|
|
3436
|
+
cursor: not-allowed;
|
|
3437
|
+
}
|
|
3438
|
+
|
|
3439
|
+
.lb-mix-match__qty-stepper-value {
|
|
3440
|
+
display: inline-flex;
|
|
3441
|
+
align-items: center;
|
|
3442
|
+
justify-content: center;
|
|
3443
|
+
min-width: 36px;
|
|
3444
|
+
padding: 0 8px;
|
|
3445
|
+
font-family: inherit;
|
|
3446
|
+
font-size: 13px;
|
|
3447
|
+
font-weight: 500;
|
|
3448
|
+
color: var(--lb-picker-text);
|
|
3449
|
+
box-sizing: border-box;
|
|
3450
|
+
}
|
|
3451
|
+
|
|
3159
3452
|
.lb-mix-match__modal-add {
|
|
3160
3453
|
padding: 8px 20px;
|
|
3161
3454
|
background: var(--lb-picker-add-bg);
|
|
@@ -3834,7 +4127,7 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
3834
4127
|
const controller = new AbortController();
|
|
3835
4128
|
this.abortController = controller;
|
|
3836
4129
|
this.renderLoading();
|
|
3837
|
-
const client = (0,
|
|
4130
|
+
const client = (0, import_core8.createStorefrontClient)({
|
|
3838
4131
|
shopDomain: this.shopDomain,
|
|
3839
4132
|
accessToken: this.storefrontToken
|
|
3840
4133
|
});
|
|
@@ -3859,7 +4152,7 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
3859
4152
|
handle
|
|
3860
4153
|
);
|
|
3861
4154
|
}
|
|
3862
|
-
const cssPromise = client.query(
|
|
4155
|
+
const cssPromise = client.query(import_core8.SHOP_CUSTOM_CSS_QUERY, void 0, {
|
|
3863
4156
|
signal: controller.signal
|
|
3864
4157
|
}).catch(() => null);
|
|
3865
4158
|
await bundlePromise;
|
|
@@ -3871,8 +4164,8 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
3871
4164
|
}
|
|
3872
4165
|
const css = await cssPromise;
|
|
3873
4166
|
if (css?.shop?.metafield?.value) {
|
|
3874
|
-
(0,
|
|
3875
|
-
const sanitized = (0,
|
|
4167
|
+
(0, import_core8.injectCustomCss)(this.shopDomain, css.shop.metafield.value);
|
|
4168
|
+
const sanitized = (0, import_core8.sanitizeCustomCss)(css.shop.metafield.value);
|
|
3876
4169
|
if (sanitized.ok) this.shopCustomCss = sanitized.css;
|
|
3877
4170
|
}
|
|
3878
4171
|
await this.applyABVariants();
|
|
@@ -3900,14 +4193,14 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
3900
4193
|
this.bundles.map(async (bundle) => {
|
|
3901
4194
|
if (!bundle.abTestId || !bundle.abVariantB) return bundle;
|
|
3902
4195
|
try {
|
|
3903
|
-
const assignment = await (0,
|
|
4196
|
+
const assignment = await (0, import_core8.getABTestAssignment)(
|
|
3904
4197
|
this.appUrl,
|
|
3905
4198
|
this.shopDomain,
|
|
3906
4199
|
bundle.abTestId,
|
|
3907
4200
|
bundle.id
|
|
3908
4201
|
);
|
|
3909
4202
|
if (assignment?.variant === "B") {
|
|
3910
|
-
return (0,
|
|
4203
|
+
return (0, import_core8.applyABVariantB)(bundle);
|
|
3911
4204
|
}
|
|
3912
4205
|
} catch (err) {
|
|
3913
4206
|
console.warn(
|
|
@@ -3922,7 +4215,7 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
3922
4215
|
}
|
|
3923
4216
|
async fetchSingleBundle(client, signal) {
|
|
3924
4217
|
const data = await client.query(
|
|
3925
|
-
|
|
4218
|
+
import_core8.BUNDLE_METAOBJECT_QUERY,
|
|
3926
4219
|
{ id: this.bundleGid },
|
|
3927
4220
|
{ signal }
|
|
3928
4221
|
);
|
|
@@ -3930,7 +4223,7 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
3930
4223
|
this.bundles = [];
|
|
3931
4224
|
return;
|
|
3932
4225
|
}
|
|
3933
|
-
const parsed = (0,
|
|
4226
|
+
const parsed = (0, import_core8.parseMetaobjectBundle)(
|
|
3934
4227
|
data.metaobject.id,
|
|
3935
4228
|
data.metaobject.fields
|
|
3936
4229
|
);
|
|
@@ -3938,7 +4231,7 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
3938
4231
|
}
|
|
3939
4232
|
async fetchProductBundles(client, signal, productHandle) {
|
|
3940
4233
|
const data = await client.query(
|
|
3941
|
-
|
|
4234
|
+
import_core8.BUNDLES_FOR_PRODUCT_QUERY,
|
|
3942
4235
|
{ handle: productHandle },
|
|
3943
4236
|
{ signal }
|
|
3944
4237
|
);
|
|
@@ -3949,7 +4242,7 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
3949
4242
|
const refs = data.product.metafield?.references?.nodes ?? [];
|
|
3950
4243
|
const bundles = [];
|
|
3951
4244
|
for (const ref of refs) {
|
|
3952
|
-
const parsed = (0,
|
|
4245
|
+
const parsed = (0, import_core8.parseMetaobjectBundle)(ref.id, ref.fields);
|
|
3953
4246
|
if (parsed) bundles.push(parsed);
|
|
3954
4247
|
}
|
|
3955
4248
|
this.bundles = bundles;
|
|
@@ -3982,7 +4275,7 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
3982
4275
|
*/
|
|
3983
4276
|
async defaultAddToCart(lines) {
|
|
3984
4277
|
if (typeof window === "undefined") return;
|
|
3985
|
-
const client = (0,
|
|
4278
|
+
const client = (0, import_core8.createStorefrontClient)({
|
|
3986
4279
|
shopDomain: this.shopDomain,
|
|
3987
4280
|
accessToken: this.storefrontToken
|
|
3988
4281
|
});
|
|
@@ -3993,7 +4286,7 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
3993
4286
|
let checkoutUrl = null;
|
|
3994
4287
|
if (existingCartId) {
|
|
3995
4288
|
const res = await client.query(
|
|
3996
|
-
|
|
4289
|
+
import_core8.CART_LINES_ADD_MUTATION,
|
|
3997
4290
|
{ cartId: existingCartId, lines }
|
|
3998
4291
|
);
|
|
3999
4292
|
const payload = res.cartLinesAdd;
|
|
@@ -4005,7 +4298,7 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
4005
4298
|
}
|
|
4006
4299
|
if (!checkoutUrl) {
|
|
4007
4300
|
const res = await client.query(
|
|
4008
|
-
|
|
4301
|
+
import_core8.CART_CREATE_MUTATION,
|
|
4009
4302
|
{ input: { lines } }
|
|
4010
4303
|
);
|
|
4011
4304
|
const payload = res.cartCreate;
|
|
@@ -4051,7 +4344,7 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
4051
4344
|
const price = variant ? parseFloat(variant.price.amount) : 0;
|
|
4052
4345
|
return sum + price * line.quantity;
|
|
4053
4346
|
}, 0);
|
|
4054
|
-
(0,
|
|
4347
|
+
(0, import_core8.reportAddToCart)(
|
|
4055
4348
|
{ shopDomain: this.shopDomain, appUrl: this.appUrl },
|
|
4056
4349
|
{
|
|
4057
4350
|
bundleGid: bundle.id,
|
|
@@ -4088,7 +4381,7 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
4088
4381
|
container.setAttribute("aria-label", bundle.title);
|
|
4089
4382
|
container.setAttribute("data-bundle-type", bundle.bundleType);
|
|
4090
4383
|
container.setAttribute("data-bundle-gid", bundle.id);
|
|
4091
|
-
(0,
|
|
4384
|
+
(0, import_core9.applyWidgetConfigVars)(container, bundle.widgetConfig);
|
|
4092
4385
|
this.renderCleanups.push(trackInputMode(container));
|
|
4093
4386
|
const dispatch = (lines) => this.handleAddToCart(bundle, lines);
|
|
4094
4387
|
const registerCleanup = (fn) => this.renderCleanups.push(fn);
|
|
@@ -4124,8 +4417,8 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
4124
4417
|
}
|
|
4125
4418
|
setupImpressionFor(bundle, element) {
|
|
4126
4419
|
if (!this.analyticsEnabled || !this.appUrl) return;
|
|
4127
|
-
const cleanup = (0,
|
|
4128
|
-
(0,
|
|
4420
|
+
const cleanup = (0, import_core8.observeImpression)(element, () => {
|
|
4421
|
+
(0, import_core8.reportImpression)(
|
|
4129
4422
|
{ shopDomain: this.shopDomain, appUrl: this.appUrl },
|
|
4130
4423
|
{
|
|
4131
4424
|
bundleGid: bundle.id,
|