@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.js
CHANGED
|
@@ -18,7 +18,9 @@ import {
|
|
|
18
18
|
|
|
19
19
|
// src/renderers/fixed.ts
|
|
20
20
|
import {
|
|
21
|
-
resolveBundleQty
|
|
21
|
+
resolveBundleQty,
|
|
22
|
+
isVariantFulfillable,
|
|
23
|
+
shouldShowLowStockBadge
|
|
22
24
|
} from "@lime-bundles/core";
|
|
23
25
|
|
|
24
26
|
// src/dropdown/bind-dropdown.ts
|
|
@@ -67,6 +69,20 @@ function firstEnabled(opts) {
|
|
|
67
69
|
for (let i = 0; i < opts.length; i++) if (!opts[i].disabled) return i;
|
|
68
70
|
return -1;
|
|
69
71
|
}
|
|
72
|
+
function findScrollableAncestor(el2) {
|
|
73
|
+
const win = el2.ownerDocument?.defaultView;
|
|
74
|
+
if (!win) return null;
|
|
75
|
+
let cur = el2.parentElement;
|
|
76
|
+
while (cur && cur !== el2.ownerDocument.body) {
|
|
77
|
+
const style = win.getComputedStyle(cur);
|
|
78
|
+
const overflowY = style.overflowY;
|
|
79
|
+
if (overflowY === "auto" || overflowY === "scroll" || overflowY === "hidden") {
|
|
80
|
+
return cur;
|
|
81
|
+
}
|
|
82
|
+
cur = cur.parentElement;
|
|
83
|
+
}
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
70
86
|
var VARIANT_SELECT_CLASSES = [
|
|
71
87
|
"lb-bundle-variant-select",
|
|
72
88
|
"lb-mix-match__variant-select"
|
|
@@ -172,6 +188,11 @@ function bindDropdown(select) {
|
|
|
172
188
|
if (rect.width === 0) return false;
|
|
173
189
|
const visibleCount = Math.min(optionEls.length || 1, MAX_VISIBLE_ITEMS);
|
|
174
190
|
const desiredHeight = visibleCount * ITEM_HEIGHT_PX + LIST_PAD_Y;
|
|
191
|
+
const scrollable = listbox.hasAttribute("data-lb-dropdown-portal") ? null : findScrollableAncestor(trigger);
|
|
192
|
+
const clip = scrollable ? (() => {
|
|
193
|
+
const r = scrollable.getBoundingClientRect();
|
|
194
|
+
return { top: r.top, bottom: r.bottom };
|
|
195
|
+
})() : void 0;
|
|
175
196
|
const result = computePosition({
|
|
176
197
|
trigger: {
|
|
177
198
|
top: rect.top,
|
|
@@ -180,7 +201,8 @@ function bindDropdown(select) {
|
|
|
180
201
|
width: rect.width
|
|
181
202
|
},
|
|
182
203
|
viewportHeight: window.innerHeight,
|
|
183
|
-
desiredHeight
|
|
204
|
+
desiredHeight,
|
|
205
|
+
clip
|
|
184
206
|
});
|
|
185
207
|
listbox.setAttribute("data-placement", result.placement);
|
|
186
208
|
listbox.style.maxHeight = `${result.maxHeight}px`;
|
|
@@ -534,9 +556,18 @@ function renderFixedBundle(container, bundle, onAddToCart, onCleanup) {
|
|
|
534
556
|
const list = el("div", "lb-fixed__products");
|
|
535
557
|
const rowHandles = [];
|
|
536
558
|
rows.forEach((rowState) => {
|
|
537
|
-
const handle = renderProductRow(
|
|
538
|
-
|
|
539
|
-
|
|
559
|
+
const handle = renderProductRow(
|
|
560
|
+
rowState,
|
|
561
|
+
currency,
|
|
562
|
+
qtyFor,
|
|
563
|
+
{
|
|
564
|
+
enabled: wc.showLowStockBadge,
|
|
565
|
+
threshold: wc.lowStockThreshold
|
|
566
|
+
},
|
|
567
|
+
() => {
|
|
568
|
+
updatePricing();
|
|
569
|
+
}
|
|
570
|
+
);
|
|
540
571
|
rowHandles.push(handle);
|
|
541
572
|
list.appendChild(handle.el);
|
|
542
573
|
});
|
|
@@ -592,7 +623,9 @@ function renderFixedBundle(container, bundle, onAddToCart, onCleanup) {
|
|
|
592
623
|
function buildRowState(bundle, product, productIndex) {
|
|
593
624
|
const selectedVariantIds = bundle.selectedVariantIds?.[productIndex] ?? null;
|
|
594
625
|
const merchantScoped = selectedVariantIds && selectedVariantIds.length > 0 ? product.variants.nodes.filter((v) => selectedVariantIds.includes(v.id)) : product.variants.nodes;
|
|
595
|
-
const firstInStock = merchantScoped.find(
|
|
626
|
+
const firstInStock = merchantScoped.find(
|
|
627
|
+
(v) => isVariantFulfillable(v, resolveBundleQty(bundle, product.id, v.id))
|
|
628
|
+
) ?? null;
|
|
596
629
|
const eligibleVariants = merchantScoped;
|
|
597
630
|
const isOos = !firstInStock;
|
|
598
631
|
const selected = firstInStock ?? merchantScoped[0] ?? null;
|
|
@@ -656,7 +689,7 @@ function deriveHeaderBadge(bundle, totalCents, saleCents, currency) {
|
|
|
656
689
|
}
|
|
657
690
|
return `-${formatCents(savings, currency)}`;
|
|
658
691
|
}
|
|
659
|
-
function renderProductRow(state, currency, qtyFor, onVariantChange) {
|
|
692
|
+
function renderProductRow(state, currency, qtyFor, lowStock, onVariantChange) {
|
|
660
693
|
const rowEl = el(
|
|
661
694
|
"div",
|
|
662
695
|
state.isOos ? "lb-bundle-product-row lb-bundle-product-row--oos" : "lb-bundle-product-row",
|
|
@@ -703,6 +736,12 @@ function renderProductRow(state, currency, qtyFor, onVariantChange) {
|
|
|
703
736
|
oosLabel.textContent = "Out of stock";
|
|
704
737
|
info.appendChild(oosLabel);
|
|
705
738
|
} else if (state.selected) {
|
|
739
|
+
const isSinglePinnedVariant = state.eligibleVariants.length === 1 && state.product.variants.nodes.length > 1;
|
|
740
|
+
if (isSinglePinnedVariant) {
|
|
741
|
+
const badge = el("span", "lb-bundle-variant-badge");
|
|
742
|
+
badge.textContent = state.eligibleVariants[0].title;
|
|
743
|
+
info.appendChild(badge);
|
|
744
|
+
}
|
|
706
745
|
const prices = el("span", "lb-bundle-product-prices");
|
|
707
746
|
const compare = el("span", "lb-bundle-product-compare-price", {
|
|
708
747
|
"data-product-compare-price": ""
|
|
@@ -718,6 +757,11 @@ function renderProductRow(state, currency, qtyFor, onVariantChange) {
|
|
|
718
757
|
});
|
|
719
758
|
unitPriceEl.setAttribute("hidden", "");
|
|
720
759
|
info.appendChild(unitPriceEl);
|
|
760
|
+
const lowStockEl = el("span", "lb-bundle-low-stock-badge", {
|
|
761
|
+
"data-low-stock-badge": ""
|
|
762
|
+
});
|
|
763
|
+
lowStockEl.setAttribute("hidden", "");
|
|
764
|
+
info.appendChild(lowStockEl);
|
|
721
765
|
const applyVariantToRow = (variant) => {
|
|
722
766
|
const unit = parseCents(variant.price.amount);
|
|
723
767
|
priceEl.textContent = formatCents(unit, currency);
|
|
@@ -752,6 +796,18 @@ function renderProductRow(state, currency, qtyFor, onVariantChange) {
|
|
|
752
796
|
});
|
|
753
797
|
thumbImg.alt = nextImage.altText ?? state.product.title;
|
|
754
798
|
}
|
|
799
|
+
const required = qtyFor(state.product.id, variant.id);
|
|
800
|
+
if (shouldShowLowStockBadge(
|
|
801
|
+
variant,
|
|
802
|
+
required,
|
|
803
|
+
lowStock.threshold,
|
|
804
|
+
lowStock.enabled
|
|
805
|
+
)) {
|
|
806
|
+
lowStockEl.textContent = `Only ${variant.quantityAvailable} left`;
|
|
807
|
+
lowStockEl.removeAttribute("hidden");
|
|
808
|
+
} else {
|
|
809
|
+
lowStockEl.setAttribute("hidden", "");
|
|
810
|
+
}
|
|
755
811
|
};
|
|
756
812
|
applyVariantToRow(state.selected);
|
|
757
813
|
if (state.eligibleVariants.length > 1) {
|
|
@@ -770,7 +826,7 @@ function renderProductRow(state, currency, qtyFor, onVariantChange) {
|
|
|
770
826
|
});
|
|
771
827
|
};
|
|
772
828
|
const isValueAvailable = (optionIndex, value, selected) => state.eligibleVariants.some((v) => {
|
|
773
|
-
if (!v.
|
|
829
|
+
if (!isVariantFulfillable(v, qtyFor(state.product.id, v.id))) return false;
|
|
774
830
|
if (v.selectedOptions[optionIndex]?.value !== value) return false;
|
|
775
831
|
return v.selectedOptions.every(
|
|
776
832
|
(o, i) => i === optionIndex || o.value === selected[i]
|
|
@@ -834,10 +890,6 @@ function renderProductRow(state, currency, qtyFor, onVariantChange) {
|
|
|
834
890
|
if (state.selected) {
|
|
835
891
|
recomputeDisabled(state.selected.selectedOptions.map((o) => o.value));
|
|
836
892
|
}
|
|
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
893
|
}
|
|
842
894
|
}
|
|
843
895
|
rowEl.appendChild(info);
|
|
@@ -922,7 +974,12 @@ function computeSale(totalCents, discount, rows) {
|
|
|
922
974
|
}
|
|
923
975
|
|
|
924
976
|
// src/renderers/mix-match.ts
|
|
925
|
-
import {
|
|
977
|
+
import {
|
|
978
|
+
DEFAULT_PRODUCT_RULE,
|
|
979
|
+
isVariantFulfillable as isVariantFulfillable2,
|
|
980
|
+
maxAddableQuantity,
|
|
981
|
+
shouldShowLowStockBadge as shouldShowLowStockBadge2
|
|
982
|
+
} from "@lime-bundles/core";
|
|
926
983
|
var PLACEHOLDER_THUMB_SVG2 = `
|
|
927
984
|
<svg class="lb-bundle-placeholder-icon" viewBox="0 0 28 28" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false">
|
|
928
985
|
<rect x="4" y="4" width="20" height="20" rx="3"></rect>
|
|
@@ -943,18 +1000,36 @@ var SEARCH_CLEAR_ICON_SVG = `
|
|
|
943
1000
|
<svg width="16" height="16" viewBox="0 0 20 20" fill="currentColor" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
944
1001
|
<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
1002
|
</svg>`;
|
|
1003
|
+
var STEPPER_MINUS_SVG = `
|
|
1004
|
+
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
1005
|
+
<line x1="3" y1="7" x2="11" y2="7" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
|
1006
|
+
</svg>`;
|
|
1007
|
+
var STEPPER_PLUS_SVG = `
|
|
1008
|
+
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
1009
|
+
<line x1="7" y1="3" x2="7" y2="11" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
|
1010
|
+
<line x1="3" y1="7" x2="11" y2="7" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
|
1011
|
+
</svg>`;
|
|
1012
|
+
function ruleFor(bundle, productId) {
|
|
1013
|
+
return bundle.productRules[productId] ?? DEFAULT_PRODUCT_RULE;
|
|
1014
|
+
}
|
|
1015
|
+
function alreadyInBundleFor(selections, productId, variantId) {
|
|
1016
|
+
let sum = 0;
|
|
1017
|
+
for (const s of selections) {
|
|
1018
|
+
if (s.productId === productId && s.variantId === variantId) sum += s.quantity;
|
|
1019
|
+
}
|
|
1020
|
+
return sum;
|
|
1021
|
+
}
|
|
946
1022
|
function renderMixMatchBundle(container, bundle, onAddToCart, onCleanup) {
|
|
947
1023
|
const wc = bundle.widgetConfig;
|
|
948
1024
|
const currency = bundle.products[0]?.priceRange.minVariantPrice.currencyCode ?? "USD";
|
|
949
1025
|
const requiredQty = bundle.minQuantity ?? 1;
|
|
950
|
-
const
|
|
1026
|
+
const showQtySelector = wc.mixMatchShowQuantitySelector !== false;
|
|
951
1027
|
const eligible = buildEligibleProducts(bundle, wc.outOfStockBehavior);
|
|
952
1028
|
const inStockCount = eligible.filter((e) => !e.isOos).length;
|
|
953
1029
|
if (inStockCount < requiredQty) return;
|
|
954
1030
|
const selections = [];
|
|
955
1031
|
const root = el("div", "lb-mix-match", {
|
|
956
|
-
"data-required-quantity": String(requiredQty)
|
|
957
|
-
"data-max-quantity": String(maxQty)
|
|
1032
|
+
"data-required-quantity": String(requiredQty)
|
|
958
1033
|
});
|
|
959
1034
|
const header = renderHeader2(bundle);
|
|
960
1035
|
root.appendChild(header);
|
|
@@ -988,24 +1063,17 @@ function renderMixMatchBundle(container, bundle, onAddToCart, onCleanup) {
|
|
|
988
1063
|
root.appendChild(placeholder);
|
|
989
1064
|
const modal = renderModal(bundle, eligible, currency, {
|
|
990
1065
|
showSearch: wc.showSearch,
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
1066
|
+
showQtySelector,
|
|
1067
|
+
selections,
|
|
1068
|
+
onAdd: (product, variant, quantity) => addSelection(product, variant, quantity),
|
|
1069
|
+
isComplete: () => selections.length >= requiredQty
|
|
994
1070
|
});
|
|
995
1071
|
root.appendChild(modal.el);
|
|
996
1072
|
const cta = buildCtaButton(`Select ${requiredQty} items to unlock`);
|
|
997
1073
|
cta.disabled = true;
|
|
998
1074
|
cta.addEventListener("click", () => {
|
|
999
1075
|
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);
|
|
1076
|
+
onAddToCart(buildCartLines(selections, bundle));
|
|
1009
1077
|
});
|
|
1010
1078
|
root.appendChild(cta);
|
|
1011
1079
|
root.appendChild(
|
|
@@ -1019,28 +1087,17 @@ function renderMixMatchBundle(container, bundle, onAddToCart, onCleanup) {
|
|
|
1019
1087
|
);
|
|
1020
1088
|
container.appendChild(root);
|
|
1021
1089
|
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: parseCents(firstVariant.price.amount),
|
|
1032
|
-
compareCents: firstVariant.compareAtPrice ? parseCents(firstVariant.compareAtPrice.amount) : null,
|
|
1033
|
-
unitPriceLabel: formatUnitPrice(
|
|
1034
|
-
firstVariant.unitPrice,
|
|
1035
|
-
firstVariant.unitPriceMeasurement,
|
|
1036
|
-
currency
|
|
1037
|
-
),
|
|
1038
|
-
quantity: resolveBundleQty2(bundle, firstEligible.product.id, firstVariant.id)
|
|
1039
|
-
});
|
|
1040
|
-
}
|
|
1041
1090
|
afterMutation();
|
|
1042
|
-
function addSelection(product, variant) {
|
|
1043
|
-
if (selections.length >=
|
|
1091
|
+
function addSelection(product, variant, quantity) {
|
|
1092
|
+
if (selections.length >= requiredQty) return;
|
|
1093
|
+
const rule = ruleFor(bundle, product.id);
|
|
1094
|
+
const cap = maxAddableQuantity(
|
|
1095
|
+
variant,
|
|
1096
|
+
rule.max,
|
|
1097
|
+
alreadyInBundleFor(selections, product.id, variant.id)
|
|
1098
|
+
);
|
|
1099
|
+
if (cap < rule.min) return;
|
|
1100
|
+
const clamped = Math.max(rule.min, Math.min(quantity, cap));
|
|
1044
1101
|
selections.push({
|
|
1045
1102
|
productId: product.id,
|
|
1046
1103
|
productTitle: product.title,
|
|
@@ -1054,18 +1111,10 @@ function renderMixMatchBundle(container, bundle, onAddToCart, onCleanup) {
|
|
|
1054
1111
|
variant.unitPriceMeasurement,
|
|
1055
1112
|
currency
|
|
1056
1113
|
),
|
|
1057
|
-
quantity:
|
|
1114
|
+
quantity: clamped
|
|
1058
1115
|
});
|
|
1059
1116
|
afterMutation();
|
|
1060
1117
|
}
|
|
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
1118
|
function removeSlotAt(index) {
|
|
1070
1119
|
if (index < 0 || index >= selections.length) return;
|
|
1071
1120
|
selections.splice(index, 1);
|
|
@@ -1107,6 +1156,26 @@ function renderMixMatchBundle(container, bundle, onAddToCart, onCleanup) {
|
|
|
1107
1156
|
}
|
|
1108
1157
|
}
|
|
1109
1158
|
}
|
|
1159
|
+
function buildCartLines(selections, bundle) {
|
|
1160
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
1161
|
+
for (const s of selections) {
|
|
1162
|
+
const key = `${s.productId}::${s.variantId}`;
|
|
1163
|
+
const existing = grouped.get(key);
|
|
1164
|
+
if (existing) {
|
|
1165
|
+
existing.quantity += s.quantity;
|
|
1166
|
+
} else {
|
|
1167
|
+
grouped.set(key, { variantId: s.variantId, quantity: s.quantity });
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
return Array.from(grouped.values()).map((line) => ({
|
|
1171
|
+
merchandiseId: line.variantId,
|
|
1172
|
+
quantity: line.quantity,
|
|
1173
|
+
attributes: [
|
|
1174
|
+
{ key: "_lime_bundle_gid", value: bundle.id },
|
|
1175
|
+
{ key: "_lime_bundle_type", value: bundle.bundleType }
|
|
1176
|
+
]
|
|
1177
|
+
}));
|
|
1178
|
+
}
|
|
1110
1179
|
function renderHeader2(bundle) {
|
|
1111
1180
|
const wc = bundle.widgetConfig;
|
|
1112
1181
|
const header = el("div", "lb-bundle-header");
|
|
@@ -1222,7 +1291,7 @@ function renderFilledSlot(selection, index, currency, onRemove) {
|
|
|
1222
1291
|
thumb.insertAdjacentHTML("beforeend", PLACEHOLDER_THUMB_SVG2);
|
|
1223
1292
|
}
|
|
1224
1293
|
const qtyBadge = el("span", "lb-bundle-qty-badge");
|
|
1225
|
-
qtyBadge.textContent =
|
|
1294
|
+
qtyBadge.textContent = `\xD7${selection.quantity}`;
|
|
1226
1295
|
thumb.appendChild(qtyBadge);
|
|
1227
1296
|
slot.appendChild(thumb);
|
|
1228
1297
|
const info = el("div", "lb-mix-match__filled-info");
|
|
@@ -1411,8 +1480,9 @@ function renderModal(bundle, eligible, currency, handlers) {
|
|
|
1411
1480
|
rowsBuilt = true;
|
|
1412
1481
|
list.innerHTML = "";
|
|
1413
1482
|
eligible.forEach((ep) => {
|
|
1483
|
+
const rule = ruleFor(bundle, ep.product.id);
|
|
1414
1484
|
const availableVariants = ep.variants;
|
|
1415
|
-
const firstAvailVariant = ep.variants.find((v) => v.
|
|
1485
|
+
const firstAvailVariant = ep.variants.find((v) => isVariantFulfillable2(v, rule.min)) ?? ep.firstAvailableVariant ?? ep.variants[0];
|
|
1416
1486
|
if (!firstAvailVariant) return;
|
|
1417
1487
|
let currentVariant = firstAvailVariant;
|
|
1418
1488
|
const productEl = el(
|
|
@@ -1421,7 +1491,7 @@ function renderModal(bundle, eligible, currency, handlers) {
|
|
|
1421
1491
|
{ "data-product-id": ep.product.id.replace(/^.*\//, "") }
|
|
1422
1492
|
);
|
|
1423
1493
|
const thumb = el("div", "lb-mix-match__modal-product-thumb");
|
|
1424
|
-
const initialThumbVariant = ep.variants.find((v) => v.
|
|
1494
|
+
const initialThumbVariant = ep.variants.find((v) => isVariantFulfillable2(v, rule.min)) ?? ep.variants[0] ?? null;
|
|
1425
1495
|
const initialThumbImage = initialThumbVariant?.image ?? ep.product.featuredImage ?? null;
|
|
1426
1496
|
let thumbImg = null;
|
|
1427
1497
|
if (initialThumbImage) {
|
|
@@ -1440,9 +1510,7 @@ function renderModal(bundle, eligible, currency, handlers) {
|
|
|
1440
1510
|
thumb.insertAdjacentHTML("beforeend", PLACEHOLDER_THUMB_SVG2);
|
|
1441
1511
|
}
|
|
1442
1512
|
const countBadge = el("span", "lb-bundle-qty-badge");
|
|
1443
|
-
countBadge.
|
|
1444
|
-
resolveBundleQty2(bundle, ep.product.id, currentVariant.id)
|
|
1445
|
-
);
|
|
1513
|
+
countBadge.hidden = true;
|
|
1446
1514
|
thumb.appendChild(countBadge);
|
|
1447
1515
|
productEl.appendChild(thumb);
|
|
1448
1516
|
const info = el("div", "lb-mix-match__modal-product-info");
|
|
@@ -1450,10 +1518,7 @@ function renderModal(bundle, eligible, currency, handlers) {
|
|
|
1450
1518
|
title.textContent = ep.product.title;
|
|
1451
1519
|
info.appendChild(title);
|
|
1452
1520
|
const price = el("p", "lb-mix-match__modal-product-price");
|
|
1453
|
-
price.textContent = formatCents(
|
|
1454
|
-
parseCents(currentVariant.price.amount) * resolveBundleQty2(bundle, ep.product.id, currentVariant.id),
|
|
1455
|
-
currency
|
|
1456
|
-
);
|
|
1521
|
+
price.textContent = formatCents(parseCents(currentVariant.price.amount), currency);
|
|
1457
1522
|
info.appendChild(price);
|
|
1458
1523
|
const unitPrice = el(
|
|
1459
1524
|
"p",
|
|
@@ -1470,11 +1535,59 @@ function renderModal(bundle, eligible, currency, handlers) {
|
|
|
1470
1535
|
unitPrice.hidden = true;
|
|
1471
1536
|
}
|
|
1472
1537
|
info.appendChild(unitPrice);
|
|
1538
|
+
const lowStockBadge = el("span", "lb-bundle-low-stock-badge", {
|
|
1539
|
+
"data-low-stock-badge": ""
|
|
1540
|
+
});
|
|
1541
|
+
lowStockBadge.hidden = true;
|
|
1542
|
+
info.appendChild(lowStockBadge);
|
|
1543
|
+
const refreshLowStockBadge = (variant) => {
|
|
1544
|
+
if (shouldShowLowStockBadge2(
|
|
1545
|
+
variant,
|
|
1546
|
+
rule.min,
|
|
1547
|
+
bundle.widgetConfig.lowStockThreshold,
|
|
1548
|
+
bundle.widgetConfig.showLowStockBadge
|
|
1549
|
+
)) {
|
|
1550
|
+
lowStockBadge.textContent = `Only ${variant.quantityAvailable} left`;
|
|
1551
|
+
lowStockBadge.hidden = false;
|
|
1552
|
+
} else {
|
|
1553
|
+
lowStockBadge.hidden = true;
|
|
1554
|
+
}
|
|
1555
|
+
};
|
|
1556
|
+
refreshLowStockBadge(currentVariant);
|
|
1557
|
+
const computeStepperBounds = () => {
|
|
1558
|
+
const already = alreadyInBundleFor(
|
|
1559
|
+
handlers.selections,
|
|
1560
|
+
ep.product.id,
|
|
1561
|
+
currentVariant.id
|
|
1562
|
+
);
|
|
1563
|
+
const cap = maxAddableQuantity(currentVariant, rule.max, already);
|
|
1564
|
+
const max = Math.max(rule.min, cap);
|
|
1565
|
+
return { min: rule.min, max, cap };
|
|
1566
|
+
};
|
|
1567
|
+
let stepper = null;
|
|
1568
|
+
if (!ep.isOos && handlers.showQtySelector) {
|
|
1569
|
+
const qtyGroup = el(
|
|
1570
|
+
"div",
|
|
1571
|
+
"lb-bundle-variant-option-group lb-mix-match__qty-stepper-group"
|
|
1572
|
+
);
|
|
1573
|
+
const qtyLabel = el("span", "lb-bundle-variant-option-label");
|
|
1574
|
+
qtyLabel.textContent = "Quantity";
|
|
1575
|
+
qtyGroup.appendChild(qtyLabel);
|
|
1576
|
+
stepper = renderQtyStepper({
|
|
1577
|
+
initial: rule.min,
|
|
1578
|
+
getBounds: () => {
|
|
1579
|
+
const { min, max } = computeStepperBounds();
|
|
1580
|
+
return { min, max };
|
|
1581
|
+
}
|
|
1582
|
+
});
|
|
1583
|
+
qtyGroup.appendChild(stepper.el);
|
|
1584
|
+
info.appendChild(qtyGroup);
|
|
1585
|
+
}
|
|
1473
1586
|
const row = {
|
|
1474
1587
|
el: productEl,
|
|
1475
1588
|
product: ep.product,
|
|
1476
1589
|
variant: firstAvailVariant,
|
|
1477
|
-
|
|
1590
|
+
refreshFromSelections: () => {
|
|
1478
1591
|
}
|
|
1479
1592
|
};
|
|
1480
1593
|
if (availableVariants.length > 1) {
|
|
@@ -1493,7 +1606,7 @@ function renderModal(bundle, eligible, currency, handlers) {
|
|
|
1493
1606
|
});
|
|
1494
1607
|
};
|
|
1495
1608
|
const isValueAvailable = (optionIndex, value, selected) => availableVariants.some((v) => {
|
|
1496
|
-
if (!v.
|
|
1609
|
+
if (!isVariantFulfillable2(v, rule.min)) return false;
|
|
1497
1610
|
if (v.selectedOptions[optionIndex]?.value !== value) return false;
|
|
1498
1611
|
return v.selectedOptions.every(
|
|
1499
1612
|
(o, i) => i === optionIndex || o.value === selected[i]
|
|
@@ -1518,15 +1631,7 @@ function renderModal(bundle, eligible, currency, handlers) {
|
|
|
1518
1631
|
}
|
|
1519
1632
|
currentVariant = next;
|
|
1520
1633
|
row.variant = next;
|
|
1521
|
-
|
|
1522
|
-
bundle,
|
|
1523
|
-
ep.product.id,
|
|
1524
|
-
currentVariant.id
|
|
1525
|
-
);
|
|
1526
|
-
price.textContent = formatCents(
|
|
1527
|
-
parseCents(currentVariant.price.amount) * nextQty,
|
|
1528
|
-
currency
|
|
1529
|
-
);
|
|
1634
|
+
price.textContent = formatCents(parseCents(currentVariant.price.amount), currency);
|
|
1530
1635
|
const nextUnitText = formatUnitPrice(
|
|
1531
1636
|
currentVariant.unitPrice,
|
|
1532
1637
|
currentVariant.unitPriceMeasurement,
|
|
@@ -1549,7 +1654,8 @@ function renderModal(bundle, eligible, currency, handlers) {
|
|
|
1549
1654
|
thumbImg.alt = nextImage.altText ?? ep.product.title;
|
|
1550
1655
|
}
|
|
1551
1656
|
recomputeDisabled(next.selectedOptions.map((o) => o.value));
|
|
1552
|
-
|
|
1657
|
+
refreshLowStockBadge(currentVariant);
|
|
1658
|
+
row.refreshFromSelections();
|
|
1553
1659
|
};
|
|
1554
1660
|
const groupsContainer = el("div", "lb-bundle-variant-option-groups");
|
|
1555
1661
|
optionNames.forEach((name, position) => {
|
|
@@ -1596,25 +1702,45 @@ function renderModal(bundle, eligible, currency, handlers) {
|
|
|
1596
1702
|
info.appendChild(soldOut);
|
|
1597
1703
|
}
|
|
1598
1704
|
productEl.appendChild(info);
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1705
|
+
let addBtn = null;
|
|
1706
|
+
const refreshAddState = () => {
|
|
1707
|
+
if (!addBtn) return;
|
|
1708
|
+
const { cap } = computeStepperBounds();
|
|
1709
|
+
const already = alreadyInBundleFor(
|
|
1710
|
+
handlers.selections,
|
|
1711
|
+
ep.product.id,
|
|
1712
|
+
currentVariant.id
|
|
1602
1713
|
);
|
|
1714
|
+
if (already > 0) {
|
|
1715
|
+
countBadge.textContent = String(already);
|
|
1716
|
+
countBadge.hidden = false;
|
|
1717
|
+
} else {
|
|
1718
|
+
countBadge.hidden = true;
|
|
1719
|
+
}
|
|
1720
|
+
addBtn.disabled = handlers.isComplete() || cap < rule.min;
|
|
1603
1721
|
};
|
|
1604
1722
|
if (!ep.isOos) {
|
|
1605
|
-
const
|
|
1723
|
+
const actions = el("div", "lb-mix-match__modal-product-actions");
|
|
1724
|
+
addBtn = document.createElement("button");
|
|
1606
1725
|
addBtn.type = "button";
|
|
1607
1726
|
addBtn.className = "lb-mix-match__modal-add";
|
|
1608
1727
|
addBtn.textContent = "Add";
|
|
1609
1728
|
addBtn.addEventListener("click", () => {
|
|
1610
|
-
if (
|
|
1611
|
-
handlers.
|
|
1729
|
+
if (!addBtn || addBtn.disabled) return;
|
|
1730
|
+
if (handlers.isComplete()) return;
|
|
1731
|
+
const qty = stepper ? stepper.value() : rule.min;
|
|
1732
|
+
handlers.onAdd(ep.product, currentVariant, qty);
|
|
1733
|
+
if (stepper) stepper.reset(rule.min);
|
|
1612
1734
|
close();
|
|
1613
1735
|
});
|
|
1614
|
-
|
|
1736
|
+
actions.appendChild(addBtn);
|
|
1737
|
+
productEl.appendChild(actions);
|
|
1615
1738
|
}
|
|
1616
|
-
row.
|
|
1617
|
-
|
|
1739
|
+
row.refreshFromSelections = () => {
|
|
1740
|
+
if (stepper) stepper.refresh();
|
|
1741
|
+
refreshAddState();
|
|
1742
|
+
};
|
|
1743
|
+
row.refreshFromSelections();
|
|
1618
1744
|
productRows.push(row);
|
|
1619
1745
|
list.appendChild(productEl);
|
|
1620
1746
|
});
|
|
@@ -1649,7 +1775,7 @@ function renderModal(bundle, eligible, currency, handlers) {
|
|
|
1649
1775
|
let isOpen = false;
|
|
1650
1776
|
function open() {
|
|
1651
1777
|
if (isOpen) return;
|
|
1652
|
-
if (handlers.
|
|
1778
|
+
if (handlers.isComplete()) return;
|
|
1653
1779
|
isOpen = true;
|
|
1654
1780
|
buildRows();
|
|
1655
1781
|
lastFocused = overlay.getRootNode().activeElement;
|
|
@@ -1674,17 +1800,73 @@ function renderModal(bundle, eligible, currency, handlers) {
|
|
|
1674
1800
|
if (e.target === overlay) close();
|
|
1675
1801
|
}
|
|
1676
1802
|
function refreshCounts() {
|
|
1677
|
-
productRows.forEach((r) => r.
|
|
1803
|
+
productRows.forEach((r) => r.refreshFromSelections());
|
|
1678
1804
|
}
|
|
1679
1805
|
return { el: overlay, open, close, refreshCounts };
|
|
1680
1806
|
}
|
|
1807
|
+
function renderQtyStepper(opts) {
|
|
1808
|
+
const wrap = el("div", "lb-mix-match__qty-stepper", {
|
|
1809
|
+
role: "group",
|
|
1810
|
+
"aria-label": "Quantity"
|
|
1811
|
+
});
|
|
1812
|
+
const minus = document.createElement("button");
|
|
1813
|
+
minus.type = "button";
|
|
1814
|
+
minus.className = "lb-mix-match__qty-stepper-button lb-mix-match__qty-stepper-button--minus";
|
|
1815
|
+
minus.setAttribute("aria-label", "Decrease quantity");
|
|
1816
|
+
minus.innerHTML = STEPPER_MINUS_SVG;
|
|
1817
|
+
wrap.appendChild(minus);
|
|
1818
|
+
const valueEl = el("span", "lb-mix-match__qty-stepper-value", {
|
|
1819
|
+
"aria-live": "polite"
|
|
1820
|
+
});
|
|
1821
|
+
wrap.appendChild(valueEl);
|
|
1822
|
+
const plus = document.createElement("button");
|
|
1823
|
+
plus.type = "button";
|
|
1824
|
+
plus.className = "lb-mix-match__qty-stepper-button lb-mix-match__qty-stepper-button--plus";
|
|
1825
|
+
plus.setAttribute("aria-label", "Increase quantity");
|
|
1826
|
+
plus.innerHTML = STEPPER_PLUS_SVG;
|
|
1827
|
+
wrap.appendChild(plus);
|
|
1828
|
+
let current = clamp2(opts.initial, opts.getBounds());
|
|
1829
|
+
function clamp2(n, b) {
|
|
1830
|
+
return Math.max(b.min, Math.min(b.max, n));
|
|
1831
|
+
}
|
|
1832
|
+
function paint() {
|
|
1833
|
+
const b = opts.getBounds();
|
|
1834
|
+
current = clamp2(current, b);
|
|
1835
|
+
valueEl.textContent = String(current);
|
|
1836
|
+
minus.disabled = current <= b.min;
|
|
1837
|
+
plus.disabled = current >= b.max;
|
|
1838
|
+
}
|
|
1839
|
+
minus.addEventListener("click", () => {
|
|
1840
|
+
const b = opts.getBounds();
|
|
1841
|
+
current = clamp2(current - 1, b);
|
|
1842
|
+
paint();
|
|
1843
|
+
});
|
|
1844
|
+
plus.addEventListener("click", () => {
|
|
1845
|
+
const b = opts.getBounds();
|
|
1846
|
+
current = clamp2(current + 1, b);
|
|
1847
|
+
paint();
|
|
1848
|
+
});
|
|
1849
|
+
paint();
|
|
1850
|
+
return {
|
|
1851
|
+
el: wrap,
|
|
1852
|
+
value: () => current,
|
|
1853
|
+
reset(qty) {
|
|
1854
|
+
current = clamp2(qty, opts.getBounds());
|
|
1855
|
+
paint();
|
|
1856
|
+
},
|
|
1857
|
+
refresh: paint
|
|
1858
|
+
};
|
|
1859
|
+
}
|
|
1681
1860
|
function buildEligibleProducts(bundle, oosBehavior) {
|
|
1682
1861
|
const result = [];
|
|
1683
1862
|
const seen = /* @__PURE__ */ new Set();
|
|
1684
1863
|
for (const product of bundle.products) {
|
|
1685
1864
|
if (seen.has(product.id)) continue;
|
|
1686
1865
|
seen.add(product.id);
|
|
1687
|
-
const
|
|
1866
|
+
const rule = ruleFor(bundle, product.id);
|
|
1867
|
+
const available = product.variants.nodes.filter(
|
|
1868
|
+
(v) => isVariantFulfillable2(v, rule.min)
|
|
1869
|
+
);
|
|
1688
1870
|
const isOos = available.length === 0;
|
|
1689
1871
|
if (isOos && oosBehavior === "hide") continue;
|
|
1690
1872
|
result.push({
|
|
@@ -1717,10 +1899,14 @@ function sanitizeId(gid) {
|
|
|
1717
1899
|
}
|
|
1718
1900
|
|
|
1719
1901
|
// src/renderers/volume.ts
|
|
1902
|
+
import { isVariantFulfillable as isVariantFulfillable3, shouldShowLowStockBadge as shouldShowLowStockBadge3 } from "@lime-bundles/core";
|
|
1720
1903
|
function renderVolumeBundle(container, bundle, onAddToCart, onCleanup) {
|
|
1721
1904
|
const wc = bundle.widgetConfig;
|
|
1722
1905
|
const product = bundle.products[0];
|
|
1723
|
-
const
|
|
1906
|
+
const minTierQty = bundle.volumeTiers[0]?.minQuantity ?? 1;
|
|
1907
|
+
const variant = product?.variants.nodes.find(
|
|
1908
|
+
(v) => isVariantFulfillable3(v, minTierQty)
|
|
1909
|
+
);
|
|
1724
1910
|
if (!variant && wc.outOfStockBehavior === "hide") return;
|
|
1725
1911
|
const basePriceCents = variant ? parseCents(variant.price.amount) : 0;
|
|
1726
1912
|
const currency = variant?.price.currencyCode ?? "USD";
|
|
@@ -1819,6 +2005,18 @@ function renderVolumeBundle(container, bundle, onAddToCart, onCleanup) {
|
|
|
1819
2005
|
}
|
|
1820
2006
|
]);
|
|
1821
2007
|
});
|
|
2008
|
+
if (variant && shouldShowLowStockBadge3(
|
|
2009
|
+
variant,
|
|
2010
|
+
minTierQty,
|
|
2011
|
+
wc.lowStockThreshold,
|
|
2012
|
+
wc.showLowStockBadge
|
|
2013
|
+
)) {
|
|
2014
|
+
const lowStock = el("span", "lb-bundle-low-stock-badge", {
|
|
2015
|
+
"data-low-stock-badge": ""
|
|
2016
|
+
});
|
|
2017
|
+
lowStock.textContent = `Only ${variant.quantityAvailable} left`;
|
|
2018
|
+
root.appendChild(lowStock);
|
|
2019
|
+
}
|
|
1822
2020
|
root.appendChild(cta);
|
|
1823
2021
|
root.appendChild(
|
|
1824
2022
|
el("p", "lb-bundle-error", { "data-error": "", "aria-live": "polite" })
|
|
@@ -1972,7 +2170,10 @@ function renderSavingsBar3(resolved, selectedIndex, currency) {
|
|
|
1972
2170
|
}
|
|
1973
2171
|
function renderCta2(bundle, onClick) {
|
|
1974
2172
|
const product = bundle.products[0];
|
|
1975
|
-
const
|
|
2173
|
+
const minTierQty = bundle.volumeTiers[0]?.minQuantity ?? 1;
|
|
2174
|
+
const isAvailable = product?.variants.nodes.some(
|
|
2175
|
+
(v) => isVariantFulfillable3(v, minTierQty)
|
|
2176
|
+
);
|
|
1976
2177
|
const label = isAvailable ? bundle.widgetConfig.cta.ctaText || "Add to cart" : "Sold out";
|
|
1977
2178
|
const button = buildCtaButton(label);
|
|
1978
2179
|
if (!isAvailable) button.disabled = true;
|
|
@@ -2349,15 +2550,19 @@ var BUNDLE_BASE_CSS = `/* Lime Bundles \u2014 shared base styles for all bundle
|
|
|
2349
2550
|
display: none;
|
|
2350
2551
|
}
|
|
2351
2552
|
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2553
|
+
/* Read-only variant text shown below the product title when only one
|
|
2554
|
+
variant is in scope (single-variant product, or merchant pinned a
|
|
2555
|
+
single variant). Same visual treatment as the mix-match filled
|
|
2556
|
+
slot's variant text \u2014 see .lb-mix-match__filled-variant in
|
|
2557
|
+
bundle-mix-match.css. Both share this rule via the comma selector
|
|
2558
|
+
so the storefront UX stays consistent across bundle types. */
|
|
2559
|
+
.lb-bundle-variant-badge,
|
|
2560
|
+
.lb-mix-match__slot--filled .lb-mix-match__filled-variant {
|
|
2561
|
+
display: block;
|
|
2357
2562
|
font-size: 12px;
|
|
2358
|
-
line-height:
|
|
2563
|
+
line-height: 20px;
|
|
2564
|
+
margin-top: 2px;
|
|
2359
2565
|
color: color-mix(in srgb, var(--lb-text) 60%, transparent);
|
|
2360
|
-
margin-top: 8px;
|
|
2361
2566
|
}
|
|
2362
2567
|
|
|
2363
2568
|
/* Per-option variant pickers. Each option's label + select sit inside a
|
|
@@ -2631,6 +2836,24 @@ var BUNDLE_BASE_CSS = `/* Lime Bundles \u2014 shared base styles for all bundle
|
|
|
2631
2836
|
margin-left: auto;
|
|
2632
2837
|
}
|
|
2633
2838
|
|
|
2839
|
+
/* "Only X left" low-stock badge \u2014 appears alongside product/variant
|
|
2840
|
+
info when ProductVariant.quantityAvailable falls at or below
|
|
2841
|
+
wc.lowStockThreshold. Hidden when quantityAvailable is unknown
|
|
2842
|
+
(Storefront token without read_product_inventory). */
|
|
2843
|
+
.lb-bundle-low-stock-badge {
|
|
2844
|
+
display: inline-flex;
|
|
2845
|
+
align-items: center;
|
|
2846
|
+
gap: 4px;
|
|
2847
|
+
padding: 2px 8px;
|
|
2848
|
+
font-size: 11px;
|
|
2849
|
+
font-weight: 600;
|
|
2850
|
+
line-height: 1.4;
|
|
2851
|
+
color: var(--lb-low-stock-text);
|
|
2852
|
+
background-color: var(--lb-low-stock-bg);
|
|
2853
|
+
border-radius: 4px;
|
|
2854
|
+
white-space: nowrap;
|
|
2855
|
+
}
|
|
2856
|
+
|
|
2634
2857
|
/* A/B test: hide save badge until JS swaps the label (prevents flash of default) */
|
|
2635
2858
|
.lb-ab-pending {
|
|
2636
2859
|
visibility: hidden;
|
|
@@ -2854,12 +3077,9 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
2854
3077
|
text-decoration: underline;
|
|
2855
3078
|
}
|
|
2856
3079
|
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
margin-top: 2px;
|
|
2861
|
-
color: color-mix(in srgb, var(--lb-text) 60%, transparent);
|
|
2862
|
-
}
|
|
3080
|
+
/* .lb-mix-match__filled-variant \u2014 styled jointly with
|
|
3081
|
+
.lb-bundle-variant-badge above to keep the variant text consistent
|
|
3082
|
+
across mix-match and fixed bundle widgets. */
|
|
2863
3083
|
|
|
2864
3084
|
.lb-mix-match__filled-price {
|
|
2865
3085
|
/* --lb-product-price-display fallback is the "on" branch; Liquid sets 'none' when merchant disables. */
|
|
@@ -3067,8 +3287,8 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
3067
3287
|
|
|
3068
3288
|
.lb-mix-match__modal-product-thumb {
|
|
3069
3289
|
position: relative;
|
|
3070
|
-
width:
|
|
3071
|
-
min-width:
|
|
3290
|
+
width: 60px;
|
|
3291
|
+
min-width: 60px;
|
|
3072
3292
|
/* Modal picker thumbs follow the merchant's pickerThumbnailRatio \u2014
|
|
3073
3293
|
independent from the main widget's thumbnailRatio so a merchant can
|
|
3074
3294
|
e.g. show tall picker thumbs with square main thumbs. */
|
|
@@ -3117,7 +3337,7 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
3117
3337
|
font-size: 12px;
|
|
3118
3338
|
line-height: 20px;
|
|
3119
3339
|
color: inherit;
|
|
3120
|
-
margin:
|
|
3340
|
+
margin: 2px 0 0;
|
|
3121
3341
|
}
|
|
3122
3342
|
|
|
3123
3343
|
.lb-mix-match__modal-product-unit-price {
|
|
@@ -3156,6 +3376,86 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
3156
3376
|
outline-offset: 2px;
|
|
3157
3377
|
}
|
|
3158
3378
|
|
|
3379
|
+
/* Per-pick quantity stepper inside the picker modal product row.
|
|
3380
|
+
Three flex cells (\u2212 / number / +) sharing a single outer border \u2014
|
|
3381
|
+
the same picker-variant border vars the variant select uses, so
|
|
3382
|
+
the two pieces visually agree even when the merchant tweaks
|
|
3383
|
+
--lb-picker-variant-border-width / -color / -radius. The cell
|
|
3384
|
+
dividers come from a 1px border on the centre value rather than
|
|
3385
|
+
per-button borders, so the rounded outer corners stay clean.
|
|
3386
|
+
Mirrors extensions/bundle-theme/assets/bundle-mix-match.css so
|
|
3387
|
+
the headless web component matches the Liquid theme widget. */
|
|
3388
|
+
/* Quantity label + stepper sit together in a .lb-bundle-variant-option-group
|
|
3389
|
+
wrapper so the label-to-stepper gap inherits the same 2px the variant
|
|
3390
|
+
pickers use. The wrapper carries the margin-top that spaces the qty
|
|
3391
|
+
group from the unit-price / low-stock-badge above; the stepper itself
|
|
3392
|
+
has no top margin so the group can be repositioned without coupling. */
|
|
3393
|
+
.lb-mix-match__qty-stepper-group {
|
|
3394
|
+
margin-top: 8px;
|
|
3395
|
+
/* Group is a flex column; without an explicit width it stretches to
|
|
3396
|
+
fill the info column so the \u2212/value/+ cells get pulled apart. Pin
|
|
3397
|
+
to fit-content so the wrapper hugs the stepper's natural width. */
|
|
3398
|
+
width: fit-content;
|
|
3399
|
+
}
|
|
3400
|
+
|
|
3401
|
+
.lb-mix-match__qty-stepper {
|
|
3402
|
+
display: inline-flex;
|
|
3403
|
+
align-items: stretch;
|
|
3404
|
+
flex-shrink: 0;
|
|
3405
|
+
height: 32px;
|
|
3406
|
+
border: var(--lb-picker-variant-border-width) solid var(--lb-picker-variant-border-color);
|
|
3407
|
+
border-radius: var(--lb-picker-variant-radius);
|
|
3408
|
+
background-color: var(--lb-picker-bg);
|
|
3409
|
+
overflow: hidden;
|
|
3410
|
+
box-sizing: border-box;
|
|
3411
|
+
}
|
|
3412
|
+
|
|
3413
|
+
.lb-mix-match__qty-stepper-button {
|
|
3414
|
+
appearance: none;
|
|
3415
|
+
-webkit-appearance: none;
|
|
3416
|
+
background: transparent;
|
|
3417
|
+
border: none;
|
|
3418
|
+
margin: 0;
|
|
3419
|
+
padding: 0;
|
|
3420
|
+
width: 28px;
|
|
3421
|
+
font-family: inherit;
|
|
3422
|
+
font-size: 16px;
|
|
3423
|
+
font-weight: 500;
|
|
3424
|
+
line-height: 1;
|
|
3425
|
+
color: var(--lb-picker-text);
|
|
3426
|
+
cursor: pointer;
|
|
3427
|
+
display: inline-flex;
|
|
3428
|
+
align-items: center;
|
|
3429
|
+
justify-content: center;
|
|
3430
|
+
}
|
|
3431
|
+
|
|
3432
|
+
.lb-mix-match__qty-stepper-button:hover:not(:disabled) {
|
|
3433
|
+
background: color-mix(in srgb, var(--lb-picker-text) 6%, transparent);
|
|
3434
|
+
}
|
|
3435
|
+
|
|
3436
|
+
.lb-mix-match__qty-stepper-button:focus-visible {
|
|
3437
|
+
outline: 2px solid var(--lb-primary-color);
|
|
3438
|
+
outline-offset: -2px;
|
|
3439
|
+
}
|
|
3440
|
+
|
|
3441
|
+
.lb-mix-match__qty-stepper-button:disabled {
|
|
3442
|
+
opacity: 0.4;
|
|
3443
|
+
cursor: not-allowed;
|
|
3444
|
+
}
|
|
3445
|
+
|
|
3446
|
+
.lb-mix-match__qty-stepper-value {
|
|
3447
|
+
display: inline-flex;
|
|
3448
|
+
align-items: center;
|
|
3449
|
+
justify-content: center;
|
|
3450
|
+
min-width: 36px;
|
|
3451
|
+
padding: 0 8px;
|
|
3452
|
+
font-family: inherit;
|
|
3453
|
+
font-size: 13px;
|
|
3454
|
+
font-weight: 500;
|
|
3455
|
+
color: var(--lb-picker-text);
|
|
3456
|
+
box-sizing: border-box;
|
|
3457
|
+
}
|
|
3458
|
+
|
|
3159
3459
|
.lb-mix-match__modal-add {
|
|
3160
3460
|
padding: 8px 20px;
|
|
3161
3461
|
background: var(--lb-picker-add-bg);
|