@lime-bundles/react 2.5.0 → 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 +137 -71
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +142 -76
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -304,6 +304,20 @@ var { computePosition, emptyTypeAheadState, handleKey, pushTypeAheadChar } = dro
|
|
|
304
304
|
var ITEM_HEIGHT_PX = 32;
|
|
305
305
|
var LIST_PAD_Y = 8;
|
|
306
306
|
var MAX_VISIBLE_ITEMS = 8;
|
|
307
|
+
function findScrollableAncestor(el) {
|
|
308
|
+
const win = el.ownerDocument?.defaultView;
|
|
309
|
+
if (!win) return null;
|
|
310
|
+
let cur = el.parentElement;
|
|
311
|
+
while (cur && cur !== el.ownerDocument.body) {
|
|
312
|
+
const style = win.getComputedStyle(cur);
|
|
313
|
+
const overflowY = style.overflowY;
|
|
314
|
+
if (overflowY === "auto" || overflowY === "scroll" || overflowY === "hidden") {
|
|
315
|
+
return cur;
|
|
316
|
+
}
|
|
317
|
+
cur = cur.parentElement;
|
|
318
|
+
}
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
307
321
|
function VariantDropdown({
|
|
308
322
|
options,
|
|
309
323
|
value,
|
|
@@ -353,6 +367,11 @@ function VariantDropdown({
|
|
|
353
367
|
if (rect.width === 0) return;
|
|
354
368
|
const visibleCount = Math.min(options.length || 1, MAX_VISIBLE_ITEMS);
|
|
355
369
|
const desiredHeight = visibleCount * ITEM_HEIGHT_PX + LIST_PAD_Y;
|
|
370
|
+
const scrollable = findScrollableAncestor(trigger);
|
|
371
|
+
const clip = scrollable ? (() => {
|
|
372
|
+
const r = scrollable.getBoundingClientRect();
|
|
373
|
+
return { top: r.top, bottom: r.bottom };
|
|
374
|
+
})() : void 0;
|
|
356
375
|
setPosition(
|
|
357
376
|
computePosition({
|
|
358
377
|
trigger: {
|
|
@@ -362,7 +381,8 @@ function VariantDropdown({
|
|
|
362
381
|
width: rect.width
|
|
363
382
|
},
|
|
364
383
|
viewportHeight: window.innerHeight,
|
|
365
|
-
desiredHeight
|
|
384
|
+
desiredHeight,
|
|
385
|
+
clip
|
|
366
386
|
})
|
|
367
387
|
);
|
|
368
388
|
}, [isOpen, options.length]);
|
|
@@ -770,12 +790,15 @@ import { useCallback as useCallback5, useEffect as useEffect7, useMemo as useMem
|
|
|
770
790
|
import {
|
|
771
791
|
formatMoney as formatMoney2,
|
|
772
792
|
formatUnitPrice as formatUnitPrice2,
|
|
773
|
-
validateQuantity,
|
|
774
|
-
resolveBundleQty as resolveBundleQty2,
|
|
775
793
|
isVariantFulfillable as isVariantFulfillable2,
|
|
776
|
-
shouldShowLowStockBadge as shouldShowLowStockBadge2
|
|
794
|
+
shouldShowLowStockBadge as shouldShowLowStockBadge2,
|
|
795
|
+
maxAddableQuantity,
|
|
796
|
+
DEFAULT_PRODUCT_RULE
|
|
777
797
|
} from "@lime-bundles/core";
|
|
778
798
|
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
799
|
+
function ruleFor(bundle, productId) {
|
|
800
|
+
return bundle.productRules[productId] ?? DEFAULT_PRODUCT_RULE;
|
|
801
|
+
}
|
|
779
802
|
function MixMatchBundle(props) {
|
|
780
803
|
const {
|
|
781
804
|
shopDomain,
|
|
@@ -819,56 +842,92 @@ function MixMatchBundle(props) {
|
|
|
819
842
|
);
|
|
820
843
|
}
|
|
821
844
|
}, [result, onError]);
|
|
822
|
-
const
|
|
823
|
-
|
|
824
|
-
|
|
845
|
+
const showStepper = bundle?.widgetConfig.mixMatchShowQuantitySelector !== false;
|
|
846
|
+
const distinctSelectedProducts = useMemo5(() => {
|
|
847
|
+
const ids = /* @__PURE__ */ new Set();
|
|
848
|
+
for (const sel of selections.values()) {
|
|
849
|
+
if (sel.quantity > 0) ids.add(sel.productId);
|
|
850
|
+
}
|
|
851
|
+
return ids;
|
|
852
|
+
}, [selections]);
|
|
853
|
+
const totalQuantity = useMemo5(
|
|
854
|
+
() => Array.from(selections.values()).reduce((sum, s) => sum + s.quantity, 0),
|
|
855
|
+
[selections]
|
|
825
856
|
);
|
|
826
|
-
const
|
|
827
|
-
const
|
|
857
|
+
const requiredPicks = bundle?.minQuantity ?? 0;
|
|
858
|
+
const meetsMinPicks = distinctSelectedProducts.size >= requiredPicks;
|
|
859
|
+
const valid = !!bundle && meetsMinPicks;
|
|
860
|
+
const remaining = Math.max(0, requiredPicks - distinctSelectedProducts.size);
|
|
861
|
+
const validationMessage = valid ? null : remaining > 0 ? `Pick ${remaining} more product${remaining === 1 ? "" : "s"}` : null;
|
|
862
|
+
const selectVariant = useCallback5(
|
|
828
863
|
(productId, variant) => {
|
|
864
|
+
if (!bundle) return;
|
|
865
|
+
const rule = ruleFor(bundle, productId);
|
|
866
|
+
const maxAddable = maxAddableQuantity(variant, rule.max, 0);
|
|
867
|
+
if (maxAddable < rule.min) return;
|
|
829
868
|
setSelections((prev) => {
|
|
830
869
|
const next = new Map(prev);
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
next.set(key, {
|
|
837
|
-
productId,
|
|
838
|
-
variantId: variant.id,
|
|
839
|
-
quantity: configuredQty
|
|
840
|
-
});
|
|
841
|
-
}
|
|
870
|
+
next.set(`${productId}:${variant.id}`, {
|
|
871
|
+
productId,
|
|
872
|
+
variantId: variant.id,
|
|
873
|
+
quantity: rule.min
|
|
874
|
+
});
|
|
842
875
|
return next;
|
|
843
876
|
});
|
|
844
877
|
},
|
|
845
878
|
[bundle]
|
|
846
879
|
);
|
|
847
|
-
const
|
|
848
|
-
(
|
|
880
|
+
const deselect = useCallback5((productId, variantId) => {
|
|
881
|
+
setSelections((prev) => {
|
|
882
|
+
if (!prev.has(`${productId}:${variantId}`)) return prev;
|
|
883
|
+
const next = new Map(prev);
|
|
884
|
+
next.delete(`${productId}:${variantId}`);
|
|
885
|
+
return next;
|
|
886
|
+
});
|
|
887
|
+
}, []);
|
|
888
|
+
const setQuantity = useCallback5(
|
|
889
|
+
(productId, variant, quantity) => {
|
|
890
|
+
if (!bundle) return;
|
|
891
|
+
const rule = ruleFor(bundle, productId);
|
|
892
|
+
const maxAddable = maxAddableQuantity(variant, rule.max, 0);
|
|
893
|
+
const key = `${productId}:${variant.id}`;
|
|
849
894
|
setSelections((prev) => {
|
|
850
895
|
const next = new Map(prev);
|
|
851
|
-
|
|
852
|
-
if (quantity <= 0) {
|
|
896
|
+
if (quantity < rule.min || maxAddable < rule.min) {
|
|
853
897
|
next.delete(key);
|
|
854
|
-
|
|
855
|
-
next.set(key, { productId, variantId, quantity });
|
|
898
|
+
return next;
|
|
856
899
|
}
|
|
900
|
+
const clamped = Math.min(quantity, maxAddable);
|
|
901
|
+
next.set(key, {
|
|
902
|
+
productId,
|
|
903
|
+
variantId: variant.id,
|
|
904
|
+
quantity: clamped
|
|
905
|
+
});
|
|
857
906
|
return next;
|
|
858
907
|
});
|
|
859
908
|
},
|
|
860
|
-
[]
|
|
909
|
+
[bundle]
|
|
861
910
|
);
|
|
862
911
|
const handleAddToCart = useCallback5(async () => {
|
|
863
|
-
if (!bundle || !
|
|
864
|
-
const
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
}
|
|
912
|
+
if (!bundle || !valid) return;
|
|
913
|
+
const byVariant = /* @__PURE__ */ new Map();
|
|
914
|
+
for (const sel of selections.values()) {
|
|
915
|
+
if (sel.quantity <= 0) continue;
|
|
916
|
+
byVariant.set(
|
|
917
|
+
sel.variantId,
|
|
918
|
+
(byVariant.get(sel.variantId) ?? 0) + sel.quantity
|
|
919
|
+
);
|
|
920
|
+
}
|
|
921
|
+
const lines = Array.from(byVariant.entries()).map(
|
|
922
|
+
([variantId, quantity]) => ({
|
|
923
|
+
merchandiseId: variantId,
|
|
924
|
+
quantity,
|
|
925
|
+
attributes: [
|
|
926
|
+
{ key: "_lime_bundle_gid", value: bundle.id },
|
|
927
|
+
{ key: "_lime_bundle_type", value: bundle.bundleType }
|
|
928
|
+
]
|
|
929
|
+
})
|
|
930
|
+
);
|
|
872
931
|
setAddingToCart(true);
|
|
873
932
|
setCartError(null);
|
|
874
933
|
try {
|
|
@@ -894,15 +953,7 @@ function MixMatchBundle(props) {
|
|
|
894
953
|
} finally {
|
|
895
954
|
setAddingToCart(false);
|
|
896
955
|
}
|
|
897
|
-
}, [
|
|
898
|
-
bundle,
|
|
899
|
-
selections,
|
|
900
|
-
validation.valid,
|
|
901
|
-
onAddToCart,
|
|
902
|
-
onError,
|
|
903
|
-
trackAddToCart,
|
|
904
|
-
totalQuantity
|
|
905
|
-
]);
|
|
956
|
+
}, [bundle, selections, valid, onAddToCart, onError, trackAddToCart, totalQuantity]);
|
|
906
957
|
if (result.status === "loading") {
|
|
907
958
|
return /* @__PURE__ */ jsxs3("div", { className: `lb-bundle lb-bundle--loading ${className ?? ""}`, children: [
|
|
908
959
|
/* @__PURE__ */ jsx3("div", { className: "lb-skeleton lb-skeleton--title" }),
|
|
@@ -925,27 +976,29 @@ function MixMatchBundle(props) {
|
|
|
925
976
|
children: [
|
|
926
977
|
/* @__PURE__ */ jsx3("h3", { className: "lb-bundle__title", children: bundle.title }),
|
|
927
978
|
bundle.discountLabel && /* @__PURE__ */ jsx3("span", { className: "lb-bundle__discount-badge", children: bundle.discountLabel }),
|
|
928
|
-
/* @__PURE__ */ jsx3("p", { className: "lb-bundle__instructions", children:
|
|
979
|
+
/* @__PURE__ */ jsx3("p", { className: "lb-bundle__instructions", children: requiredPicks > 0 ? `Pick ${requiredPicks} product${requiredPicks === 1 ? "" : "s"}` : "Pick your products" }),
|
|
929
980
|
/* @__PURE__ */ jsx3("div", { className: "lb-bundle__products lb-bundle__products--selectable", children: bundle.products.map((product) => /* @__PURE__ */ jsx3(
|
|
930
981
|
MixMatchProductRow,
|
|
931
982
|
{
|
|
932
983
|
bundle,
|
|
933
984
|
product,
|
|
934
985
|
currency,
|
|
986
|
+
showStepper,
|
|
935
987
|
selections,
|
|
936
|
-
|
|
937
|
-
|
|
988
|
+
onSelect: selectVariant,
|
|
989
|
+
onDeselect: deselect,
|
|
990
|
+
onSetQuantity: setQuantity
|
|
938
991
|
},
|
|
939
992
|
product.id
|
|
940
993
|
)) }),
|
|
941
|
-
|
|
994
|
+
validationMessage && /* @__PURE__ */ jsx3("p", { className: "lb-bundle__validation", role: "status", children: validationMessage }),
|
|
942
995
|
cartError && /* @__PURE__ */ jsx3("p", { className: "lb-bundle__error", role: "alert", children: cartError }),
|
|
943
996
|
/* @__PURE__ */ jsx3(
|
|
944
997
|
"button",
|
|
945
998
|
{
|
|
946
999
|
className: "lb-bundle__cta",
|
|
947
1000
|
onClick: handleAddToCart,
|
|
948
|
-
disabled: addingToCart || !
|
|
1001
|
+
disabled: addingToCart || !valid,
|
|
949
1002
|
"aria-busy": addingToCart,
|
|
950
1003
|
children: addingToCart ? "Adding..." : bundle.widgetConfig.cta.ctaText ?? `Add ${totalQuantity} Items to Cart`
|
|
951
1004
|
}
|
|
@@ -958,9 +1011,11 @@ function MixMatchProductRow({
|
|
|
958
1011
|
bundle,
|
|
959
1012
|
product,
|
|
960
1013
|
currency,
|
|
1014
|
+
showStepper,
|
|
961
1015
|
selections,
|
|
962
|
-
|
|
963
|
-
|
|
1016
|
+
onSelect,
|
|
1017
|
+
onDeselect,
|
|
1018
|
+
onSetQuantity
|
|
964
1019
|
}) {
|
|
965
1020
|
const variants = product.variants.nodes;
|
|
966
1021
|
const optionNames = useMemo5(() => {
|
|
@@ -968,20 +1023,12 @@ function MixMatchProductRow({
|
|
|
968
1023
|
return first ? first.selectedOptions.map((o) => o.name) : [];
|
|
969
1024
|
}, [variants]);
|
|
970
1025
|
const showPicker = variants.length > 1 && optionNames.length > 0;
|
|
1026
|
+
const rule = ruleFor(bundle, product.id);
|
|
971
1027
|
const { selectedValues, selectedVariant, setOptionValue, optionsFor } = useVariantSelection({ variants, optionNames });
|
|
972
|
-
const displayVariant = selectedVariant ?? variants.find(
|
|
973
|
-
(v) => isVariantFulfillable2(v, resolveBundleQty2(bundle, product.id, v.id))
|
|
974
|
-
) ?? variants[0] ?? null;
|
|
1028
|
+
const displayVariant = selectedVariant ?? variants.find((v) => isVariantFulfillable2(v, rule.min)) ?? variants[0] ?? null;
|
|
975
1029
|
if (!displayVariant) return null;
|
|
976
|
-
const
|
|
977
|
-
|
|
978
|
-
product.id,
|
|
979
|
-
displayVariant.id
|
|
980
|
-
);
|
|
981
|
-
const displayVariantFulfillable = isVariantFulfillable2(
|
|
982
|
-
displayVariant,
|
|
983
|
-
displayVariantQty
|
|
984
|
-
);
|
|
1030
|
+
const fulfillable = isVariantFulfillable2(displayVariant, rule.min);
|
|
1031
|
+
const maxAddable = maxAddableQuantity(displayVariant, rule.max, 0);
|
|
985
1032
|
const key = `${product.id}:${displayVariant.id}`;
|
|
986
1033
|
const selected = selections.get(key);
|
|
987
1034
|
const thumbImage = displayVariant.image ?? product.featuredImage;
|
|
@@ -1010,7 +1057,7 @@ function MixMatchProductRow({
|
|
|
1010
1057
|
unitPriceText && /* @__PURE__ */ jsx3("p", { className: "lb-bundle__product-unit-price", children: unitPriceText }),
|
|
1011
1058
|
shouldShowLowStockBadge2(
|
|
1012
1059
|
displayVariant,
|
|
1013
|
-
|
|
1060
|
+
rule.min,
|
|
1014
1061
|
bundle.widgetConfig.lowStockThreshold,
|
|
1015
1062
|
bundle.widgetConfig.showLowStockBadge
|
|
1016
1063
|
) && /* @__PURE__ */ jsxs3("span", { className: "lb-bundle-low-stock-badge", children: [
|
|
@@ -1036,39 +1083,58 @@ function MixMatchProductRow({
|
|
|
1036
1083
|
);
|
|
1037
1084
|
}) })
|
|
1038
1085
|
] }),
|
|
1039
|
-
/* @__PURE__ */ jsx3("div", { className: "lb-bundle__product-actions", children: selected ? /* @__PURE__ */ jsxs3("div", { className: "lb-bundle__quantity-control", children: [
|
|
1086
|
+
/* @__PURE__ */ jsx3("div", { className: "lb-bundle__product-actions", children: selected ? showStepper ? /* @__PURE__ */ jsxs3("div", { className: "lb-bundle__quantity-control", children: [
|
|
1040
1087
|
/* @__PURE__ */ jsx3(
|
|
1041
1088
|
"button",
|
|
1042
1089
|
{
|
|
1043
1090
|
"aria-label": `Decrease ${product.title}`,
|
|
1044
|
-
onClick: () =>
|
|
1091
|
+
onClick: () => onSetQuantity(
|
|
1045
1092
|
product.id,
|
|
1046
|
-
displayVariant
|
|
1093
|
+
displayVariant,
|
|
1047
1094
|
selected.quantity - 1
|
|
1048
1095
|
),
|
|
1096
|
+
disabled: selected.quantity <= rule.min,
|
|
1049
1097
|
children: "\u2212"
|
|
1050
1098
|
}
|
|
1051
1099
|
),
|
|
1052
|
-
/* @__PURE__ */ jsx3("span", { children: selected.quantity }),
|
|
1100
|
+
/* @__PURE__ */ jsx3("span", { "aria-live": "polite", children: selected.quantity }),
|
|
1053
1101
|
/* @__PURE__ */ jsx3(
|
|
1054
1102
|
"button",
|
|
1055
1103
|
{
|
|
1056
1104
|
"aria-label": `Increase ${product.title}`,
|
|
1057
|
-
onClick: () =>
|
|
1105
|
+
onClick: () => onSetQuantity(
|
|
1058
1106
|
product.id,
|
|
1059
|
-
displayVariant
|
|
1107
|
+
displayVariant,
|
|
1060
1108
|
selected.quantity + 1
|
|
1061
1109
|
),
|
|
1110
|
+
disabled: selected.quantity >= maxAddable,
|
|
1062
1111
|
children: "+"
|
|
1063
1112
|
}
|
|
1113
|
+
),
|
|
1114
|
+
/* @__PURE__ */ jsx3(
|
|
1115
|
+
"button",
|
|
1116
|
+
{
|
|
1117
|
+
className: "lb-bundle__remove-btn",
|
|
1118
|
+
"aria-label": `Remove ${product.title}`,
|
|
1119
|
+
onClick: () => onDeselect(product.id, displayVariant.id),
|
|
1120
|
+
children: "Remove"
|
|
1121
|
+
}
|
|
1064
1122
|
)
|
|
1065
1123
|
] }) : /* @__PURE__ */ jsx3(
|
|
1124
|
+
"button",
|
|
1125
|
+
{
|
|
1126
|
+
className: "lb-bundle__select-btn lb-bundle__select-btn--selected",
|
|
1127
|
+
"aria-label": `Remove ${product.title}`,
|
|
1128
|
+
onClick: () => onDeselect(product.id, displayVariant.id),
|
|
1129
|
+
children: "Selected"
|
|
1130
|
+
}
|
|
1131
|
+
) : /* @__PURE__ */ jsx3(
|
|
1066
1132
|
"button",
|
|
1067
1133
|
{
|
|
1068
1134
|
className: "lb-bundle__select-btn",
|
|
1069
|
-
onClick: () =>
|
|
1070
|
-
disabled: !
|
|
1071
|
-
children:
|
|
1135
|
+
onClick: () => onSelect(product.id, displayVariant),
|
|
1136
|
+
disabled: !fulfillable || maxAddable <= 0,
|
|
1137
|
+
children: fulfillable && maxAddable > 0 ? "Select" : "Sold out"
|
|
1072
1138
|
}
|
|
1073
1139
|
) })
|
|
1074
1140
|
]
|
|
@@ -1421,7 +1487,7 @@ import {
|
|
|
1421
1487
|
computeBundleSaleCents,
|
|
1422
1488
|
calculateTierSavings as calculateTierSavings2,
|
|
1423
1489
|
getActiveTier as getActiveTier2,
|
|
1424
|
-
validateQuantity
|
|
1490
|
+
validateQuantity,
|
|
1425
1491
|
transformImageUrl,
|
|
1426
1492
|
formatCountdown,
|
|
1427
1493
|
formatMoney as formatMoney4,
|
|
@@ -1489,6 +1555,6 @@ export {
|
|
|
1489
1555
|
useBundlesForProduct,
|
|
1490
1556
|
useVariantSelection,
|
|
1491
1557
|
useWidgetConfigVars,
|
|
1492
|
-
|
|
1558
|
+
validateQuantity
|
|
1493
1559
|
};
|
|
1494
1560
|
//# sourceMappingURL=index.js.map
|