@lime-bundles/widget 2.4.0 → 2.4.1
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 +326 -91
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +22 -1
- package/dist/index.d.ts +22 -1
- package/dist/index.js +305 -71
- package/dist/index.js.map +1 -1
- package/dist/lime-bundle.global.js +82 -25
- package/dist/lime-bundle.global.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.cts
CHANGED
|
@@ -62,4 +62,25 @@ declare class LimeBundleElement extends HTMLElement {
|
|
|
62
62
|
private renderError;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
/**
|
|
66
|
+
* Input-mode tracker — toggles `using-mouse` / `using-keyboard` classes on a
|
|
67
|
+
* target element so CSS can scope focus styles by the customer's current
|
|
68
|
+
* input device. Default is mouse; the first keyboard-navigation keypress
|
|
69
|
+
* (Tab, arrow keys, Enter, Space, Escape, Home/End/PageUp/PageDown) flips
|
|
70
|
+
* to keyboard mode, and the next pointer click flips back.
|
|
71
|
+
*
|
|
72
|
+
* Multiple targets share one pair of document-level listeners — installed
|
|
73
|
+
* on the first `trackInputMode` call, removed when the last target is
|
|
74
|
+
* released. Safe to call across every widget instance on a page without
|
|
75
|
+
* stacking listeners.
|
|
76
|
+
*
|
|
77
|
+
* CSS shape (see bundle-base.css):
|
|
78
|
+
* .using-mouse .lb-bundle-widget :focus { outline: none; }
|
|
79
|
+
*
|
|
80
|
+
* The Liquid theme mirrors this behaviour from `bundle-widget.js` against
|
|
81
|
+
* `document.documentElement` so classic and headless storefronts render the
|
|
82
|
+
* same focus rings.
|
|
83
|
+
*/
|
|
84
|
+
declare function trackInputMode(target: HTMLElement): () => void;
|
|
85
|
+
|
|
86
|
+
export { LimeBundleElement, trackInputMode };
|
package/dist/index.d.ts
CHANGED
|
@@ -62,4 +62,25 @@ declare class LimeBundleElement extends HTMLElement {
|
|
|
62
62
|
private renderError;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
/**
|
|
66
|
+
* Input-mode tracker — toggles `using-mouse` / `using-keyboard` classes on a
|
|
67
|
+
* target element so CSS can scope focus styles by the customer's current
|
|
68
|
+
* input device. Default is mouse; the first keyboard-navigation keypress
|
|
69
|
+
* (Tab, arrow keys, Enter, Space, Escape, Home/End/PageUp/PageDown) flips
|
|
70
|
+
* to keyboard mode, and the next pointer click flips back.
|
|
71
|
+
*
|
|
72
|
+
* Multiple targets share one pair of document-level listeners — installed
|
|
73
|
+
* on the first `trackInputMode` call, removed when the last target is
|
|
74
|
+
* released. Safe to call across every widget instance on a page without
|
|
75
|
+
* stacking listeners.
|
|
76
|
+
*
|
|
77
|
+
* CSS shape (see bundle-base.css):
|
|
78
|
+
* .using-mouse .lb-bundle-widget :focus { outline: none; }
|
|
79
|
+
*
|
|
80
|
+
* The Liquid theme mirrors this behaviour from `bundle-widget.js` against
|
|
81
|
+
* `document.documentElement` so classic and headless storefronts render the
|
|
82
|
+
* same focus rings.
|
|
83
|
+
*/
|
|
84
|
+
declare function trackInputMode(target: HTMLElement): () => void;
|
|
85
|
+
|
|
86
|
+
export { LimeBundleElement, trackInputMode };
|
package/dist/index.js
CHANGED
|
@@ -212,12 +212,11 @@ function renderFixedBundle(container, bundle, onAddToCart, onCleanup) {
|
|
|
212
212
|
}
|
|
213
213
|
function buildRowState(bundle, product, productIndex) {
|
|
214
214
|
const selectedVariantIds = bundle.selectedVariantIds?.[productIndex] ?? null;
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
const
|
|
219
|
-
const
|
|
220
|
-
const selected = eligibleVariants[0] ?? null;
|
|
215
|
+
const merchantScoped = selectedVariantIds && selectedVariantIds.length > 0 ? product.variants.nodes.filter((v) => selectedVariantIds.includes(v.id)) : product.variants.nodes;
|
|
216
|
+
const firstInStock = merchantScoped.find((v) => v.availableForSale) ?? null;
|
|
217
|
+
const eligibleVariants = merchantScoped;
|
|
218
|
+
const isOos = !firstInStock;
|
|
219
|
+
const selected = firstInStock ?? merchantScoped[0] ?? null;
|
|
221
220
|
const qty = selected ? resolveBundleQty(bundle, product.id, selected.id) : 1;
|
|
222
221
|
return { product, eligibleVariants, selected, qty, isOos };
|
|
223
222
|
}
|
|
@@ -366,33 +365,85 @@ function renderProductRow(state, currency, qtyFor, onVariantChange) {
|
|
|
366
365
|
};
|
|
367
366
|
applyVariantToRow(state.selected);
|
|
368
367
|
if (state.eligibleVariants.length > 1) {
|
|
369
|
-
const
|
|
370
|
-
|
|
371
|
-
select.setAttribute("data-variant-select", "");
|
|
372
|
-
select.name = `lb-variant-${state.product.id.replace(/^.*\//, "")}`;
|
|
373
|
-
select.setAttribute(
|
|
374
|
-
"aria-label",
|
|
375
|
-
`Select variant for ${state.product.title}`
|
|
368
|
+
const optionNames = state.eligibleVariants[0].selectedOptions.map(
|
|
369
|
+
(o) => o.name
|
|
376
370
|
);
|
|
377
|
-
state.
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
371
|
+
const productIdTail = state.product.id.replace(/^.*\//, "");
|
|
372
|
+
const optionSelects = [];
|
|
373
|
+
const resolveVariant = (values) => state.eligibleVariants.find(
|
|
374
|
+
(v) => v.selectedOptions.every((o, i) => o.value === values[i]) && v.selectedOptions.length === values.length
|
|
375
|
+
) ?? null;
|
|
376
|
+
const syncSelectsToVariant = (variant) => {
|
|
377
|
+
variant.selectedOptions.forEach((o, i) => {
|
|
378
|
+
const sel = optionSelects[i];
|
|
379
|
+
if (sel && sel.value !== o.value) sel.value = o.value;
|
|
380
|
+
});
|
|
381
|
+
};
|
|
382
|
+
const isValueAvailable = (optionIndex, value, selected) => state.eligibleVariants.some((v) => {
|
|
383
|
+
if (!v.availableForSale) return false;
|
|
384
|
+
if (v.selectedOptions[optionIndex]?.value !== value) return false;
|
|
385
|
+
return v.selectedOptions.every(
|
|
386
|
+
(o, i) => i === optionIndex || o.value === selected[i]
|
|
387
387
|
);
|
|
388
|
-
|
|
388
|
+
});
|
|
389
|
+
const recomputeDisabled = (selected) => {
|
|
390
|
+
optionSelects.forEach((sel, i) => {
|
|
391
|
+
Array.from(sel.options).forEach((opt) => {
|
|
392
|
+
opt.disabled = !isValueAvailable(i, opt.value, selected);
|
|
393
|
+
});
|
|
394
|
+
});
|
|
395
|
+
};
|
|
396
|
+
const handleChange = () => {
|
|
397
|
+
const values = optionSelects.map((s) => s.value);
|
|
398
|
+
const variant = resolveVariant(values);
|
|
399
|
+
if (!variant) {
|
|
400
|
+
if (state.selected) {
|
|
401
|
+
syncSelectsToVariant(state.selected);
|
|
402
|
+
recomputeDisabled(state.selected.selectedOptions.map((o) => o.value));
|
|
403
|
+
}
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
389
406
|
state.selected = variant;
|
|
390
407
|
state.qty = qtyFor(state.product.id, variant.id);
|
|
391
408
|
if (qtyBadgeRef) qtyBadgeRef.textContent = String(state.qty);
|
|
392
409
|
applyVariantToRow(variant);
|
|
410
|
+
recomputeDisabled(variant.selectedOptions.map((o) => o.value));
|
|
393
411
|
onVariantChange();
|
|
412
|
+
};
|
|
413
|
+
const groupsContainer = el("div", "lb-bundle-variant-option-groups");
|
|
414
|
+
optionNames.forEach((name2, position) => {
|
|
415
|
+
const group = el("div", "lb-bundle-variant-option-group");
|
|
416
|
+
const label = el("span", "lb-bundle-variant-option-label");
|
|
417
|
+
label.textContent = name2;
|
|
418
|
+
group.appendChild(label);
|
|
419
|
+
const select = document.createElement("select");
|
|
420
|
+
select.className = "lb-bundle-variant-select";
|
|
421
|
+
select.setAttribute("data-variant-option", "");
|
|
422
|
+
select.setAttribute("data-option-position", String(position + 1));
|
|
423
|
+
select.name = `lb-variant-${productIdTail}-${position + 1}`;
|
|
424
|
+
select.setAttribute("aria-label", name2);
|
|
425
|
+
const seen = /* @__PURE__ */ new Set();
|
|
426
|
+
state.eligibleVariants.forEach((v) => {
|
|
427
|
+
const value = v.selectedOptions[position]?.value;
|
|
428
|
+
if (!value || seen.has(value)) return;
|
|
429
|
+
seen.add(value);
|
|
430
|
+
const opt = document.createElement("option");
|
|
431
|
+
opt.value = value;
|
|
432
|
+
opt.textContent = value;
|
|
433
|
+
if (state.selected?.selectedOptions[position]?.value === value) {
|
|
434
|
+
opt.selected = true;
|
|
435
|
+
}
|
|
436
|
+
select.appendChild(opt);
|
|
437
|
+
});
|
|
438
|
+
select.addEventListener("change", handleChange);
|
|
439
|
+
optionSelects.push(select);
|
|
440
|
+
group.appendChild(select);
|
|
441
|
+
groupsContainer.appendChild(group);
|
|
394
442
|
});
|
|
395
|
-
info.appendChild(
|
|
443
|
+
info.appendChild(groupsContainer);
|
|
444
|
+
if (state.selected) {
|
|
445
|
+
recomputeDisabled(state.selected.selectedOptions.map((o) => o.value));
|
|
446
|
+
}
|
|
396
447
|
} else if (state.eligibleVariants.length === 1 && state.product.variants.nodes.length > 1) {
|
|
397
448
|
const badge = el("span", "lb-bundle-variant-badge");
|
|
398
449
|
badge.textContent = state.eligibleVariants[0].title;
|
|
@@ -481,6 +532,7 @@ function computeSale(totalCents, discount, rows) {
|
|
|
481
532
|
}
|
|
482
533
|
|
|
483
534
|
// src/renderers/mix-match.ts
|
|
535
|
+
import { resolveBundleQty as resolveBundleQty2 } from "@lime-bundles/core";
|
|
484
536
|
var PLACEHOLDER_THUMB_SVG2 = `
|
|
485
537
|
<svg class="lb-bundle-placeholder-icon" viewBox="0 0 28 28" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false">
|
|
486
538
|
<rect x="4" y="4" width="20" height="20" rx="3"></rect>
|
|
@@ -548,9 +600,6 @@ function renderMixMatchBundle(container, bundle, onAddToCart, onCleanup) {
|
|
|
548
600
|
showSearch: wc.showSearch,
|
|
549
601
|
onAdd: (product, variant) => addSelection(product, variant),
|
|
550
602
|
onRemove: (productId, variantId) => removeSelection(productId, variantId),
|
|
551
|
-
countFor: (productId, variantId) => selections.filter(
|
|
552
|
-
(s) => s.productId === productId && s.variantId === variantId
|
|
553
|
-
).length,
|
|
554
603
|
isOverMax: () => selections.length >= maxQty
|
|
555
604
|
});
|
|
556
605
|
root.appendChild(modal.el);
|
|
@@ -560,7 +609,7 @@ function renderMixMatchBundle(container, bundle, onAddToCart, onCleanup) {
|
|
|
560
609
|
if (cta.disabled) return;
|
|
561
610
|
const lines = selections.map((s) => ({
|
|
562
611
|
merchandiseId: s.variantId,
|
|
563
|
-
quantity:
|
|
612
|
+
quantity: s.quantity,
|
|
564
613
|
attributes: [
|
|
565
614
|
{ key: "_lime_bundle_gid", value: bundle.id },
|
|
566
615
|
{ key: "_lime_bundle_type", value: bundle.bundleType }
|
|
@@ -594,7 +643,8 @@ function renderMixMatchBundle(container, bundle, onAddToCart, onCleanup) {
|
|
|
594
643
|
firstVariant.unitPrice,
|
|
595
644
|
firstVariant.unitPriceMeasurement,
|
|
596
645
|
currency
|
|
597
|
-
)
|
|
646
|
+
),
|
|
647
|
+
quantity: resolveBundleQty2(bundle, firstEligible.product.id, firstVariant.id)
|
|
598
648
|
});
|
|
599
649
|
}
|
|
600
650
|
afterMutation();
|
|
@@ -612,7 +662,8 @@ function renderMixMatchBundle(container, bundle, onAddToCart, onCleanup) {
|
|
|
612
662
|
variant.unitPrice,
|
|
613
663
|
variant.unitPriceMeasurement,
|
|
614
664
|
currency
|
|
615
|
-
)
|
|
665
|
+
),
|
|
666
|
+
quantity: resolveBundleQty2(bundle, product.id, variant.id)
|
|
616
667
|
});
|
|
617
668
|
afterMutation();
|
|
618
669
|
}
|
|
@@ -779,6 +830,9 @@ function renderFilledSlot(selection, index, currency, onRemove) {
|
|
|
779
830
|
} else {
|
|
780
831
|
thumb.insertAdjacentHTML("beforeend", PLACEHOLDER_THUMB_SVG2);
|
|
781
832
|
}
|
|
833
|
+
const qtyBadge = el("span", "lb-bundle-qty-badge");
|
|
834
|
+
qtyBadge.textContent = String(selection.quantity);
|
|
835
|
+
thumb.appendChild(qtyBadge);
|
|
782
836
|
slot.appendChild(thumb);
|
|
783
837
|
const info = el("div", "lb-mix-match__filled-info");
|
|
784
838
|
const title = el("span", "lb-mix-match__filled-title");
|
|
@@ -789,14 +843,16 @@ function renderFilledSlot(selection, index, currency, onRemove) {
|
|
|
789
843
|
variant.textContent = selection.variantTitle;
|
|
790
844
|
info.appendChild(variant);
|
|
791
845
|
}
|
|
846
|
+
const linePrice = selection.priceCents * selection.quantity;
|
|
847
|
+
const lineCompare = selection.compareCents !== null ? selection.compareCents * selection.quantity : null;
|
|
792
848
|
const priceWrap = el("span", "lb-mix-match__filled-price");
|
|
793
|
-
if (
|
|
849
|
+
if (lineCompare !== null && lineCompare > linePrice) {
|
|
794
850
|
const compare = el("span", "lb-mix-match__filled-compare");
|
|
795
|
-
compare.textContent = formatCents(
|
|
851
|
+
compare.textContent = formatCents(lineCompare, currency);
|
|
796
852
|
priceWrap.appendChild(compare);
|
|
797
853
|
}
|
|
798
854
|
const priceEl = document.createElement("span");
|
|
799
|
-
priceEl.textContent = formatCents(
|
|
855
|
+
priceEl.textContent = formatCents(linePrice, currency);
|
|
800
856
|
priceWrap.appendChild(priceEl);
|
|
801
857
|
info.appendChild(priceWrap);
|
|
802
858
|
if (selection.unitPriceLabel) {
|
|
@@ -837,7 +893,10 @@ function renderPricingSection(showCompareAtPrice) {
|
|
|
837
893
|
return;
|
|
838
894
|
}
|
|
839
895
|
wrap.style.display = "";
|
|
840
|
-
const totalCents = selections.reduce(
|
|
896
|
+
const totalCents = selections.reduce(
|
|
897
|
+
(s, sel) => s + sel.priceCents * sel.quantity,
|
|
898
|
+
0
|
|
899
|
+
);
|
|
841
900
|
const saleCents = computeBundleSaleCents(totalCents, bundle.discountConfig);
|
|
842
901
|
if (showCompareAtPrice && totalCents > saleCents) {
|
|
843
902
|
compare.textContent = formatCents(totalCents, currency);
|
|
@@ -864,7 +923,10 @@ function renderSavingsBar2() {
|
|
|
864
923
|
wrap.style.display = "none";
|
|
865
924
|
return;
|
|
866
925
|
}
|
|
867
|
-
const totalCents = selections.reduce(
|
|
926
|
+
const totalCents = selections.reduce(
|
|
927
|
+
(s, sel) => s + sel.priceCents * sel.quantity,
|
|
928
|
+
0
|
|
929
|
+
);
|
|
868
930
|
const saleCents = computeBundleSaleCents(totalCents, bundle.discountConfig);
|
|
869
931
|
const savings = Math.max(0, totalCents - saleCents);
|
|
870
932
|
if (savings <= 0) {
|
|
@@ -958,8 +1020,8 @@ function renderModal(bundle, eligible, currency, handlers) {
|
|
|
958
1020
|
rowsBuilt = true;
|
|
959
1021
|
list.innerHTML = "";
|
|
960
1022
|
eligible.forEach((ep) => {
|
|
961
|
-
const availableVariants = ep.variants
|
|
962
|
-
const firstAvailVariant =
|
|
1023
|
+
const availableVariants = ep.variants;
|
|
1024
|
+
const firstAvailVariant = ep.variants.find((v) => v.availableForSale) ?? ep.firstAvailableVariant ?? ep.variants[0];
|
|
963
1025
|
if (!firstAvailVariant) return;
|
|
964
1026
|
let currentVariant = firstAvailVariant;
|
|
965
1027
|
const productEl = el(
|
|
@@ -984,7 +1046,9 @@ function renderModal(bundle, eligible, currency, handlers) {
|
|
|
984
1046
|
thumb.insertAdjacentHTML("beforeend", PLACEHOLDER_THUMB_SVG2);
|
|
985
1047
|
}
|
|
986
1048
|
const countBadge = el("span", "lb-bundle-qty-badge");
|
|
987
|
-
countBadge.
|
|
1049
|
+
countBadge.textContent = String(
|
|
1050
|
+
resolveBundleQty2(bundle, ep.product.id, currentVariant.id)
|
|
1051
|
+
);
|
|
988
1052
|
thumb.appendChild(countBadge);
|
|
989
1053
|
productEl.appendChild(thumb);
|
|
990
1054
|
const info = el("div", "lb-mix-match__modal-product-info");
|
|
@@ -993,7 +1057,7 @@ function renderModal(bundle, eligible, currency, handlers) {
|
|
|
993
1057
|
info.appendChild(title);
|
|
994
1058
|
const price = el("p", "lb-mix-match__modal-product-price");
|
|
995
1059
|
price.textContent = formatCents(
|
|
996
|
-
parseCents(currentVariant.price.amount),
|
|
1060
|
+
parseCents(currentVariant.price.amount) * resolveBundleQty2(bundle, ep.product.id, currentVariant.id),
|
|
997
1061
|
currency
|
|
998
1062
|
);
|
|
999
1063
|
info.appendChild(price);
|
|
@@ -1020,28 +1084,53 @@ function renderModal(bundle, eligible, currency, handlers) {
|
|
|
1020
1084
|
}
|
|
1021
1085
|
};
|
|
1022
1086
|
if (availableVariants.length > 1) {
|
|
1023
|
-
const
|
|
1024
|
-
|
|
1025
|
-
select.setAttribute("data-variant-select", "");
|
|
1026
|
-
select.name = `lb-variant-${ep.product.id.replace(/^.*\//, "")}`;
|
|
1027
|
-
select.setAttribute(
|
|
1028
|
-
"aria-label",
|
|
1029
|
-
`Select variant for ${ep.product.title}`
|
|
1087
|
+
const optionNames = availableVariants[0].selectedOptions.map(
|
|
1088
|
+
(o) => o.name
|
|
1030
1089
|
);
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1090
|
+
const productIdTail = ep.product.id.replace(/^.*\//, "");
|
|
1091
|
+
const optionSelects = [];
|
|
1092
|
+
const resolveVariant = (values) => availableVariants.find(
|
|
1093
|
+
(v) => v.selectedOptions.length === values.length && v.selectedOptions.every((o, i) => o.value === values[i])
|
|
1094
|
+
) ?? null;
|
|
1095
|
+
const syncSelectsToVariant = (v) => {
|
|
1096
|
+
v.selectedOptions.forEach((o, i) => {
|
|
1097
|
+
const sel = optionSelects[i];
|
|
1098
|
+
if (sel && sel.value !== o.value) sel.value = o.value;
|
|
1099
|
+
});
|
|
1100
|
+
};
|
|
1101
|
+
const isValueAvailable = (optionIndex, value, selected) => availableVariants.some((v) => {
|
|
1102
|
+
if (!v.availableForSale) return false;
|
|
1103
|
+
if (v.selectedOptions[optionIndex]?.value !== value) return false;
|
|
1104
|
+
return v.selectedOptions.every(
|
|
1105
|
+
(o, i) => i === optionIndex || o.value === selected[i]
|
|
1106
|
+
);
|
|
1037
1107
|
});
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1108
|
+
const recomputeDisabled = (selected) => {
|
|
1109
|
+
optionSelects.forEach((sel, i) => {
|
|
1110
|
+
Array.from(sel.options).forEach((opt) => {
|
|
1111
|
+
opt.disabled = !isValueAvailable(i, opt.value, selected);
|
|
1112
|
+
});
|
|
1113
|
+
});
|
|
1114
|
+
};
|
|
1115
|
+
const handleChange = () => {
|
|
1116
|
+
const values = optionSelects.map((s) => s.value);
|
|
1117
|
+
const next = resolveVariant(values);
|
|
1118
|
+
if (!next) {
|
|
1119
|
+
syncSelectsToVariant(currentVariant);
|
|
1120
|
+
recomputeDisabled(
|
|
1121
|
+
currentVariant.selectedOptions.map((o) => o.value)
|
|
1122
|
+
);
|
|
1123
|
+
return;
|
|
1124
|
+
}
|
|
1041
1125
|
currentVariant = next;
|
|
1042
1126
|
row.variant = next;
|
|
1127
|
+
const nextQty = resolveBundleQty2(
|
|
1128
|
+
bundle,
|
|
1129
|
+
ep.product.id,
|
|
1130
|
+
currentVariant.id
|
|
1131
|
+
);
|
|
1043
1132
|
price.textContent = formatCents(
|
|
1044
|
-
parseCents(currentVariant.price.amount),
|
|
1133
|
+
parseCents(currentVariant.price.amount) * nextQty,
|
|
1045
1134
|
currency
|
|
1046
1135
|
);
|
|
1047
1136
|
const nextUnitText = formatUnitPrice(
|
|
@@ -1056,9 +1145,43 @@ function renderModal(bundle, eligible, currency, handlers) {
|
|
|
1056
1145
|
unitPrice.textContent = "";
|
|
1057
1146
|
unitPrice.hidden = true;
|
|
1058
1147
|
}
|
|
1148
|
+
recomputeDisabled(next.selectedOptions.map((o) => o.value));
|
|
1059
1149
|
rowUpdateCount();
|
|
1150
|
+
};
|
|
1151
|
+
const groupsContainer = el("div", "lb-bundle-variant-option-groups");
|
|
1152
|
+
optionNames.forEach((name, position) => {
|
|
1153
|
+
const group = el("div", "lb-bundle-variant-option-group");
|
|
1154
|
+
const label = el("span", "lb-bundle-variant-option-label");
|
|
1155
|
+
label.textContent = name;
|
|
1156
|
+
group.appendChild(label);
|
|
1157
|
+
const select = document.createElement("select");
|
|
1158
|
+
select.className = "lb-mix-match__variant-select";
|
|
1159
|
+
select.setAttribute("data-variant-option", "");
|
|
1160
|
+
select.setAttribute("data-option-position", String(position + 1));
|
|
1161
|
+
select.name = `lb-variant-${productIdTail}-${position + 1}`;
|
|
1162
|
+
select.setAttribute("aria-label", name);
|
|
1163
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1164
|
+
availableVariants.forEach((v) => {
|
|
1165
|
+
const value = v.selectedOptions[position]?.value;
|
|
1166
|
+
if (!value || seen.has(value)) return;
|
|
1167
|
+
seen.add(value);
|
|
1168
|
+
const opt = document.createElement("option");
|
|
1169
|
+
opt.value = value;
|
|
1170
|
+
opt.textContent = value;
|
|
1171
|
+
if (firstAvailVariant.selectedOptions[position]?.value === value) {
|
|
1172
|
+
opt.selected = true;
|
|
1173
|
+
}
|
|
1174
|
+
select.appendChild(opt);
|
|
1175
|
+
});
|
|
1176
|
+
select.addEventListener("change", handleChange);
|
|
1177
|
+
optionSelects.push(select);
|
|
1178
|
+
group.appendChild(select);
|
|
1179
|
+
groupsContainer.appendChild(group);
|
|
1060
1180
|
});
|
|
1061
|
-
info.appendChild(
|
|
1181
|
+
info.appendChild(groupsContainer);
|
|
1182
|
+
recomputeDisabled(
|
|
1183
|
+
firstAvailVariant.selectedOptions.map((o) => o.value)
|
|
1184
|
+
);
|
|
1062
1185
|
} else if (availableVariants.length === 1 && firstAvailVariant.title !== "Default Title") {
|
|
1063
1186
|
const variantLabel = el("span", "lb-mix-match__filled-variant");
|
|
1064
1187
|
variantLabel.textContent = firstAvailVariant.title;
|
|
@@ -1071,13 +1194,9 @@ function renderModal(bundle, eligible, currency, handlers) {
|
|
|
1071
1194
|
}
|
|
1072
1195
|
productEl.appendChild(info);
|
|
1073
1196
|
const rowUpdateCount = () => {
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
countBadge.style.display = "";
|
|
1078
|
-
} else {
|
|
1079
|
-
countBadge.style.display = "none";
|
|
1080
|
-
}
|
|
1197
|
+
countBadge.textContent = String(
|
|
1198
|
+
resolveBundleQty2(bundle, ep.product.id, currentVariant.id)
|
|
1199
|
+
);
|
|
1081
1200
|
};
|
|
1082
1201
|
if (!ep.isOos) {
|
|
1083
1202
|
const addBtn = document.createElement("button");
|
|
@@ -1087,6 +1206,7 @@ function renderModal(bundle, eligible, currency, handlers) {
|
|
|
1087
1206
|
addBtn.addEventListener("click", () => {
|
|
1088
1207
|
if (handlers.isOverMax()) return;
|
|
1089
1208
|
handlers.onAdd(ep.product, currentVariant);
|
|
1209
|
+
close();
|
|
1090
1210
|
});
|
|
1091
1211
|
productEl.appendChild(addBtn);
|
|
1092
1212
|
}
|
|
@@ -1492,6 +1612,61 @@ function clamp(n, min, max) {
|
|
|
1492
1612
|
// src/lime-bundle.ts
|
|
1493
1613
|
import { applyWidgetConfigVars } from "@lime-bundles/core";
|
|
1494
1614
|
|
|
1615
|
+
// src/utils/input-mode.ts
|
|
1616
|
+
var NAV_KEYS = /* @__PURE__ */ new Set([
|
|
1617
|
+
"Tab",
|
|
1618
|
+
"ArrowUp",
|
|
1619
|
+
"ArrowDown",
|
|
1620
|
+
"ArrowLeft",
|
|
1621
|
+
"ArrowRight",
|
|
1622
|
+
"Home",
|
|
1623
|
+
"End",
|
|
1624
|
+
"PageUp",
|
|
1625
|
+
"PageDown",
|
|
1626
|
+
"Enter",
|
|
1627
|
+
" ",
|
|
1628
|
+
"Escape"
|
|
1629
|
+
]);
|
|
1630
|
+
var targets = /* @__PURE__ */ new Set();
|
|
1631
|
+
var listenersAttached = false;
|
|
1632
|
+
function setAll(on) {
|
|
1633
|
+
const off = on === "using-mouse" ? "using-keyboard" : "using-mouse";
|
|
1634
|
+
for (const el2 of targets) {
|
|
1635
|
+
el2.classList.add(on);
|
|
1636
|
+
el2.classList.remove(off);
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
function onKeyDown(e) {
|
|
1640
|
+
if (NAV_KEYS.has(e.key)) setAll("using-keyboard");
|
|
1641
|
+
}
|
|
1642
|
+
function onPointerDown() {
|
|
1643
|
+
setAll("using-mouse");
|
|
1644
|
+
}
|
|
1645
|
+
function attachListeners() {
|
|
1646
|
+
if (listenersAttached) return;
|
|
1647
|
+
listenersAttached = true;
|
|
1648
|
+
document.addEventListener("keydown", onKeyDown, true);
|
|
1649
|
+
document.addEventListener("pointerdown", onPointerDown, true);
|
|
1650
|
+
}
|
|
1651
|
+
function detachListeners() {
|
|
1652
|
+
if (!listenersAttached) return;
|
|
1653
|
+
listenersAttached = false;
|
|
1654
|
+
document.removeEventListener("keydown", onKeyDown, true);
|
|
1655
|
+
document.removeEventListener("pointerdown", onPointerDown, true);
|
|
1656
|
+
}
|
|
1657
|
+
function trackInputMode(target) {
|
|
1658
|
+
target.classList.add("using-mouse");
|
|
1659
|
+
target.classList.remove("using-keyboard");
|
|
1660
|
+
targets.add(target);
|
|
1661
|
+
attachListeners();
|
|
1662
|
+
return () => {
|
|
1663
|
+
targets.delete(target);
|
|
1664
|
+
target.classList.remove("using-mouse");
|
|
1665
|
+
target.classList.remove("using-keyboard");
|
|
1666
|
+
if (targets.size === 0) detachListeners();
|
|
1667
|
+
};
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1495
1670
|
// src/styles/bundle-css.ts
|
|
1496
1671
|
var BUNDLE_BASE_CSS = `/* Lime Bundles \u2014 shared base styles for all bundle widget types */
|
|
1497
1672
|
|
|
@@ -1776,6 +1951,56 @@ var BUNDLE_BASE_CSS = `/* Lime Bundles \u2014 shared base styles for all bundle
|
|
|
1776
1951
|
margin-top: 8px;
|
|
1777
1952
|
}
|
|
1778
1953
|
|
|
1954
|
+
/* Per-option variant pickers. Each option's label + select sit inside a
|
|
1955
|
+
.lb-bundle-variant-option-group (flex column, 2px gap between label and
|
|
1956
|
+
select); the groups stack inside a .lb-bundle-variant-option-groups
|
|
1957
|
+
parent (flex column, 12px gap between groups). The parent owns the top
|
|
1958
|
+
offset from the preceding unit-price line, so individual labels and
|
|
1959
|
+
selects don't carry their own vertical margins. */
|
|
1960
|
+
.lb-bundle-variant-option-groups {
|
|
1961
|
+
display: flex;
|
|
1962
|
+
flex-direction: column;
|
|
1963
|
+
gap: 12px;
|
|
1964
|
+
margin-top: 8px;
|
|
1965
|
+
}
|
|
1966
|
+
|
|
1967
|
+
.lb-bundle-variant-option-group {
|
|
1968
|
+
display: flex;
|
|
1969
|
+
flex-direction: column;
|
|
1970
|
+
gap: 2px;
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1973
|
+
.lb-bundle-variant-option-label {
|
|
1974
|
+
display: block;
|
|
1975
|
+
margin: 0;
|
|
1976
|
+
font-size: 12px;
|
|
1977
|
+
line-height: 16px;
|
|
1978
|
+
font-weight: 600;
|
|
1979
|
+
letter-spacing: 0.05em;
|
|
1980
|
+
text-transform: uppercase;
|
|
1981
|
+
color: color-mix(in srgb, var(--lb-text) 60%, transparent);
|
|
1982
|
+
}
|
|
1983
|
+
|
|
1984
|
+
.lb-mix-match__modal-product-info .lb-bundle-variant-option-groups {
|
|
1985
|
+
margin-top: 4px;
|
|
1986
|
+
}
|
|
1987
|
+
|
|
1988
|
+
/* Focus-ring suppression for mouse users. A small JS helper \u2014 see
|
|
1989
|
+
packages/widget/src/utils/input-mode.ts and bundle-widget.js \u2014 toggles
|
|
1990
|
+
.using-mouse / .using-keyboard on the widget root (or html in the Liquid
|
|
1991
|
+
path) based on the customer's current input device. Default is mouse, so
|
|
1992
|
+
click-to-focus doesn't leave a keyboard-style ring. The modal overlay
|
|
1993
|
+
gets its own selector because the Liquid path reparents it to body,
|
|
1994
|
+
outside the widget root. */
|
|
1995
|
+
.using-mouse .lb-bundle-widget :focus,
|
|
1996
|
+
.using-mouse .lb-bundle-widget :focus-visible,
|
|
1997
|
+
.using-mouse .lb-mix-match__modal-overlay :focus,
|
|
1998
|
+
.using-mouse .lb-mix-match__modal-overlay :focus-visible {
|
|
1999
|
+
outline: none;
|
|
2000
|
+
outline-offset: 0;
|
|
2001
|
+
box-shadow: none;
|
|
2002
|
+
}
|
|
2003
|
+
|
|
1779
2004
|
.lb-bundle-quantity {
|
|
1780
2005
|
font-size: 12px;
|
|
1781
2006
|
line-height: 16px;
|
|
@@ -2033,9 +2258,10 @@ var BUNDLE_FIXED_CSS = `/* Lime Bundles \u2014 Fixed bundle styles */
|
|
|
2033
2258
|
border: none;
|
|
2034
2259
|
}
|
|
2035
2260
|
|
|
2036
|
-
/* Variant picker select \u2014 styled to match the variant badge aesthetic
|
|
2261
|
+
/* Variant picker select \u2014 styled to match the variant badge aesthetic.
|
|
2262
|
+
Sits inside .lb-bundle-variant-option-group so vertical spacing is owned
|
|
2263
|
+
by the group/groups flex gap, not the select itself. */
|
|
2037
2264
|
.lb-bundle-variant-select {
|
|
2038
|
-
margin-top: 8px;
|
|
2039
2265
|
display: inline-block;
|
|
2040
2266
|
border: var(--lb-variant-border-width) solid var(--lb-variant-border-color);
|
|
2041
2267
|
border-radius: var(--lb-variant-radius);
|
|
@@ -2052,7 +2278,8 @@ var BUNDLE_FIXED_CSS = `/* Lime Bundles \u2014 Fixed bundle styles */
|
|
|
2052
2278
|
background-repeat: no-repeat;
|
|
2053
2279
|
background-position: right 8px center;
|
|
2054
2280
|
background-size: 12px;
|
|
2055
|
-
|
|
2281
|
+
width: 50%;
|
|
2282
|
+
max-width: 50%;
|
|
2056
2283
|
}
|
|
2057
2284
|
|
|
2058
2285
|
.lb-bundle-variant-select:focus-visible {
|
|
@@ -2459,7 +2686,6 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
2459
2686
|
|
|
2460
2687
|
.lb-mix-match__variant-select {
|
|
2461
2688
|
font-size: 12px;
|
|
2462
|
-
margin: 4px 0 0;
|
|
2463
2689
|
padding: 4px 24px 4px 8px;
|
|
2464
2690
|
border: var(--lb-picker-variant-border-width) solid var(--lb-picker-variant-border-color);
|
|
2465
2691
|
border-radius: var(--lb-picker-variant-radius);
|
|
@@ -2472,7 +2698,8 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
2472
2698
|
font-family: inherit;
|
|
2473
2699
|
min-height: 32px;
|
|
2474
2700
|
cursor: pointer;
|
|
2475
|
-
|
|
2701
|
+
width: 50%;
|
|
2702
|
+
max-width: 50%;
|
|
2476
2703
|
appearance: none;
|
|
2477
2704
|
-webkit-appearance: none;
|
|
2478
2705
|
}
|
|
@@ -2556,6 +2783,11 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
2556
2783
|
.lb-mix-match__modal-overlay--open .lb-mix-match__modal {
|
|
2557
2784
|
transform: translateY(0);
|
|
2558
2785
|
}
|
|
2786
|
+
|
|
2787
|
+
.lb-mix-match__variant-select {
|
|
2788
|
+
width: 80%;
|
|
2789
|
+
max-width: 80%;
|
|
2790
|
+
}
|
|
2559
2791
|
}
|
|
2560
2792
|
|
|
2561
2793
|
/* === Reduced Motion === */
|
|
@@ -3129,6 +3361,7 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
3129
3361
|
container.setAttribute("data-bundle-type", bundle.bundleType);
|
|
3130
3362
|
container.setAttribute("data-bundle-gid", bundle.id);
|
|
3131
3363
|
applyWidgetConfigVars(container, bundle.widgetConfig);
|
|
3364
|
+
this.renderCleanups.push(trackInputMode(container));
|
|
3132
3365
|
const dispatch = (lines) => this.handleAddToCart(bundle, lines);
|
|
3133
3366
|
const registerCleanup = (fn) => this.renderCleanups.push(fn);
|
|
3134
3367
|
switch (bundle.bundleType) {
|
|
@@ -3230,6 +3463,7 @@ if (typeof customElements !== "undefined" && !customElements.get("lime-bundle"))
|
|
|
3230
3463
|
customElements.define("lime-bundle", LimeBundleElement);
|
|
3231
3464
|
}
|
|
3232
3465
|
export {
|
|
3233
|
-
LimeBundleElement
|
|
3466
|
+
LimeBundleElement,
|
|
3467
|
+
trackInputMode
|
|
3234
3468
|
};
|
|
3235
3469
|
//# sourceMappingURL=index.js.map
|