@lime-bundles/react 6.2.0 → 7.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/index.cjs +307 -305
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +23 -41
- package/dist/index.d.ts +23 -41
- package/dist/index.js +142 -142
- package/dist/index.js.map +1 -1
- package/dist/styles.css +35 -7
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -20,58 +20,55 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
-
BUNDLES_FOR_PRODUCT_QUERY: () =>
|
|
24
|
-
BUNDLE_METAOBJECT_QUERY: () =>
|
|
23
|
+
BUNDLES_FOR_PRODUCT_QUERY: () => import_core17.BUNDLES_FOR_PRODUCT_QUERY,
|
|
24
|
+
BUNDLE_METAOBJECT_QUERY: () => import_core17.BUNDLE_METAOBJECT_QUERY,
|
|
25
25
|
BogoBundle: () => BogoBundle,
|
|
26
26
|
BundleCountdown: () => BundleCountdown,
|
|
27
|
-
BundleParseError: () =>
|
|
28
|
-
DEFAULT_OUT_OF_STOCK_BEHAVIOR: () => import_core18.DEFAULT_OUT_OF_STOCK_BEHAVIOR,
|
|
27
|
+
BundleParseError: () => import_core16.BundleParseError,
|
|
29
28
|
FixedBundle: () => FixedBundle,
|
|
30
29
|
MixMatchBundle: () => MixMatchBundle,
|
|
31
30
|
MultiStepBundle: () => MultiStepBundle,
|
|
32
|
-
SHOP_SETTINGS_QUERY: () =>
|
|
33
|
-
StorefrontApiError: () =>
|
|
31
|
+
SHOP_SETTINGS_QUERY: () => import_core17.SHOP_SETTINGS_QUERY,
|
|
32
|
+
StorefrontApiError: () => import_core16.StorefrontApiError,
|
|
34
33
|
VariantDropdown: () => VariantDropdown,
|
|
35
34
|
VolumeBundle: () => VolumeBundle,
|
|
36
|
-
WIDGET_CONFIG_DEFAULTS: () =>
|
|
37
|
-
applyWidgetConfigVars: () =>
|
|
38
|
-
calculateDiscount: () =>
|
|
39
|
-
calculateTierSavings: () =>
|
|
40
|
-
computeBundleSaleCents: () =>
|
|
41
|
-
computeFixedPricing: () =>
|
|
42
|
-
createStorefrontClient: () =>
|
|
35
|
+
WIDGET_CONFIG_DEFAULTS: () => import_core17.WIDGET_CONFIG_DEFAULTS,
|
|
36
|
+
applyWidgetConfigVars: () => import_core17.applyWidgetConfigVars,
|
|
37
|
+
calculateDiscount: () => import_core17.calculateDiscount,
|
|
38
|
+
calculateTierSavings: () => import_core17.calculateTierSavings,
|
|
39
|
+
computeBundleSaleCents: () => import_core17.computeBundleSaleCents,
|
|
40
|
+
computeFixedPricing: () => import_core17.computeFixedPricing,
|
|
41
|
+
createStorefrontClient: () => import_core17.createStorefrontClient,
|
|
43
42
|
fetchBundleData: () => fetchBundleData,
|
|
44
|
-
fetchBundlesForProduct: () =>
|
|
43
|
+
fetchBundlesForProduct: () => import_core17.fetchBundlesForProduct,
|
|
45
44
|
fetchShopCustomCss: () => fetchShopCustomCss,
|
|
46
45
|
fetchShopSettings: () => fetchShopSettings,
|
|
47
46
|
fetchShopSettingsOrDefault: () => fetchShopSettingsOrDefault,
|
|
48
|
-
formatCents: () =>
|
|
49
|
-
formatCountdown: () =>
|
|
50
|
-
formatMoney: () =>
|
|
51
|
-
formatUnitPrice: () =>
|
|
52
|
-
getActiveTier: () =>
|
|
53
|
-
injectCustomCss: () =>
|
|
54
|
-
mergeWidgetConfig: () =>
|
|
55
|
-
observeImpression: () =>
|
|
56
|
-
parseCents: () =>
|
|
57
|
-
parseMetaobjectBundle: () =>
|
|
58
|
-
parseMetaobjectBundleStrict: () =>
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
transformImageUrl: () => import_core18.transformImageUrl,
|
|
47
|
+
formatCents: () => import_core17.formatCents,
|
|
48
|
+
formatCountdown: () => import_core17.formatCountdown,
|
|
49
|
+
formatMoney: () => import_core17.formatMoney,
|
|
50
|
+
formatUnitPrice: () => import_core17.formatUnitPrice,
|
|
51
|
+
getActiveTier: () => import_core17.getActiveTier,
|
|
52
|
+
injectCustomCss: () => import_core17.injectCustomCss,
|
|
53
|
+
mergeWidgetConfig: () => import_core17.mergeWidgetConfig,
|
|
54
|
+
observeImpression: () => import_core17.observeImpression,
|
|
55
|
+
parseCents: () => import_core17.parseCents,
|
|
56
|
+
parseMetaobjectBundle: () => import_core17.parseMetaobjectBundle,
|
|
57
|
+
parseMetaobjectBundleStrict: () => import_core17.parseMetaobjectBundleStrict,
|
|
58
|
+
percentageDiscountUnit: () => import_core17.percentageDiscountUnit,
|
|
59
|
+
productsForStep: () => import_core17.productsForStep,
|
|
60
|
+
reportAddToCart: () => import_core17.reportAddToCart,
|
|
61
|
+
reportImpression: () => import_core17.reportImpression,
|
|
62
|
+
sanitizeCustomCss: () => import_core17.sanitizeCustomCss,
|
|
63
|
+
stepHeadroom: () => import_core17.stepHeadroom,
|
|
64
|
+
thumbnailRatioVars: () => import_core17.thumbnailRatioVars,
|
|
65
|
+
transformImageUrl: () => import_core17.transformImageUrl,
|
|
68
66
|
useAnalytics: () => useAnalytics,
|
|
69
67
|
useBundleData: () => useBundleData,
|
|
70
68
|
useBundlesForProduct: () => useBundlesForProduct,
|
|
71
|
-
useShopSettings: () => useShopSettings,
|
|
72
69
|
useVariantSelection: () => useVariantSelection,
|
|
73
70
|
useWidgetConfigVars: () => useWidgetConfigVars,
|
|
74
|
-
validateQuantity: () =>
|
|
71
|
+
validateQuantity: () => import_core17.validateQuantity
|
|
75
72
|
});
|
|
76
73
|
module.exports = __toCommonJS(index_exports);
|
|
77
74
|
|
|
@@ -141,8 +138,7 @@ async function fetchBundleDataWithWarnings(options) {
|
|
|
141
138
|
// src/fetchShopSettings.ts
|
|
142
139
|
var import_core2 = require("@lime-bundles/core");
|
|
143
140
|
var FALLBACK = {
|
|
144
|
-
customCss: null
|
|
145
|
-
outOfStockBehavior: import_core2.DEFAULT_OUT_OF_STOCK_BEHAVIOR
|
|
141
|
+
customCss: null
|
|
146
142
|
};
|
|
147
143
|
var cache = /* @__PURE__ */ new Map();
|
|
148
144
|
async function fetchShopSettings(options) {
|
|
@@ -160,10 +156,7 @@ async function fetchShopSettings(options) {
|
|
|
160
156
|
{ signal: options.signal }
|
|
161
157
|
);
|
|
162
158
|
return {
|
|
163
|
-
customCss: data.shop?.customCss?.value ?? null
|
|
164
|
-
outOfStockBehavior: (0, import_core2.parseOutOfStockBehavior)(
|
|
165
|
-
data.shop?.outOfStockBehavior?.value
|
|
166
|
-
)
|
|
159
|
+
customCss: data.shop?.customCss?.value ?? null
|
|
167
160
|
};
|
|
168
161
|
})();
|
|
169
162
|
cache.set(options.shopDomain, promise);
|
|
@@ -317,19 +310,25 @@ function useWidgetConfigVars(config) {
|
|
|
317
310
|
var import_react4 = require("react");
|
|
318
311
|
var import_core6 = require("@lime-bundles/core");
|
|
319
312
|
var { findVariantByOptions, isOptionValueAvailable, toPickerVariant } = import_core6.variants;
|
|
320
|
-
function defaultInitial(variants) {
|
|
321
|
-
return variants.find((v) => v.availableForSale) ?? variants[0] ?? null;
|
|
322
|
-
}
|
|
323
313
|
function useVariantSelection({
|
|
324
314
|
variants,
|
|
325
315
|
optionNames,
|
|
326
316
|
initialVariant,
|
|
327
|
-
optionValuesByPosition
|
|
317
|
+
optionValuesByPosition,
|
|
318
|
+
requiredQtyFor
|
|
328
319
|
}) {
|
|
329
|
-
const
|
|
330
|
-
() =>
|
|
331
|
-
[
|
|
320
|
+
const pickerVariants = (0, import_react4.useMemo)(
|
|
321
|
+
() => variants.map((v) => toPickerVariant(v, requiredQtyFor?.(v.id))),
|
|
322
|
+
[variants, requiredQtyFor]
|
|
332
323
|
);
|
|
324
|
+
const seed = (0, import_react4.useMemo)(() => {
|
|
325
|
+
if (initialVariant) return initialVariant;
|
|
326
|
+
const covered = pickerVariants.find((p) => p.available);
|
|
327
|
+
if (covered) {
|
|
328
|
+
return variants.find((v) => v.id === covered.id) ?? null;
|
|
329
|
+
}
|
|
330
|
+
return variants.find((v) => v.availableForSale) ?? variants[0] ?? null;
|
|
331
|
+
}, [initialVariant, variants, pickerVariants]);
|
|
333
332
|
const [selectedValues, setSelectedValues] = (0, import_react4.useState)(
|
|
334
333
|
() => seed ? seed.selectedOptions.map((o) => o.value) : optionNames.map(() => "")
|
|
335
334
|
);
|
|
@@ -337,10 +336,6 @@ function useVariantSelection({
|
|
|
337
336
|
if (!seed) return;
|
|
338
337
|
setSelectedValues(seed.selectedOptions.map((o) => o.value));
|
|
339
338
|
}, [seed?.id]);
|
|
340
|
-
const pickerVariants = (0, import_react4.useMemo)(
|
|
341
|
-
() => variants.map((v) => toPickerVariant(v)),
|
|
342
|
-
[variants]
|
|
343
|
-
);
|
|
344
339
|
const selectedVariant = (0, import_react4.useMemo)(() => {
|
|
345
340
|
if (selectedValues.length !== optionNames.length) return null;
|
|
346
341
|
const match = findVariantByOptions(pickerVariants, selectedValues);
|
|
@@ -968,7 +963,11 @@ function FixedProductRow({ bundle, product, currency, isOos, onVariantChange })
|
|
|
968
963
|
const showPicker = variants.length > 1 && optionNames.length > 0;
|
|
969
964
|
const { selectedValues, selectedVariant, setOptionValue, optionsFor } = useVariantSelection({
|
|
970
965
|
variants,
|
|
971
|
-
optionNames
|
|
966
|
+
optionNames,
|
|
967
|
+
// Quantity-aware dropdown: a variant that can't cover this row's
|
|
968
|
+
// bundle quantity greys out instead of walking the shopper into a
|
|
969
|
+
// cart Shopify caps at add time.
|
|
970
|
+
requiredQtyFor: (variantId) => (0, import_core8.resolveBundleQty)(bundle, product.id, variantId)
|
|
972
971
|
});
|
|
973
972
|
(0, import_react8.useEffect)(() => {
|
|
974
973
|
onVariantChange(product.id, selectedVariant ?? null);
|
|
@@ -1063,41 +1062,16 @@ function FixedProductRow({ bundle, product, currency, isOos, onVariantChange })
|
|
|
1063
1062
|
}
|
|
1064
1063
|
|
|
1065
1064
|
// src/components/MixMatchBundle.tsx
|
|
1066
|
-
var
|
|
1067
|
-
var
|
|
1068
|
-
|
|
1069
|
-
// src/hooks/useShopSettings.ts
|
|
1070
|
-
var import_react9 = require("react");
|
|
1071
|
-
var import_core9 = require("@lime-bundles/core");
|
|
1072
|
-
function useShopSettings(options) {
|
|
1073
|
-
const [outOfStockBehavior, setOutOfStockBehavior] = (0, import_react9.useState)(import_core9.DEFAULT_OUT_OF_STOCK_BEHAVIOR);
|
|
1074
|
-
const { shopDomain, storefrontAccessToken } = options;
|
|
1075
|
-
(0, import_react9.useEffect)(() => {
|
|
1076
|
-
const controller = new AbortController();
|
|
1077
|
-
let active = true;
|
|
1078
|
-
fetchShopSettingsOrDefault({
|
|
1079
|
-
shopDomain,
|
|
1080
|
-
storefrontAccessToken,
|
|
1081
|
-
signal: controller.signal
|
|
1082
|
-
}).then((settings) => {
|
|
1083
|
-
if (!active || controller.signal.aborted) return;
|
|
1084
|
-
setOutOfStockBehavior(settings.outOfStockBehavior);
|
|
1085
|
-
});
|
|
1086
|
-
return () => {
|
|
1087
|
-
active = false;
|
|
1088
|
-
controller.abort();
|
|
1089
|
-
};
|
|
1090
|
-
}, [shopDomain, storefrontAccessToken]);
|
|
1091
|
-
return { outOfStockBehavior };
|
|
1092
|
-
}
|
|
1065
|
+
var import_react12 = require("react");
|
|
1066
|
+
var import_core10 = require("@lime-bundles/core");
|
|
1093
1067
|
|
|
1094
1068
|
// src/components/MixMatchPicker.tsx
|
|
1095
|
-
var
|
|
1069
|
+
var import_react11 = require("react");
|
|
1096
1070
|
var import_react_dom = require("react-dom");
|
|
1097
|
-
var
|
|
1071
|
+
var import_core9 = require("@lime-bundles/core");
|
|
1098
1072
|
|
|
1099
1073
|
// src/hooks/useScrollLock.ts
|
|
1100
|
-
var
|
|
1074
|
+
var import_react9 = require("react");
|
|
1101
1075
|
var lockCount = 0;
|
|
1102
1076
|
var savedY = 0;
|
|
1103
1077
|
function lock() {
|
|
@@ -1126,7 +1100,7 @@ function unlock() {
|
|
|
1126
1100
|
window.scrollTo(0, savedY);
|
|
1127
1101
|
}
|
|
1128
1102
|
function useScrollLock(active) {
|
|
1129
|
-
(0,
|
|
1103
|
+
(0, import_react9.useEffect)(() => {
|
|
1130
1104
|
if (!active) return;
|
|
1131
1105
|
lock();
|
|
1132
1106
|
return unlock;
|
|
@@ -1134,7 +1108,7 @@ function useScrollLock(active) {
|
|
|
1134
1108
|
}
|
|
1135
1109
|
|
|
1136
1110
|
// src/hooks/useFocusTrap.ts
|
|
1137
|
-
var
|
|
1111
|
+
var import_react10 = require("react");
|
|
1138
1112
|
var FOCUSABLE_SELECTOR = [
|
|
1139
1113
|
"a[href]",
|
|
1140
1114
|
"button:not([disabled])",
|
|
@@ -1157,14 +1131,16 @@ function useFocusTrap({
|
|
|
1157
1131
|
active,
|
|
1158
1132
|
containerRef,
|
|
1159
1133
|
onEscape,
|
|
1134
|
+
initialFocusRef,
|
|
1160
1135
|
returnFocusRef
|
|
1161
1136
|
}) {
|
|
1162
|
-
const previouslyFocused = (0,
|
|
1163
|
-
(0,
|
|
1137
|
+
const previouslyFocused = (0, import_react10.useRef)(null);
|
|
1138
|
+
(0, import_react10.useEffect)(() => {
|
|
1164
1139
|
if (!active) return;
|
|
1165
1140
|
const container = containerRef.current;
|
|
1166
1141
|
previouslyFocused.current = document.activeElement ?? null;
|
|
1167
|
-
container
|
|
1142
|
+
const initial = initialFocusRef?.current ?? container;
|
|
1143
|
+
initial?.focus();
|
|
1168
1144
|
function onKeyDown(e) {
|
|
1169
1145
|
if (e.key === "Escape") {
|
|
1170
1146
|
e.preventDefault();
|
|
@@ -1208,7 +1184,7 @@ function ruleFor(bundle, productId, variantId) {
|
|
|
1208
1184
|
const vRule = bundle.variantRules[variantId];
|
|
1209
1185
|
if (vRule) return vRule;
|
|
1210
1186
|
}
|
|
1211
|
-
return bundle.productRules[productId] ??
|
|
1187
|
+
return bundle.productRules[productId] ?? import_core9.DEFAULT_PRODUCT_RULE;
|
|
1212
1188
|
}
|
|
1213
1189
|
function variantRuleFor(bundle, variantId) {
|
|
1214
1190
|
return variantId ? bundle.variantRules[variantId] : void 0;
|
|
@@ -1216,7 +1192,7 @@ function variantRuleFor(bundle, variantId) {
|
|
|
1216
1192
|
function sanitizeId(gid) {
|
|
1217
1193
|
return gid.replace(/[^a-zA-Z0-9_-]/g, "-");
|
|
1218
1194
|
}
|
|
1219
|
-
function buildEligibleProducts(bundle
|
|
1195
|
+
function buildEligibleProducts(bundle) {
|
|
1220
1196
|
const result = [];
|
|
1221
1197
|
const seen = /* @__PURE__ */ new Set();
|
|
1222
1198
|
for (const product of bundle.products) {
|
|
@@ -1224,10 +1200,9 @@ function buildEligibleProducts(bundle, oosBehavior) {
|
|
|
1224
1200
|
seen.add(product.id);
|
|
1225
1201
|
const rule = ruleFor(bundle, product.id);
|
|
1226
1202
|
const available = product.variants.nodes.filter(
|
|
1227
|
-
(v) => (0,
|
|
1203
|
+
(v) => (0, import_core9.isVariantFulfillable)(v, rule.min)
|
|
1228
1204
|
);
|
|
1229
1205
|
const isOos = available.length === 0;
|
|
1230
|
-
if (isOos && oosBehavior === "hide") continue;
|
|
1231
1206
|
result.push({
|
|
1232
1207
|
product,
|
|
1233
1208
|
variants: product.variants.nodes,
|
|
@@ -1242,7 +1217,6 @@ function normalizeText(str) {
|
|
|
1242
1217
|
}
|
|
1243
1218
|
var FILTERS_ALL = "__all__";
|
|
1244
1219
|
function MixMatchPicker({
|
|
1245
|
-
outOfStockBehavior,
|
|
1246
1220
|
bundle,
|
|
1247
1221
|
currency,
|
|
1248
1222
|
requiredQty,
|
|
@@ -1258,17 +1232,18 @@ function MixMatchPicker({
|
|
|
1258
1232
|
styleVarsRef
|
|
1259
1233
|
}) {
|
|
1260
1234
|
const wc = bundle.widgetConfig;
|
|
1261
|
-
const overlayRef = (0,
|
|
1262
|
-
const dialogRef = (0,
|
|
1263
|
-
const
|
|
1264
|
-
const
|
|
1265
|
-
const [
|
|
1266
|
-
const [
|
|
1267
|
-
const
|
|
1268
|
-
|
|
1269
|
-
|
|
1235
|
+
const overlayRef = (0, import_react11.useRef)(null);
|
|
1236
|
+
const dialogRef = (0, import_react11.useRef)(null);
|
|
1237
|
+
const closeBtnRef = (0, import_react11.useRef)(null);
|
|
1238
|
+
const mouseDownTarget = (0, import_react11.useRef)(null);
|
|
1239
|
+
const [open, setOpen] = (0, import_react11.useState)(false);
|
|
1240
|
+
const [query, setQuery] = (0, import_react11.useState)("");
|
|
1241
|
+
const [activeType, setActiveType] = (0, import_react11.useState)(FILTERS_ALL);
|
|
1242
|
+
const eligible = (0, import_react11.useMemo)(
|
|
1243
|
+
() => buildEligibleProducts(bundle),
|
|
1244
|
+
[bundle]
|
|
1270
1245
|
);
|
|
1271
|
-
const productTypes = (0,
|
|
1246
|
+
const productTypes = (0, import_react11.useMemo)(() => {
|
|
1272
1247
|
const types = [];
|
|
1273
1248
|
const seen = /* @__PURE__ */ new Set();
|
|
1274
1249
|
for (const ep of eligible) {
|
|
@@ -1284,7 +1259,7 @@ function MixMatchPicker({
|
|
|
1284
1259
|
const totalUnits = selections.reduce((sum, s) => sum + s.quantity, 0);
|
|
1285
1260
|
const atCapacity = totalUnits >= requiredQty;
|
|
1286
1261
|
const shownUnits = Math.min(totalUnits, requiredQty);
|
|
1287
|
-
(0,
|
|
1262
|
+
(0, import_react11.useEffect)(() => {
|
|
1288
1263
|
const id = requestAnimationFrame(() => setOpen(true));
|
|
1289
1264
|
return () => cancelAnimationFrame(id);
|
|
1290
1265
|
}, []);
|
|
@@ -1292,6 +1267,7 @@ function MixMatchPicker({
|
|
|
1292
1267
|
useFocusTrap({
|
|
1293
1268
|
active: true,
|
|
1294
1269
|
containerRef: dialogRef,
|
|
1270
|
+
initialFocusRef: closeBtnRef,
|
|
1295
1271
|
onEscape: onClose
|
|
1296
1272
|
});
|
|
1297
1273
|
const remaining = Math.max(0, requiredQty - shownUnits);
|
|
@@ -1300,14 +1276,25 @@ function MixMatchPicker({
|
|
|
1300
1276
|
const pct = bundle.discountConfig.discountType === "percentage" && bundle.discountConfig.discountValue ? Math.round(bundle.discountConfig.discountValue) : 0;
|
|
1301
1277
|
return pct > 0 ? `Choose ${remaining} more to unlock ${pct}% off` : `Choose ${remaining} more to complete your bundle`;
|
|
1302
1278
|
})();
|
|
1303
|
-
const
|
|
1279
|
+
const [selectedAtOpen] = (0, import_react11.useState)(
|
|
1280
|
+
() => new Set(selections.map((s) => s.productId))
|
|
1281
|
+
);
|
|
1282
|
+
const ordered = (0, import_react11.useMemo)(() => {
|
|
1283
|
+
const front = [];
|
|
1284
|
+
const back = [];
|
|
1285
|
+
for (const ep of eligible) {
|
|
1286
|
+
(selectedAtOpen.has(ep.product.id) ? back : front).push(ep);
|
|
1287
|
+
}
|
|
1288
|
+
return front.concat(back);
|
|
1289
|
+
}, [eligible, selectedAtOpen]);
|
|
1290
|
+
const filtered = (0, import_react11.useMemo)(() => {
|
|
1304
1291
|
const nq = normalizeText(query.trim());
|
|
1305
|
-
return
|
|
1292
|
+
return ordered.filter((ep) => {
|
|
1306
1293
|
const matchesQuery = !nq || normalizeText(ep.product.title).includes(nq);
|
|
1307
1294
|
const matchesType = activeType === FILTERS_ALL || (ep.product.productType ?? "") === activeType;
|
|
1308
1295
|
return matchesQuery && matchesType;
|
|
1309
1296
|
});
|
|
1310
|
-
}, [
|
|
1297
|
+
}, [ordered, query, activeType]);
|
|
1311
1298
|
const showEmpty = filtered.length === 0 && query.trim().length > 0;
|
|
1312
1299
|
function onOverlayMouseDown(e) {
|
|
1313
1300
|
mouseDownTarget.current = e.target;
|
|
@@ -1356,6 +1343,7 @@ function MixMatchPicker({
|
|
|
1356
1343
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1357
1344
|
"button",
|
|
1358
1345
|
{
|
|
1346
|
+
ref: closeBtnRef,
|
|
1359
1347
|
type: "button",
|
|
1360
1348
|
className: "lb-mix-match__modal-close",
|
|
1361
1349
|
"data-modal-close": true,
|
|
@@ -1510,16 +1498,16 @@ function MixMatchPickerRow({
|
|
|
1510
1498
|
}) {
|
|
1511
1499
|
const { product, variants, isOos } = eligible;
|
|
1512
1500
|
const rule = ruleFor(bundle, product.id);
|
|
1513
|
-
const optionNames = (0,
|
|
1501
|
+
const optionNames = (0, import_react11.useMemo)(() => {
|
|
1514
1502
|
const first = variants[0];
|
|
1515
1503
|
return first ? first.selectedOptions.map((o) => o.name) : [];
|
|
1516
1504
|
}, [variants]);
|
|
1517
1505
|
const showPicker = variants.length > 1 && optionNames.length > 0;
|
|
1518
1506
|
const { selectedValues, selectedVariant, setOptionValue, optionsFor } = useVariantSelection({ variants, optionNames });
|
|
1519
|
-
const currentVariant = selectedVariant ?? variants.find((v) => (0,
|
|
1507
|
+
const currentVariant = selectedVariant ?? variants.find((v) => (0, import_core9.isVariantFulfillable)(v, rule.min)) ?? eligible.firstAvailableVariant ?? variants[0] ?? null;
|
|
1520
1508
|
const vRule = ruleFor(bundle, product.id, currentVariant?.id);
|
|
1521
1509
|
const variantMax = variantRuleFor(bundle, currentVariant?.id)?.max;
|
|
1522
|
-
const alreadyInBundle = (0,
|
|
1510
|
+
const alreadyInBundle = (0, import_react11.useMemo)(() => {
|
|
1523
1511
|
if (!currentVariant) return 0;
|
|
1524
1512
|
let sum = 0;
|
|
1525
1513
|
for (const s of selections) {
|
|
@@ -1529,13 +1517,13 @@ function MixMatchPickerRow({
|
|
|
1529
1517
|
}
|
|
1530
1518
|
return sum;
|
|
1531
1519
|
}, [selections, product.id, currentVariant]);
|
|
1532
|
-
const committedSelection = (0,
|
|
1520
|
+
const committedSelection = (0, import_react11.useMemo)(
|
|
1533
1521
|
() => currentVariant ? selections.find((s) => s.variantId === currentVariant.id) ?? null : null,
|
|
1534
1522
|
[selections, currentVariant]
|
|
1535
1523
|
);
|
|
1536
1524
|
const variantInBundle = committedSelection !== null;
|
|
1537
1525
|
const committedQty = committedSelection?.quantity ?? 0;
|
|
1538
|
-
const productOtherUnits = (0,
|
|
1526
|
+
const productOtherUnits = (0, import_react11.useMemo)(() => {
|
|
1539
1527
|
if (!currentVariant) return 0;
|
|
1540
1528
|
let sum = 0;
|
|
1541
1529
|
for (const s of selections) {
|
|
@@ -1552,23 +1540,23 @@ function MixMatchPickerRow({
|
|
|
1552
1540
|
committedQty + remainingUnits,
|
|
1553
1541
|
variantMax ?? Infinity
|
|
1554
1542
|
);
|
|
1555
|
-
const cap = currentVariant ? (0,
|
|
1543
|
+
const cap = currentVariant ? (0, import_core9.maxAddableQuantity)(
|
|
1556
1544
|
currentVariant,
|
|
1557
1545
|
spotCeiling,
|
|
1558
1546
|
Math.max(0, alreadyInBundle - committedQty)
|
|
1559
1547
|
) : 0;
|
|
1560
1548
|
const stepperMax = Math.max(vRule.min, cap);
|
|
1561
|
-
const [qty, setQty] = (0,
|
|
1562
|
-
(0,
|
|
1549
|
+
const [qty, setQty] = (0, import_react11.useState)(vRule.min);
|
|
1550
|
+
(0, import_react11.useEffect)(() => {
|
|
1563
1551
|
setQty((prev) => Math.max(vRule.min, Math.min(prev, stepperMax)));
|
|
1564
1552
|
}, [stepperMax, vRule.min]);
|
|
1565
1553
|
const currentVariantId = currentVariant?.id ?? null;
|
|
1566
|
-
(0,
|
|
1554
|
+
(0, import_react11.useEffect)(() => {
|
|
1567
1555
|
if (!variantInBundle) setQty(vRule.min);
|
|
1568
1556
|
}, [variantInBundle, currentVariantId, vRule.min]);
|
|
1569
1557
|
if (!currentVariant) return null;
|
|
1570
1558
|
const thumbImage = currentVariant.image ?? product.featuredImage ?? null;
|
|
1571
|
-
const unitPriceText = (0,
|
|
1559
|
+
const unitPriceText = (0, import_core9.formatUnitPrice)(
|
|
1572
1560
|
currentVariant.unitPrice,
|
|
1573
1561
|
currentVariant.unitPriceMeasurement,
|
|
1574
1562
|
currency
|
|
@@ -1638,8 +1626,8 @@ function MixMatchPickerRow({
|
|
|
1638
1626
|
}
|
|
1639
1627
|
) }),
|
|
1640
1628
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("p", { className: "lb-mix-match__modal-product-price", children: [
|
|
1641
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { "data-row-price-sale": true, children: (0,
|
|
1642
|
-
compareCents !== null && compareCents > priceCents && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("s", { className: "lb-mix-match__modal-product-compare", "data-row-compare": true, children: (0,
|
|
1629
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { "data-row-price-sale": true, children: (0, import_core9.formatMoney)(priceCents, currency) }),
|
|
1630
|
+
compareCents !== null && compareCents > priceCents && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("s", { className: "lb-mix-match__modal-product-compare", "data-row-compare": true, children: (0, import_core9.formatMoney)(compareCents, currency) })
|
|
1643
1631
|
] }),
|
|
1644
1632
|
unitPriceText && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "lb-mix-match__modal-product-unit-price lb-bundle-product-unit-price", children: unitPriceText }),
|
|
1645
1633
|
showPicker && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "lb-bundle-variant-option-groups", children: optionNames.map((optionName, optionIndex) => {
|
|
@@ -1790,7 +1778,7 @@ function ruleFor2(bundle, productId, variantId) {
|
|
|
1790
1778
|
const vRule = bundle.variantRules[variantId];
|
|
1791
1779
|
if (vRule) return vRule;
|
|
1792
1780
|
}
|
|
1793
|
-
return bundle.productRules[productId] ??
|
|
1781
|
+
return bundle.productRules[productId] ?? import_core10.DEFAULT_PRODUCT_RULE;
|
|
1794
1782
|
}
|
|
1795
1783
|
function variantRuleFor2(bundle, variantId) {
|
|
1796
1784
|
return variantId ? bundle.variantRules[variantId] : void 0;
|
|
@@ -1825,10 +1813,6 @@ function MixMatchBundle(props) {
|
|
|
1825
1813
|
storefrontAccessToken,
|
|
1826
1814
|
bundleGid
|
|
1827
1815
|
});
|
|
1828
|
-
const { outOfStockBehavior } = useShopSettings({
|
|
1829
|
-
shopDomain,
|
|
1830
|
-
storefrontAccessToken
|
|
1831
|
-
});
|
|
1832
1816
|
const { elementRef, trackAddToCart } = useAnalytics({
|
|
1833
1817
|
shopDomain,
|
|
1834
1818
|
appUrl: appUrl ?? `https://${shopDomain}`,
|
|
@@ -1836,16 +1820,16 @@ function MixMatchBundle(props) {
|
|
|
1836
1820
|
bundleType: "mix_match",
|
|
1837
1821
|
enabled: analyticsEnabled !== false
|
|
1838
1822
|
});
|
|
1839
|
-
const [selections, setSelections] = (0,
|
|
1840
|
-
const [seededFor, setSeededFor] = (0,
|
|
1841
|
-
const [pickerOpen, setPickerOpen] = (0,
|
|
1842
|
-
const [addingToCart, setAddingToCart] = (0,
|
|
1843
|
-
const [cartError, setCartError] = (0,
|
|
1823
|
+
const [selections, setSelections] = (0, import_react12.useState)([]);
|
|
1824
|
+
const [seededFor, setSeededFor] = (0, import_react12.useState)(null);
|
|
1825
|
+
const [pickerOpen, setPickerOpen] = (0, import_react12.useState)(false);
|
|
1826
|
+
const [addingToCart, setAddingToCart] = (0, import_react12.useState)(false);
|
|
1827
|
+
const [cartError, setCartError] = (0, import_react12.useState)(null);
|
|
1844
1828
|
const bundle = result.status === "success" && result.bundle.bundleType === "mix_match" ? result.bundle : null;
|
|
1845
1829
|
const setConfigVarsRef = useWidgetConfigVars(bundle?.widgetConfig);
|
|
1846
1830
|
const setModalConfigVarsRef = useWidgetConfigVars(bundle?.widgetConfig);
|
|
1847
1831
|
const slotsEdgeFadeRef = useEdgeFade();
|
|
1848
|
-
(0,
|
|
1832
|
+
(0, import_react12.useEffect)(() => {
|
|
1849
1833
|
if (result.status === "error") {
|
|
1850
1834
|
onError?.(result.error);
|
|
1851
1835
|
return;
|
|
@@ -1858,7 +1842,7 @@ function MixMatchBundle(props) {
|
|
|
1858
1842
|
);
|
|
1859
1843
|
}
|
|
1860
1844
|
}, [result, onError]);
|
|
1861
|
-
(0,
|
|
1845
|
+
(0, import_react12.useEffect)(() => {
|
|
1862
1846
|
if (!bundle || seededFor === bundle.id) return;
|
|
1863
1847
|
setSeededFor(bundle.id);
|
|
1864
1848
|
const seedCurrency = bundle.products[0]?.priceRange.minVariantPrice.currencyCode ?? "USD";
|
|
@@ -1871,7 +1855,7 @@ function MixMatchBundle(props) {
|
|
|
1871
1855
|
featuredImage: variant.image?.url ?? product.featuredImage?.url ?? null,
|
|
1872
1856
|
price: parseFloat(variant.price.amount),
|
|
1873
1857
|
compareAtPrice: variant.compareAtPrice ? parseFloat(variant.compareAtPrice.amount) : null,
|
|
1874
|
-
unitPrice: (0,
|
|
1858
|
+
unitPrice: (0, import_core10.formatUnitPrice)(
|
|
1875
1859
|
variant.unitPrice,
|
|
1876
1860
|
variant.unitPriceMeasurement,
|
|
1877
1861
|
seedCurrency
|
|
@@ -1884,7 +1868,7 @@ function MixMatchBundle(props) {
|
|
|
1884
1868
|
for (const variant2 of product.variants.nodes) {
|
|
1885
1869
|
const vRule = bundle.variantRules[variant2.id];
|
|
1886
1870
|
if (!vRule?.required) continue;
|
|
1887
|
-
if (!(0,
|
|
1871
|
+
if (!(0, import_core10.isVariantFulfillable)(variant2, Math.max(1, vRule.min))) continue;
|
|
1888
1872
|
seeds.push(pick(product, variant2, Math.max(1, vRule.min)));
|
|
1889
1873
|
seededVariant = true;
|
|
1890
1874
|
}
|
|
@@ -1892,7 +1876,7 @@ function MixMatchBundle(props) {
|
|
|
1892
1876
|
const rule = bundle.productRules[product.id];
|
|
1893
1877
|
if (!rule?.required) continue;
|
|
1894
1878
|
const variant = product.variants.nodes.find(
|
|
1895
|
-
(v) => (0,
|
|
1879
|
+
(v) => (0, import_core10.isVariantFulfillable)(v, rule.min)
|
|
1896
1880
|
);
|
|
1897
1881
|
if (!variant) continue;
|
|
1898
1882
|
seeds.push(pick(product, variant, rule.min));
|
|
@@ -1901,7 +1885,7 @@ function MixMatchBundle(props) {
|
|
|
1901
1885
|
const requiredUnits2 = bundle.minQuantity ?? 0;
|
|
1902
1886
|
const qualifies = (p) => {
|
|
1903
1887
|
const min = bundle.productRules[p.id]?.min ?? 1;
|
|
1904
|
-
return min <= requiredUnits2 && p.variants.nodes.some((v) => (0,
|
|
1888
|
+
return min <= requiredUnits2 && p.variants.nodes.some((v) => (0, import_core10.isVariantFulfillable)(v, min));
|
|
1905
1889
|
};
|
|
1906
1890
|
const seed = bundle.products.find(
|
|
1907
1891
|
(p) => p.handle === productHandle && qualifies(p)
|
|
@@ -1909,7 +1893,7 @@ function MixMatchBundle(props) {
|
|
|
1909
1893
|
if (seed) {
|
|
1910
1894
|
const min = bundle.productRules[seed.id]?.min ?? 1;
|
|
1911
1895
|
const variant = seed.variants.nodes.find(
|
|
1912
|
-
(v) => (0,
|
|
1896
|
+
(v) => (0, import_core10.isVariantFulfillable)(v, min)
|
|
1913
1897
|
);
|
|
1914
1898
|
if (variant) seeds.push(pick(seed, variant, min));
|
|
1915
1899
|
}
|
|
@@ -1918,7 +1902,7 @@ function MixMatchBundle(props) {
|
|
|
1918
1902
|
}, [bundle, seededFor, productHandle]);
|
|
1919
1903
|
const showStepper = bundle?.widgetConfig.mixMatchShowQuantitySelector !== false;
|
|
1920
1904
|
const requiredUnits = bundle?.minQuantity ?? 0;
|
|
1921
|
-
const totalQuantity = (0,
|
|
1905
|
+
const totalQuantity = (0, import_react12.useMemo)(
|
|
1922
1906
|
() => selections.reduce((sum, s) => sum + s.quantity, 0),
|
|
1923
1907
|
[selections]
|
|
1924
1908
|
);
|
|
@@ -1926,14 +1910,14 @@ function MixMatchBundle(props) {
|
|
|
1926
1910
|
const valid = !!bundle && meetsMinUnits;
|
|
1927
1911
|
const shownUnits = Math.min(totalQuantity, requiredUnits);
|
|
1928
1912
|
const remaining = Math.max(0, requiredUnits - shownUnits);
|
|
1929
|
-
const summary = (0,
|
|
1913
|
+
const summary = (0, import_react12.useMemo)(() => {
|
|
1930
1914
|
let comparePrice = 0;
|
|
1931
1915
|
let salePrice = 0;
|
|
1932
1916
|
for (const sel of selections) {
|
|
1933
1917
|
if (sel.quantity <= 0) continue;
|
|
1934
1918
|
comparePrice += sel.price * sel.quantity;
|
|
1935
1919
|
if (bundle) {
|
|
1936
|
-
salePrice += (0,
|
|
1920
|
+
salePrice += (0, import_core10.calculateDiscount)(
|
|
1937
1921
|
sel.price,
|
|
1938
1922
|
bundle.discountConfig.discountType,
|
|
1939
1923
|
bundle.discountConfig.discountValue
|
|
@@ -1944,7 +1928,7 @@ function MixMatchBundle(props) {
|
|
|
1944
1928
|
const savingsPercent = comparePrice > 0 && savings > 0 ? Math.round(savings / comparePrice * 100) : 0;
|
|
1945
1929
|
return { comparePrice, salePrice, savings, savingsPercent };
|
|
1946
1930
|
}, [selections, bundle]);
|
|
1947
|
-
const addSelection = (0,
|
|
1931
|
+
const addSelection = (0, import_react12.useCallback)(
|
|
1948
1932
|
(item) => {
|
|
1949
1933
|
setSelections((prev) => {
|
|
1950
1934
|
const units = prev.reduce((sum, s) => sum + s.quantity, 0);
|
|
@@ -1955,7 +1939,7 @@ function MixMatchBundle(props) {
|
|
|
1955
1939
|
},
|
|
1956
1940
|
[requiredUnits]
|
|
1957
1941
|
);
|
|
1958
|
-
const updateQuantity = (0,
|
|
1942
|
+
const updateQuantity = (0, import_react12.useCallback)(
|
|
1959
1943
|
(productId, variantId, quantity) => {
|
|
1960
1944
|
setSelections((prev) => {
|
|
1961
1945
|
const idx = prev.findIndex(
|
|
@@ -1978,7 +1962,7 @@ function MixMatchBundle(props) {
|
|
|
1978
1962
|
},
|
|
1979
1963
|
[requiredUnits]
|
|
1980
1964
|
);
|
|
1981
|
-
const swapSelection = (0,
|
|
1965
|
+
const swapSelection = (0, import_react12.useCallback)(
|
|
1982
1966
|
(item) => {
|
|
1983
1967
|
setSelections((prev) => {
|
|
1984
1968
|
if (!bundle) return prev;
|
|
@@ -2010,7 +1994,7 @@ function MixMatchBundle(props) {
|
|
|
2010
1994
|
},
|
|
2011
1995
|
[bundle]
|
|
2012
1996
|
);
|
|
2013
|
-
const removeVariant = (0,
|
|
1997
|
+
const removeVariant = (0, import_react12.useCallback)(
|
|
2014
1998
|
(variantId) => {
|
|
2015
1999
|
setSelections((prev) => {
|
|
2016
2000
|
const idx = prev.findIndex((s) => s.variantId === variantId);
|
|
@@ -2023,7 +2007,7 @@ function MixMatchBundle(props) {
|
|
|
2023
2007
|
},
|
|
2024
2008
|
[bundle]
|
|
2025
2009
|
);
|
|
2026
|
-
const removeSlot = (0,
|
|
2010
|
+
const removeSlot = (0, import_react12.useCallback)(
|
|
2027
2011
|
(index) => {
|
|
2028
2012
|
setSelections((prev) => {
|
|
2029
2013
|
if (index < 0 || index >= prev.length) return prev;
|
|
@@ -2035,7 +2019,7 @@ function MixMatchBundle(props) {
|
|
|
2035
2019
|
},
|
|
2036
2020
|
[bundle]
|
|
2037
2021
|
);
|
|
2038
|
-
const handleAddToCart = (0,
|
|
2022
|
+
const handleAddToCart = (0, import_react12.useCallback)(async () => {
|
|
2039
2023
|
if (!bundle || !valid) return;
|
|
2040
2024
|
const grouped = /* @__PURE__ */ new Map();
|
|
2041
2025
|
for (const sel of selections) {
|
|
@@ -2089,7 +2073,7 @@ function MixMatchBundle(props) {
|
|
|
2089
2073
|
if (!rule.required) return false;
|
|
2090
2074
|
const product = bundle.products.find((p) => p.id === pid);
|
|
2091
2075
|
return !product?.variants.nodes.some(
|
|
2092
|
-
(v) => (0,
|
|
2076
|
+
(v) => (0, import_core10.isVariantFulfillable)(v, rule.min)
|
|
2093
2077
|
);
|
|
2094
2078
|
}
|
|
2095
2079
|
);
|
|
@@ -2189,8 +2173,8 @@ function MixMatchBundle(props) {
|
|
|
2189
2173
|
),
|
|
2190
2174
|
item.variantTitle && item.variantTitle !== "Default Title" && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "lb-mix-match__filled-variant", children: item.variantTitle }),
|
|
2191
2175
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "lb-mix-match__filled-price", children: [
|
|
2192
|
-
item.compareAtPrice !== null && item.compareAtPrice > item.price && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("s", { className: "lb-mix-match__filled-compare", children: (0,
|
|
2193
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "lb-bundle-product-price", children: (0,
|
|
2176
|
+
item.compareAtPrice !== null && item.compareAtPrice > item.price && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("s", { className: "lb-mix-match__filled-compare", children: (0, import_core10.formatMoney)(item.compareAtPrice, currency) }),
|
|
2177
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "lb-bundle-product-price", children: (0, import_core10.formatMoney)(item.price, currency) }),
|
|
2194
2178
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "lb-bundle-qty-inline", children: `\xD7${item.quantity}` })
|
|
2195
2179
|
] }),
|
|
2196
2180
|
item.unitPrice && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "lb-bundle-product-unit-price", children: item.unitPrice })
|
|
@@ -2240,7 +2224,7 @@ function MixMatchBundle(props) {
|
|
|
2240
2224
|
bundle.widgetConfig.savingsBar.visible && summary.savings > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("p", { className: "lb-bundle-savings-line", "data-savings-bar": true, children: [
|
|
2241
2225
|
"You save",
|
|
2242
2226
|
" ",
|
|
2243
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { "data-savings-amount": true, children: (0,
|
|
2227
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { "data-savings-amount": true, children: (0, import_core10.formatMoney)(summary.savings, currency) }),
|
|
2244
2228
|
" ",
|
|
2245
2229
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { "data-savings-percent": true, children: [
|
|
2246
2230
|
"(",
|
|
@@ -2250,8 +2234,8 @@ function MixMatchBundle(props) {
|
|
|
2250
2234
|
] })
|
|
2251
2235
|
] }),
|
|
2252
2236
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "lb-bundle-summary__prices", children: [
|
|
2253
|
-
bundle.widgetConfig.pricing.showComparePrice && summary.savings > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "lb-bundle-compare-price", "data-compare-price": true, children: (0,
|
|
2254
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "lb-bundle-sale-price", "data-sale-price": true, children: (0,
|
|
2237
|
+
bundle.widgetConfig.pricing.showComparePrice && summary.savings > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "lb-bundle-compare-price", "data-compare-price": true, children: (0, import_core10.formatMoney)(summary.comparePrice, currency) }),
|
|
2238
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "lb-bundle-sale-price", "data-sale-price": true, children: (0, import_core10.formatMoney)(summary.salePrice, currency) })
|
|
2255
2239
|
] })
|
|
2256
2240
|
] }),
|
|
2257
2241
|
cartError && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "lb-bundle__error", role: "alert", children: cartError }),
|
|
@@ -2268,7 +2252,6 @@ function MixMatchBundle(props) {
|
|
|
2268
2252
|
pickerOpen && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2269
2253
|
MixMatchPicker,
|
|
2270
2254
|
{
|
|
2271
|
-
outOfStockBehavior,
|
|
2272
2255
|
bundle,
|
|
2273
2256
|
currency,
|
|
2274
2257
|
requiredQty: requiredUnits,
|
|
@@ -2299,7 +2282,7 @@ function countInStockProducts(bundle) {
|
|
|
2299
2282
|
seen.add(product.id);
|
|
2300
2283
|
const rule = ruleFor2(bundle, product.id);
|
|
2301
2284
|
const fulfillable = product.variants.nodes.some(
|
|
2302
|
-
(v) => (0,
|
|
2285
|
+
(v) => (0, import_core10.isVariantFulfillable)(v, rule.min)
|
|
2303
2286
|
);
|
|
2304
2287
|
if (fulfillable) count += 1;
|
|
2305
2288
|
}
|
|
@@ -2341,8 +2324,8 @@ function CloseIcon2() {
|
|
|
2341
2324
|
}
|
|
2342
2325
|
|
|
2343
2326
|
// src/components/VolumeBundle.tsx
|
|
2344
|
-
var
|
|
2345
|
-
var
|
|
2327
|
+
var import_react13 = require("react");
|
|
2328
|
+
var import_core11 = require("@lime-bundles/core");
|
|
2346
2329
|
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
2347
2330
|
function VolumeBundle(props) {
|
|
2348
2331
|
const {
|
|
@@ -2353,7 +2336,8 @@ function VolumeBundle(props) {
|
|
|
2353
2336
|
analyticsEnabled,
|
|
2354
2337
|
onAddToCart,
|
|
2355
2338
|
onError,
|
|
2356
|
-
className
|
|
2339
|
+
className,
|
|
2340
|
+
productId
|
|
2357
2341
|
} = props;
|
|
2358
2342
|
const result = useBundleData({
|
|
2359
2343
|
shopDomain,
|
|
@@ -2367,21 +2351,21 @@ function VolumeBundle(props) {
|
|
|
2367
2351
|
bundleType: "volume",
|
|
2368
2352
|
enabled: analyticsEnabled !== false
|
|
2369
2353
|
});
|
|
2370
|
-
const [quantity, setQuantity] = (0,
|
|
2371
|
-
const [addingToCart, setAddingToCart] = (0,
|
|
2372
|
-
const [cartError, setCartError] = (0,
|
|
2373
|
-
const [seededFor, setSeededFor] = (0,
|
|
2354
|
+
const [quantity, setQuantity] = (0, import_react13.useState)(1);
|
|
2355
|
+
const [addingToCart, setAddingToCart] = (0, import_react13.useState)(false);
|
|
2356
|
+
const [cartError, setCartError] = (0, import_react13.useState)(null);
|
|
2357
|
+
const [seededFor, setSeededFor] = (0, import_react13.useState)(null);
|
|
2374
2358
|
const bundle = result.status === "success" && result.bundle.bundleType === "volume" ? result.bundle : null;
|
|
2375
|
-
(0,
|
|
2359
|
+
(0, import_react13.useEffect)(() => {
|
|
2376
2360
|
if (!bundle || seededFor === bundle.id) return;
|
|
2377
2361
|
setSeededFor(bundle.id);
|
|
2378
2362
|
const tiers = bundle.volumeTiers;
|
|
2379
2363
|
if (tiers.length === 0) return;
|
|
2380
|
-
const best = (0,
|
|
2364
|
+
const best = (0, import_core11.bestValueTierIndex)(
|
|
2381
2365
|
tiers,
|
|
2382
2366
|
bundle.discountConfig.discountType
|
|
2383
2367
|
);
|
|
2384
|
-
const idx = (0,
|
|
2368
|
+
const idx = (0, import_core11.resolveDefaultTierIndex)(
|
|
2385
2369
|
bundle.widgetConfig.defaultTier,
|
|
2386
2370
|
tiers.length,
|
|
2387
2371
|
best
|
|
@@ -2390,7 +2374,7 @@ function VolumeBundle(props) {
|
|
|
2390
2374
|
}, [bundle, seededFor]);
|
|
2391
2375
|
const setConfigVarsRef = useWidgetConfigVars(bundle?.widgetConfig);
|
|
2392
2376
|
const tiersEdgeFadeRef = useEdgeFade();
|
|
2393
|
-
(0,
|
|
2377
|
+
(0, import_react13.useEffect)(() => {
|
|
2394
2378
|
if (result.status === "error") {
|
|
2395
2379
|
onError?.(result.error);
|
|
2396
2380
|
return;
|
|
@@ -2403,27 +2387,32 @@ function VolumeBundle(props) {
|
|
|
2403
2387
|
);
|
|
2404
2388
|
}
|
|
2405
2389
|
}, [result, onError]);
|
|
2406
|
-
const product = bundle
|
|
2407
|
-
const variants = (0,
|
|
2408
|
-
const optionNames = (0,
|
|
2390
|
+
const product = bundle ? (0, import_core11.resolveContextProduct)(bundle.products, { productId }) : void 0;
|
|
2391
|
+
const variants = (0, import_react13.useMemo)(() => product?.variants.nodes ?? [], [product]);
|
|
2392
|
+
const optionNames = (0, import_react13.useMemo)(() => {
|
|
2409
2393
|
const first = variants[0];
|
|
2410
2394
|
return first ? first.selectedOptions.map((o) => o.name) : [];
|
|
2411
2395
|
}, [variants]);
|
|
2412
2396
|
const showPicker = variants.length > 1 && optionNames.length > 0;
|
|
2413
2397
|
const { selectedValues, selectedVariant, setOptionValue, optionsFor } = useVariantSelection({ variants, optionNames });
|
|
2414
|
-
const minTierQty = bundle ? (0,
|
|
2415
|
-
const isSoldOut = variants.length > 0 && !variants.some((v) => (0,
|
|
2416
|
-
const displayVariant = selectedVariant ?? variants.find((v) => (0,
|
|
2398
|
+
const minTierQty = bundle ? (0, import_core11.getMinTierQuantity)(bundle.volumeTiers) : 1;
|
|
2399
|
+
const isSoldOut = variants.length > 0 && !variants.some((v) => (0, import_core11.isVariantFulfillable)(v, minTierQty));
|
|
2400
|
+
const displayVariant = selectedVariant ?? variants.find((v) => (0, import_core11.isVariantFulfillable)(v, minTierQty)) ?? variants[0];
|
|
2401
|
+
const tierAvailable = (0, import_react13.useCallback)(
|
|
2402
|
+
(tierQty) => Boolean(displayVariant && (0, import_core11.isVariantFulfillable)(displayVariant, tierQty)),
|
|
2403
|
+
[displayVariant]
|
|
2404
|
+
);
|
|
2405
|
+
const selectedTierUnavailable = !isSoldOut && !tierAvailable(quantity);
|
|
2417
2406
|
const basePrice = displayVariant ? parseFloat(displayVariant.price.amount) : product ? parseFloat(product.priceRange.minVariantPrice.amount) : 0;
|
|
2418
2407
|
const currency = product?.priceRange.minVariantPrice.currencyCode ?? "USD";
|
|
2419
2408
|
const thumbImage = displayVariant?.image ?? product?.featuredImage ?? null;
|
|
2420
|
-
const unitPriceText = displayVariant ? (0,
|
|
2409
|
+
const unitPriceText = displayVariant ? (0, import_core11.formatUnitPrice)(
|
|
2421
2410
|
displayVariant.unitPrice,
|
|
2422
2411
|
displayVariant.unitPriceMeasurement,
|
|
2423
2412
|
currency
|
|
2424
2413
|
) : null;
|
|
2425
|
-
const tierSavings = (0,
|
|
2426
|
-
() => bundle ? (0,
|
|
2414
|
+
const tierSavings = (0, import_react13.useMemo)(
|
|
2415
|
+
() => bundle ? (0, import_core11.calculateTierSavings)(
|
|
2427
2416
|
bundle.volumeTiers,
|
|
2428
2417
|
basePrice,
|
|
2429
2418
|
quantity,
|
|
@@ -2431,13 +2420,13 @@ function VolumeBundle(props) {
|
|
|
2431
2420
|
) : [],
|
|
2432
2421
|
[bundle, basePrice, quantity]
|
|
2433
2422
|
);
|
|
2434
|
-
const activeTier = bundle ? (0,
|
|
2435
|
-
const popularTierIndex = (0,
|
|
2423
|
+
const activeTier = bundle ? (0, import_core11.getActiveTier)(bundle.volumeTiers, quantity) : null;
|
|
2424
|
+
const popularTierIndex = (0, import_react13.useMemo)(() => {
|
|
2436
2425
|
if (!bundle || bundle.volumeTiers.length === 0) return -1;
|
|
2437
|
-
return (0,
|
|
2426
|
+
return (0, import_core11.resolvePopularTierIndex)(
|
|
2438
2427
|
bundle.widgetConfig.popularBadge.tierIndex,
|
|
2439
2428
|
bundle.widgetConfig.popularBadge.visible !== false,
|
|
2440
|
-
(0,
|
|
2429
|
+
(0, import_core11.bestValueTierIndex)(
|
|
2441
2430
|
bundle.volumeTiers,
|
|
2442
2431
|
bundle.discountConfig.discountType
|
|
2443
2432
|
)
|
|
@@ -2448,10 +2437,10 @@ function VolumeBundle(props) {
|
|
|
2448
2437
|
const volumeTotal = activeUnitPrice * quantity;
|
|
2449
2438
|
const volumeSavings = Math.max(0, undiscountedTotal - volumeTotal);
|
|
2450
2439
|
const volumeSavingsPercent = undiscountedTotal > 0 && volumeSavings > 0 ? Math.round(volumeSavings / undiscountedTotal * 100) : 0;
|
|
2451
|
-
const handleAddToCart = (0,
|
|
2440
|
+
const handleAddToCart = (0, import_react13.useCallback)(async () => {
|
|
2452
2441
|
if (!bundle || !product) return;
|
|
2453
2442
|
const variant = selectedVariant ?? product.variants.nodes.find(
|
|
2454
|
-
(v) => (0,
|
|
2443
|
+
(v) => (0, import_core11.isVariantFulfillable)(v, quantity)
|
|
2455
2444
|
);
|
|
2456
2445
|
if (!variant) return;
|
|
2457
2446
|
const lines = [
|
|
@@ -2537,7 +2526,7 @@ function VolumeBundle(props) {
|
|
|
2537
2526
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "lb-bundle__product-info", children: [
|
|
2538
2527
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "lb-bundle__product-title", children: product.title }),
|
|
2539
2528
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("p", { className: "lb-bundle__product-price", children: [
|
|
2540
|
-
(0,
|
|
2529
|
+
(0, import_core11.formatMoney)(basePrice, currency),
|
|
2541
2530
|
" each"
|
|
2542
2531
|
] }),
|
|
2543
2532
|
unitPriceText && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "lb-bundle__product-unit-price", children: unitPriceText }),
|
|
@@ -2571,16 +2560,20 @@ function VolumeBundle(props) {
|
|
|
2571
2560
|
const savingsPercent = Math.round(ts.savingsPercent);
|
|
2572
2561
|
const showCompare = bundle.widgetConfig.pricing.showComparePrice && ts.savings > 0;
|
|
2573
2562
|
const isChecked = activeTier === ts.tier;
|
|
2563
|
+
const available = tierAvailable(ts.tier.minQuantity);
|
|
2574
2564
|
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
2575
2565
|
"div",
|
|
2576
2566
|
{
|
|
2577
|
-
className: `lb-bundle__tier ${ts.isActive ? "lb-bundle__tier--active" : ""}`,
|
|
2567
|
+
className: `lb-bundle__tier ${ts.isActive ? "lb-bundle__tier--active" : ""} ${available ? "" : "lb-bundle__tier--oos"}`,
|
|
2578
2568
|
role: "radio",
|
|
2579
2569
|
"aria-checked": isChecked,
|
|
2570
|
+
"aria-disabled": available ? void 0 : true,
|
|
2580
2571
|
tabIndex: isChecked ? 0 : -1,
|
|
2581
|
-
onClick: () =>
|
|
2572
|
+
onClick: () => {
|
|
2573
|
+
if (available) setQuantity(ts.tier.minQuantity);
|
|
2574
|
+
},
|
|
2582
2575
|
onKeyDown: (e) => {
|
|
2583
|
-
if (e.key === "Enter" || e.key === " ") {
|
|
2576
|
+
if ((e.key === "Enter" || e.key === " ") && available) {
|
|
2584
2577
|
e.preventDefault();
|
|
2585
2578
|
setQuantity(ts.tier.minQuantity);
|
|
2586
2579
|
}
|
|
@@ -2593,8 +2586,8 @@ function VolumeBundle(props) {
|
|
|
2593
2586
|
ts.tier.minQuantity
|
|
2594
2587
|
] }),
|
|
2595
2588
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "lb-bundle__tier-price", children: [
|
|
2596
|
-
showCompare && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "lb-bundle__tier-compare", children: (0,
|
|
2597
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: (0,
|
|
2589
|
+
showCompare && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "lb-bundle__tier-compare", children: (0, import_core11.formatMoney)(basePrice, currency) }),
|
|
2590
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: (0, import_core11.formatMoney)(ts.unitPrice, currency) }),
|
|
2598
2591
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "lb-bundle__tier-unit", children: "each" })
|
|
2599
2592
|
] })
|
|
2600
2593
|
] }),
|
|
@@ -2649,7 +2642,7 @@ function VolumeBundle(props) {
|
|
|
2649
2642
|
] })
|
|
2650
2643
|
] }),
|
|
2651
2644
|
cartError && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "lb-bundle__error", role: "alert", children: cartError }),
|
|
2652
|
-
bundle && displayVariant && (0,
|
|
2645
|
+
bundle && displayVariant && (0, import_core11.shouldShowLowStockBadge)(
|
|
2653
2646
|
displayVariant,
|
|
2654
2647
|
minTierQty,
|
|
2655
2648
|
bundle.widgetConfig.lowStockThreshold,
|
|
@@ -2676,7 +2669,7 @@ function VolumeBundle(props) {
|
|
|
2676
2669
|
bundle.widgetConfig.savingsBar.visible && volumeSavings > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("p", { className: "lb-bundle-savings-line", "data-savings-bar": true, children: [
|
|
2677
2670
|
"You save",
|
|
2678
2671
|
" ",
|
|
2679
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { "data-savings-amount": true, children: (0,
|
|
2672
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { "data-savings-amount": true, children: (0, import_core11.formatMoney)(volumeSavings, currency) }),
|
|
2680
2673
|
" ",
|
|
2681
2674
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { "data-savings-percent": true, children: [
|
|
2682
2675
|
"(",
|
|
@@ -2686,8 +2679,8 @@ function VolumeBundle(props) {
|
|
|
2686
2679
|
] })
|
|
2687
2680
|
] }),
|
|
2688
2681
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "lb-bundle-summary__prices", children: [
|
|
2689
|
-
bundle.widgetConfig.pricing.showComparePrice && volumeSavings > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "lb-bundle-compare-price", "data-compare-price": true, children: (0,
|
|
2690
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "lb-bundle-sale-price", "data-total-price": true, children: (0,
|
|
2682
|
+
bundle.widgetConfig.pricing.showComparePrice && volumeSavings > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "lb-bundle-compare-price", "data-compare-price": true, children: (0, import_core11.formatMoney)(undiscountedTotal, currency) }),
|
|
2683
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "lb-bundle-sale-price", "data-total-price": true, children: (0, import_core11.formatMoney)(volumeTotal, currency) })
|
|
2691
2684
|
] })
|
|
2692
2685
|
] }),
|
|
2693
2686
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
@@ -2695,9 +2688,9 @@ function VolumeBundle(props) {
|
|
|
2695
2688
|
{
|
|
2696
2689
|
className: "lb-bundle__cta",
|
|
2697
2690
|
onClick: handleAddToCart,
|
|
2698
|
-
disabled: addingToCart || isSoldOut,
|
|
2691
|
+
disabled: addingToCart || isSoldOut || selectedTierUnavailable,
|
|
2699
2692
|
"aria-busy": addingToCart,
|
|
2700
|
-
children: addingToCart ? "Adding..." : isSoldOut ? "Sold out" : bundle.widgetConfig.cta.ctaText ?? `Add ${quantity} to Cart`
|
|
2693
|
+
children: addingToCart ? "Adding..." : isSoldOut || selectedTierUnavailable ? "Sold out" : bundle.widgetConfig.cta.ctaText ?? `Add ${quantity} to Cart`
|
|
2701
2694
|
}
|
|
2702
2695
|
)
|
|
2703
2696
|
]
|
|
@@ -2706,8 +2699,8 @@ function VolumeBundle(props) {
|
|
|
2706
2699
|
}
|
|
2707
2700
|
|
|
2708
2701
|
// src/components/BogoBundle.tsx
|
|
2709
|
-
var
|
|
2710
|
-
var
|
|
2702
|
+
var import_react14 = require("react");
|
|
2703
|
+
var import_core12 = require("@lime-bundles/core");
|
|
2711
2704
|
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
2712
2705
|
function discountedUnit(price, percent) {
|
|
2713
2706
|
const cents = Math.round(price * 100);
|
|
@@ -2737,10 +2730,10 @@ function BogoBundle(props) {
|
|
|
2737
2730
|
bundleType: "bogo",
|
|
2738
2731
|
enabled: analyticsEnabled !== false
|
|
2739
2732
|
});
|
|
2740
|
-
const [addingToCart, setAddingToCart] = (0,
|
|
2741
|
-
const [cartError, setCartError] = (0,
|
|
2742
|
-
const [selectedVariants, setSelectedVariants] = (0,
|
|
2743
|
-
const handleVariantChange = (0,
|
|
2733
|
+
const [addingToCart, setAddingToCart] = (0, import_react14.useState)(false);
|
|
2734
|
+
const [cartError, setCartError] = (0, import_react14.useState)(null);
|
|
2735
|
+
const [selectedVariants, setSelectedVariants] = (0, import_react14.useState)({ buy: null, get: null });
|
|
2736
|
+
const handleVariantChange = (0, import_react14.useCallback)(
|
|
2744
2737
|
(role, variant) => {
|
|
2745
2738
|
setSelectedVariants((prev) => {
|
|
2746
2739
|
if (prev[role] === variant) return prev;
|
|
@@ -2751,7 +2744,7 @@ function BogoBundle(props) {
|
|
|
2751
2744
|
);
|
|
2752
2745
|
const bundle = result.status === "success" && result.bundle.bundleType === "bogo" ? result.bundle : null;
|
|
2753
2746
|
const setConfigVarsRef = useWidgetConfigVars(bundle?.widgetConfig);
|
|
2754
|
-
(0,
|
|
2747
|
+
(0, import_react14.useEffect)(() => {
|
|
2755
2748
|
if (result.status === "error") {
|
|
2756
2749
|
onError?.(result.error);
|
|
2757
2750
|
return;
|
|
@@ -2764,28 +2757,28 @@ function BogoBundle(props) {
|
|
|
2764
2757
|
);
|
|
2765
2758
|
}
|
|
2766
2759
|
}, [result, onError]);
|
|
2767
|
-
const buyProduct = (0,
|
|
2760
|
+
const buyProduct = (0, import_react14.useMemo)(
|
|
2768
2761
|
() => bundle?.products.find((p) => p.id === bundle.buyProductId) ?? null,
|
|
2769
2762
|
[bundle]
|
|
2770
2763
|
);
|
|
2771
|
-
const getProduct = (0,
|
|
2764
|
+
const getProduct = (0, import_react14.useMemo)(
|
|
2772
2765
|
() => bundle?.products.find((p) => p.id === bundle.getProductId) ?? null,
|
|
2773
2766
|
[bundle]
|
|
2774
2767
|
);
|
|
2775
2768
|
const percent = bundle ? Math.min(100, Math.max(0, bundle.discountConfig.discountValue)) : 0;
|
|
2776
|
-
const resolveVariant = (0,
|
|
2769
|
+
const resolveVariant = (0, import_react14.useCallback)(
|
|
2777
2770
|
(product, selected, qty) => {
|
|
2778
2771
|
if (!product) return null;
|
|
2779
|
-
return selected ?? product.variants.nodes.find((v) => (0,
|
|
2772
|
+
return selected ?? product.variants.nodes.find((v) => (0, import_core12.isVariantFulfillable)(v, qty)) ?? product.variants.nodes[0] ?? null;
|
|
2780
2773
|
},
|
|
2781
2774
|
[]
|
|
2782
2775
|
);
|
|
2783
|
-
const oosSides = (0,
|
|
2776
|
+
const oosSides = (0, import_react14.useMemo)(() => {
|
|
2784
2777
|
if (!bundle) return 0;
|
|
2785
|
-
const short = (product, qty) => !!product && !product.variants.nodes.some((v) => (0,
|
|
2778
|
+
const short = (product, qty) => !!product && !product.variants.nodes.some((v) => (0, import_core12.isVariantFulfillable)(v, qty));
|
|
2786
2779
|
return (short(buyProduct, bundle.buyQuantity) ? 1 : 0) + (short(getProduct, bundle.getQuantity) ? 1 : 0);
|
|
2787
2780
|
}, [bundle, buyProduct, getProduct]);
|
|
2788
|
-
const handleAddToCart = (0,
|
|
2781
|
+
const handleAddToCart = (0, import_react14.useCallback)(async () => {
|
|
2789
2782
|
if (!bundle || !buyProduct || !getProduct) return;
|
|
2790
2783
|
const buyVariant2 = resolveVariant(buyProduct, selectedVariants.buy, bundle.buyQuantity);
|
|
2791
2784
|
const getVariant2 = resolveVariant(getProduct, selectedVariants.get, bundle.getQuantity);
|
|
@@ -2910,7 +2903,7 @@ function BogoBundle(props) {
|
|
|
2910
2903
|
showSavings && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("p", { className: "lb-bundle-savings-line", "data-savings-bar": true, children: [
|
|
2911
2904
|
"You save",
|
|
2912
2905
|
" ",
|
|
2913
|
-
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { "data-savings-amount": true, children: (0,
|
|
2906
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { "data-savings-amount": true, children: (0, import_core12.formatMoney)(savings, currency) }),
|
|
2914
2907
|
" ",
|
|
2915
2908
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { "data-savings-percent": true, children: [
|
|
2916
2909
|
"(",
|
|
@@ -2920,8 +2913,8 @@ function BogoBundle(props) {
|
|
|
2920
2913
|
] })
|
|
2921
2914
|
] }),
|
|
2922
2915
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "lb-bundle-summary__prices", children: [
|
|
2923
|
-
showCompare && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "lb-bundle-compare-price", "data-compare-price": true, children: (0,
|
|
2924
|
-
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "lb-bundle-sale-price", "data-sale-price": true, children: (0,
|
|
2916
|
+
showCompare && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "lb-bundle-compare-price", "data-compare-price": true, children: (0, import_core12.formatMoney)(comparePrice, currency) }),
|
|
2917
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "lb-bundle-sale-price", "data-sale-price": true, children: (0, import_core12.formatMoney)(salePrice, currency) })
|
|
2925
2918
|
] })
|
|
2926
2919
|
] }),
|
|
2927
2920
|
cartError && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "lb-bundle__error", role: "alert", children: cartError }),
|
|
@@ -2941,7 +2934,7 @@ function BogoBundle(props) {
|
|
|
2941
2934
|
}
|
|
2942
2935
|
function BogoProductRow({ side, product, quantity, percent, badge, currency, onVariantChange }) {
|
|
2943
2936
|
const variants = product.variants.nodes;
|
|
2944
|
-
const optionNames = (0,
|
|
2937
|
+
const optionNames = (0, import_react14.useMemo)(() => {
|
|
2945
2938
|
const first = variants[0];
|
|
2946
2939
|
return first ? first.selectedOptions.map((o) => o.name) : [];
|
|
2947
2940
|
}, [variants]);
|
|
@@ -2950,15 +2943,15 @@ function BogoProductRow({ side, product, quantity, percent, badge, currency, onV
|
|
|
2950
2943
|
variants,
|
|
2951
2944
|
optionNames
|
|
2952
2945
|
});
|
|
2953
|
-
(0,
|
|
2946
|
+
(0, import_react14.useEffect)(() => {
|
|
2954
2947
|
onVariantChange(side, selectedVariant ?? null);
|
|
2955
2948
|
}, [selectedVariant, side, onVariantChange]);
|
|
2956
|
-
const displayVariant = selectedVariant ?? variants.find((v) => (0,
|
|
2949
|
+
const displayVariant = selectedVariant ?? variants.find((v) => (0, import_core12.isVariantFulfillable)(v, quantity)) ?? variants[0];
|
|
2957
2950
|
const unit = displayVariant ? parseFloat(displayVariant.price.amount) : 0;
|
|
2958
2951
|
const isGet = side === "get" && percent > 0;
|
|
2959
|
-
const priceText = isGet ? (0,
|
|
2960
|
-
const compareAt = isGet ? (0,
|
|
2961
|
-
const unitPriceText = displayVariant ? (0,
|
|
2952
|
+
const priceText = isGet ? (0, import_core12.formatMoney)(discountedUnit(unit, percent), currency) : (0, import_core12.formatMoney)(unit, currency);
|
|
2953
|
+
const compareAt = isGet ? (0, import_core12.formatMoney)(unit, currency) : displayVariant?.compareAtPrice && parseFloat(displayVariant.compareAtPrice.amount) > unit ? (0, import_core12.formatMoney)(displayVariant.compareAtPrice.amount, currency) : null;
|
|
2954
|
+
const unitPriceText = displayVariant ? (0, import_core12.formatUnitPrice)(
|
|
2962
2955
|
displayVariant.unitPrice,
|
|
2963
2956
|
displayVariant.unitPriceMeasurement,
|
|
2964
2957
|
currency
|
|
@@ -3023,20 +3016,20 @@ function BogoProductRow({ side, product, quantity, percent, badge, currency, onV
|
|
|
3023
3016
|
}
|
|
3024
3017
|
|
|
3025
3018
|
// src/components/MultiStepBundle.tsx
|
|
3026
|
-
var
|
|
3027
|
-
var
|
|
3019
|
+
var import_react16 = require("react");
|
|
3020
|
+
var import_core14 = require("@lime-bundles/core");
|
|
3028
3021
|
|
|
3029
3022
|
// src/components/MultiStepPicker.tsx
|
|
3030
|
-
var
|
|
3023
|
+
var import_react15 = require("react");
|
|
3031
3024
|
var import_react_dom2 = require("react-dom");
|
|
3032
|
-
var
|
|
3025
|
+
var import_core13 = require("@lime-bundles/core");
|
|
3033
3026
|
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
3034
3027
|
function ruleFor3(bundle, productId, variantId) {
|
|
3035
3028
|
if (variantId) {
|
|
3036
3029
|
const vRule = bundle.variantRules[variantId];
|
|
3037
3030
|
if (vRule) return vRule;
|
|
3038
3031
|
}
|
|
3039
|
-
return bundle.productRules[productId] ??
|
|
3032
|
+
return bundle.productRules[productId] ?? import_core13.DEFAULT_PRODUCT_RULE;
|
|
3040
3033
|
}
|
|
3041
3034
|
function variantRuleFor3(bundle, variantId) {
|
|
3042
3035
|
return variantId ? bundle.variantRules[variantId] : void 0;
|
|
@@ -3044,15 +3037,14 @@ function variantRuleFor3(bundle, variantId) {
|
|
|
3044
3037
|
function sanitizeId2(gid) {
|
|
3045
3038
|
return gid.replace(/[^a-zA-Z0-9_-]/g, "-");
|
|
3046
3039
|
}
|
|
3047
|
-
function buildEligibleProducts2(bundle, stepIndex
|
|
3040
|
+
function buildEligibleProducts2(bundle, stepIndex) {
|
|
3048
3041
|
const result = [];
|
|
3049
|
-
for (const product of (0,
|
|
3042
|
+
for (const product of (0, import_core13.productsForStep)(bundle, stepIndex)) {
|
|
3050
3043
|
const rule = ruleFor3(bundle, product.id);
|
|
3051
3044
|
const available = product.variants.nodes.filter(
|
|
3052
|
-
(v) => (0,
|
|
3045
|
+
(v) => (0, import_core13.isVariantFulfillable)(v, rule.min)
|
|
3053
3046
|
);
|
|
3054
3047
|
const isOos = available.length === 0;
|
|
3055
|
-
if (isOos && oosBehavior === "hide") continue;
|
|
3056
3048
|
result.push({
|
|
3057
3049
|
product,
|
|
3058
3050
|
variants: product.variants.nodes,
|
|
@@ -3067,7 +3059,6 @@ function normalizeText2(str) {
|
|
|
3067
3059
|
}
|
|
3068
3060
|
var FILTERS_ALL2 = "__all__";
|
|
3069
3061
|
function MultiStepPicker({
|
|
3070
|
-
outOfStockBehavior,
|
|
3071
3062
|
bundle,
|
|
3072
3063
|
currency,
|
|
3073
3064
|
initialStep,
|
|
@@ -3084,17 +3075,19 @@ function MultiStepPicker({
|
|
|
3084
3075
|
}) {
|
|
3085
3076
|
const wc = bundle.widgetConfig;
|
|
3086
3077
|
const steps = bundle.steps;
|
|
3087
|
-
const overlayRef = (0,
|
|
3088
|
-
const dialogRef = (0,
|
|
3089
|
-
const titleRef = (0,
|
|
3090
|
-
const
|
|
3091
|
-
const
|
|
3092
|
-
const
|
|
3078
|
+
const overlayRef = (0, import_react15.useRef)(null);
|
|
3079
|
+
const dialogRef = (0, import_react15.useRef)(null);
|
|
3080
|
+
const titleRef = (0, import_react15.useRef)(null);
|
|
3081
|
+
const closeBtnRef = (0, import_react15.useRef)(null);
|
|
3082
|
+
const listRef = (0, import_react15.useRef)(null);
|
|
3083
|
+
const mouseDownTarget = (0, import_react15.useRef)(null);
|
|
3084
|
+
const [open, setOpen] = (0, import_react15.useState)(false);
|
|
3085
|
+
const [stepIndex, setStepIndex] = (0, import_react15.useState)(
|
|
3093
3086
|
() => Math.max(0, Math.min(steps.length - 1, initialStep))
|
|
3094
3087
|
);
|
|
3095
|
-
const [query, setQuery] = (0,
|
|
3096
|
-
const [activeType, setActiveType] = (0,
|
|
3097
|
-
const [announcement, setAnnouncement] = (0,
|
|
3088
|
+
const [query, setQuery] = (0, import_react15.useState)("");
|
|
3089
|
+
const [activeType, setActiveType] = (0, import_react15.useState)(FILTERS_ALL2);
|
|
3090
|
+
const [announcement, setAnnouncement] = (0, import_react15.useState)("");
|
|
3098
3091
|
const step = steps[stepIndex];
|
|
3099
3092
|
const stepMin = step?.minQuantity ?? 1;
|
|
3100
3093
|
const stepPicks = selections[stepIndex] ?? [];
|
|
@@ -3102,11 +3095,11 @@ function MultiStepPicker({
|
|
|
3102
3095
|
const stepIsSatisfied = unitsInStep >= stepMin;
|
|
3103
3096
|
const isLastStep = stepIndex === steps.length - 1;
|
|
3104
3097
|
const shownUnits = Math.min(unitsInStep, stepMin);
|
|
3105
|
-
const eligible = (0,
|
|
3106
|
-
() => buildEligibleProducts2(bundle, stepIndex
|
|
3107
|
-
[bundle, stepIndex
|
|
3098
|
+
const eligible = (0, import_react15.useMemo)(
|
|
3099
|
+
() => buildEligibleProducts2(bundle, stepIndex),
|
|
3100
|
+
[bundle, stepIndex]
|
|
3108
3101
|
);
|
|
3109
|
-
const productTypes = (0,
|
|
3102
|
+
const productTypes = (0, import_react15.useMemo)(() => {
|
|
3110
3103
|
const types = [];
|
|
3111
3104
|
const seen = /* @__PURE__ */ new Set();
|
|
3112
3105
|
for (const ep of eligible) {
|
|
@@ -3119,7 +3112,7 @@ function MultiStepPicker({
|
|
|
3119
3112
|
return types;
|
|
3120
3113
|
}, [eligible]);
|
|
3121
3114
|
const showFilters = wc.mixMatchShowTypeFilters && productTypes.length >= 2;
|
|
3122
|
-
(0,
|
|
3115
|
+
(0, import_react15.useEffect)(() => {
|
|
3123
3116
|
const id = requestAnimationFrame(() => setOpen(true));
|
|
3124
3117
|
return () => cancelAnimationFrame(id);
|
|
3125
3118
|
}, []);
|
|
@@ -3127,8 +3120,23 @@ function MultiStepPicker({
|
|
|
3127
3120
|
useFocusTrap({
|
|
3128
3121
|
active: true,
|
|
3129
3122
|
containerRef: dialogRef,
|
|
3123
|
+
initialFocusRef: closeBtnRef,
|
|
3130
3124
|
onEscape: onClose
|
|
3131
3125
|
});
|
|
3126
|
+
(0, import_react15.useEffect)(() => {
|
|
3127
|
+
if (listRef.current) listRef.current.scrollTop = 0;
|
|
3128
|
+
}, [stepIndex]);
|
|
3129
|
+
const [selectedAtLoad, setSelectedAtLoad] = (0, import_react15.useState)(
|
|
3130
|
+
() => new Set((selections[stepIndex] ?? []).map((s) => s.productId))
|
|
3131
|
+
);
|
|
3132
|
+
const ordered = (0, import_react15.useMemo)(() => {
|
|
3133
|
+
const front = [];
|
|
3134
|
+
const back = [];
|
|
3135
|
+
for (const ep of eligible) {
|
|
3136
|
+
(selectedAtLoad.has(ep.product.id) ? back : front).push(ep);
|
|
3137
|
+
}
|
|
3138
|
+
return front.concat(back);
|
|
3139
|
+
}, [eligible, selectedAtLoad]);
|
|
3132
3140
|
function goToStep(i) {
|
|
3133
3141
|
const clamped = Math.max(0, Math.min(steps.length - 1, i));
|
|
3134
3142
|
setStepIndex(clamped);
|
|
@@ -3136,6 +3144,7 @@ function MultiStepPicker({
|
|
|
3136
3144
|
setActiveType(FILTERS_ALL2);
|
|
3137
3145
|
const target = steps[clamped];
|
|
3138
3146
|
const targetPicks = selections[clamped] ?? [];
|
|
3147
|
+
setSelectedAtLoad(new Set(targetPicks.map((s) => s.productId)));
|
|
3139
3148
|
const targetUnits = targetPicks.reduce((sum, s) => sum + s.quantity, 0);
|
|
3140
3149
|
setAnnouncement(
|
|
3141
3150
|
`Step ${clamped + 1} of ${steps.length}. ${target.name}. ${Math.min(targetUnits, target.minQuantity)} of ${target.minQuantity} added`
|
|
@@ -3143,14 +3152,14 @@ function MultiStepPicker({
|
|
|
3143
3152
|
titleRef.current?.focus();
|
|
3144
3153
|
}
|
|
3145
3154
|
const subtitle = `Step ${stepIndex + 1} of ${steps.length} \xB7 ${step?.name ?? ""}`;
|
|
3146
|
-
const filtered = (0,
|
|
3155
|
+
const filtered = (0, import_react15.useMemo)(() => {
|
|
3147
3156
|
const nq = normalizeText2(query.trim());
|
|
3148
|
-
return
|
|
3157
|
+
return ordered.filter((ep) => {
|
|
3149
3158
|
const matchesQuery = !nq || normalizeText2(ep.product.title).includes(nq);
|
|
3150
3159
|
const matchesType = activeType === FILTERS_ALL2 || (ep.product.productType ?? "") === activeType;
|
|
3151
3160
|
return matchesQuery && matchesType;
|
|
3152
3161
|
});
|
|
3153
|
-
}, [
|
|
3162
|
+
}, [ordered, query, activeType]);
|
|
3154
3163
|
const showEmpty = filtered.length === 0 && query.trim().length > 0;
|
|
3155
3164
|
function onOverlayMouseDown(e) {
|
|
3156
3165
|
mouseDownTarget.current = e.target;
|
|
@@ -3208,6 +3217,7 @@ function MultiStepPicker({
|
|
|
3208
3217
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
3209
3218
|
"button",
|
|
3210
3219
|
{
|
|
3220
|
+
ref: closeBtnRef,
|
|
3211
3221
|
type: "button",
|
|
3212
3222
|
className: "lb-mix-match__modal-close",
|
|
3213
3223
|
"data-modal-close": true,
|
|
@@ -3295,7 +3305,7 @@ function MultiStepPicker({
|
|
|
3295
3305
|
})
|
|
3296
3306
|
}
|
|
3297
3307
|
),
|
|
3298
|
-
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "lb-mix-match__modal-list", "data-modal-list": true, children: filtered.map((ep) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
3308
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "lb-mix-match__modal-list", "data-modal-list": true, ref: listRef, children: filtered.map((ep) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
3299
3309
|
MultiStepPickerRow,
|
|
3300
3310
|
{
|
|
3301
3311
|
bundle,
|
|
@@ -3386,27 +3396,27 @@ function MultiStepPickerRow({
|
|
|
3386
3396
|
const { product, variants, isOos } = eligible;
|
|
3387
3397
|
const rule = ruleFor3(bundle, product.id);
|
|
3388
3398
|
const step = bundle.steps[stepIndex];
|
|
3389
|
-
const optionNames = (0,
|
|
3399
|
+
const optionNames = (0, import_react15.useMemo)(() => {
|
|
3390
3400
|
const first = variants[0];
|
|
3391
3401
|
return first ? first.selectedOptions.map((o) => o.name) : [];
|
|
3392
3402
|
}, [variants]);
|
|
3393
3403
|
const showPicker = variants.length > 1 && optionNames.length > 0;
|
|
3394
3404
|
const { selectedValues, selectedVariant, setOptionValue, optionsFor } = useVariantSelection({ variants, optionNames });
|
|
3395
|
-
const currentVariant = selectedVariant ?? variants.find((v) => (0,
|
|
3405
|
+
const currentVariant = selectedVariant ?? variants.find((v) => (0, import_core13.isVariantFulfillable)(v, rule.min)) ?? eligible.firstAvailableVariant ?? variants[0] ?? null;
|
|
3396
3406
|
const vRule = ruleFor3(bundle, product.id, currentVariant?.id);
|
|
3397
3407
|
const variantMax = variantRuleFor3(bundle, currentVariant?.id)?.max;
|
|
3398
|
-
const stepPicks = (0,
|
|
3408
|
+
const stepPicks = (0, import_react15.useMemo)(
|
|
3399
3409
|
() => selections[stepIndex] ?? [],
|
|
3400
3410
|
[selections, stepIndex]
|
|
3401
3411
|
);
|
|
3402
3412
|
const unitsInStep = stepPicks.reduce((sum, s) => sum + s.quantity, 0);
|
|
3403
|
-
const committedSelection = (0,
|
|
3413
|
+
const committedSelection = (0, import_react15.useMemo)(
|
|
3404
3414
|
() => currentVariant ? stepPicks.find((s) => s.variantId === currentVariant.id) ?? null : null,
|
|
3405
3415
|
[stepPicks, currentVariant]
|
|
3406
3416
|
);
|
|
3407
3417
|
const variantInStep = committedSelection !== null;
|
|
3408
3418
|
const committedQty = committedSelection?.quantity ?? 0;
|
|
3409
|
-
const productOtherUnits = (0,
|
|
3419
|
+
const productOtherUnits = (0, import_react15.useMemo)(() => {
|
|
3410
3420
|
if (!currentVariant) return 0;
|
|
3411
3421
|
let sum = 0;
|
|
3412
3422
|
for (let i = 0; i < selections.length; i++) {
|
|
@@ -3418,7 +3428,7 @@ function MultiStepPickerRow({
|
|
|
3418
3428
|
}
|
|
3419
3429
|
return sum;
|
|
3420
3430
|
}, [selections, product.id, currentVariant, stepIndex]);
|
|
3421
|
-
const variantOtherUnits = (0,
|
|
3431
|
+
const variantOtherUnits = (0, import_react15.useMemo)(() => {
|
|
3422
3432
|
if (!currentVariant) return 0;
|
|
3423
3433
|
let sum = 0;
|
|
3424
3434
|
for (const picks of selections) {
|
|
@@ -3430,7 +3440,7 @@ function MultiStepPickerRow({
|
|
|
3430
3440
|
}, [selections, currentVariant, committedQty]);
|
|
3431
3441
|
const swapUnits = currentVariant ? swapUnitsFor?.(stepIndex, product.id, currentVariant.id) ?? 0 : 0;
|
|
3432
3442
|
const inSwapState = swapUnits >= vRule.min;
|
|
3433
|
-
const room = (0,
|
|
3443
|
+
const room = (0, import_core13.stepHeadroom)(step?.maxQuantity ?? null, unitsInStep);
|
|
3434
3444
|
const productHeadroom = Math.min(
|
|
3435
3445
|
rule.max - productOtherUnits,
|
|
3436
3446
|
variantMax ?? Infinity
|
|
@@ -3439,20 +3449,20 @@ function MultiStepPickerRow({
|
|
|
3439
3449
|
productHeadroom,
|
|
3440
3450
|
room === Number.POSITIVE_INFINITY ? productHeadroom : committedQty + room
|
|
3441
3451
|
);
|
|
3442
|
-
const stockRemaining = currentVariant ? (0,
|
|
3452
|
+
const stockRemaining = currentVariant ? (0, import_core13.maxAddableQuantity)(currentVariant, Number.MAX_SAFE_INTEGER, variantOtherUnits) : 0;
|
|
3443
3453
|
const cap = Math.max(0, Math.min(spotCeiling, stockRemaining));
|
|
3444
3454
|
const stepperMax = Math.max(vRule.min, cap);
|
|
3445
|
-
const [qty, setQty] = (0,
|
|
3446
|
-
(0,
|
|
3455
|
+
const [qty, setQty] = (0, import_react15.useState)(vRule.min);
|
|
3456
|
+
(0, import_react15.useEffect)(() => {
|
|
3447
3457
|
setQty((prev) => Math.max(vRule.min, Math.min(prev, stepperMax)));
|
|
3448
3458
|
}, [stepperMax, vRule.min]);
|
|
3449
3459
|
const currentVariantId = currentVariant?.id ?? null;
|
|
3450
|
-
(0,
|
|
3460
|
+
(0, import_react15.useEffect)(() => {
|
|
3451
3461
|
if (!variantInStep) setQty(vRule.min);
|
|
3452
3462
|
}, [variantInStep, currentVariantId, vRule.min]);
|
|
3453
3463
|
if (!currentVariant || !step) return null;
|
|
3454
3464
|
const thumbImage = currentVariant.image ?? product.featuredImage ?? null;
|
|
3455
|
-
const unitPriceText = (0,
|
|
3465
|
+
const unitPriceText = (0, import_core13.formatUnitPrice)(
|
|
3456
3466
|
currentVariant.unitPrice,
|
|
3457
3467
|
currentVariant.unitPriceMeasurement,
|
|
3458
3468
|
currency
|
|
@@ -3522,8 +3532,8 @@ function MultiStepPickerRow({
|
|
|
3522
3532
|
}
|
|
3523
3533
|
) }),
|
|
3524
3534
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("p", { className: "lb-mix-match__modal-product-price", children: [
|
|
3525
|
-
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { "data-row-price-sale": true, children: (0,
|
|
3526
|
-
compareCents !== null && compareCents > priceCents && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("s", { className: "lb-mix-match__modal-product-compare", "data-row-compare": true, children: (0,
|
|
3535
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { "data-row-price-sale": true, children: (0, import_core13.formatMoney)(priceCents, currency) }),
|
|
3536
|
+
compareCents !== null && compareCents > priceCents && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("s", { className: "lb-mix-match__modal-product-compare", "data-row-compare": true, children: (0, import_core13.formatMoney)(compareCents, currency) })
|
|
3527
3537
|
] }),
|
|
3528
3538
|
unitPriceText && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "lb-mix-match__modal-product-unit-price lb-bundle-product-unit-price", children: unitPriceText }),
|
|
3529
3539
|
showPicker && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "lb-bundle-variant-option-groups", children: optionNames.map((optionName, optionIndex) => {
|
|
@@ -3676,7 +3686,7 @@ function ruleFor4(bundle, productId, variantId) {
|
|
|
3676
3686
|
const vRule = bundle.variantRules[variantId];
|
|
3677
3687
|
if (vRule) return vRule;
|
|
3678
3688
|
}
|
|
3679
|
-
return bundle.productRules[productId] ??
|
|
3689
|
+
return bundle.productRules[productId] ?? import_core14.DEFAULT_PRODUCT_RULE;
|
|
3680
3690
|
}
|
|
3681
3691
|
function variantRuleFor4(bundle, variantId) {
|
|
3682
3692
|
return variantId ? bundle.variantRules[variantId] : void 0;
|
|
@@ -3718,10 +3728,6 @@ function MultiStepBundle(props) {
|
|
|
3718
3728
|
storefrontAccessToken,
|
|
3719
3729
|
bundleGid
|
|
3720
3730
|
});
|
|
3721
|
-
const { outOfStockBehavior } = useShopSettings({
|
|
3722
|
-
shopDomain,
|
|
3723
|
-
storefrontAccessToken
|
|
3724
|
-
});
|
|
3725
3731
|
const { elementRef, trackAddToCart } = useAnalytics({
|
|
3726
3732
|
shopDomain,
|
|
3727
3733
|
appUrl: appUrl ?? `https://${shopDomain}`,
|
|
@@ -3729,14 +3735,14 @@ function MultiStepBundle(props) {
|
|
|
3729
3735
|
bundleType: "multi_step",
|
|
3730
3736
|
enabled: analyticsEnabled !== false
|
|
3731
3737
|
});
|
|
3732
|
-
const [selections, setSelections] = (0,
|
|
3733
|
-
const [pickerOpenAt, setPickerOpenAt] = (0,
|
|
3734
|
-
const [addingToCart, setAddingToCart] = (0,
|
|
3735
|
-
const [cartError, setCartError] = (0,
|
|
3738
|
+
const [selections, setSelections] = (0, import_react16.useState)([]);
|
|
3739
|
+
const [pickerOpenAt, setPickerOpenAt] = (0, import_react16.useState)(null);
|
|
3740
|
+
const [addingToCart, setAddingToCart] = (0, import_react16.useState)(false);
|
|
3741
|
+
const [cartError, setCartError] = (0, import_react16.useState)(null);
|
|
3736
3742
|
const bundle = result.status === "success" && result.bundle.bundleType === "multi_step" ? result.bundle : null;
|
|
3737
3743
|
const setConfigVarsRef = useWidgetConfigVars(bundle?.widgetConfig);
|
|
3738
3744
|
const setModalConfigVarsRef = useWidgetConfigVars(bundle?.widgetConfig);
|
|
3739
|
-
(0,
|
|
3745
|
+
(0, import_react16.useEffect)(() => {
|
|
3740
3746
|
if (result.status === "error") {
|
|
3741
3747
|
onError?.(result.error);
|
|
3742
3748
|
return;
|
|
@@ -3749,17 +3755,17 @@ function MultiStepBundle(props) {
|
|
|
3749
3755
|
);
|
|
3750
3756
|
}
|
|
3751
3757
|
}, [result, onError]);
|
|
3752
|
-
const steps = (0,
|
|
3753
|
-
(0,
|
|
3758
|
+
const steps = (0, import_react16.useMemo)(() => bundle?.steps ?? [], [bundle]);
|
|
3759
|
+
(0, import_react16.useEffect)(() => {
|
|
3754
3760
|
const seeded = steps.map(() => []);
|
|
3755
3761
|
if (bundle) {
|
|
3756
3762
|
const seedCurrency = bundle.products[0]?.priceRange.minVariantPrice.currencyCode ?? "USD";
|
|
3757
3763
|
for (const [pid, rule] of Object.entries(bundle.productRules)) {
|
|
3758
3764
|
if (!rule.required) continue;
|
|
3759
3765
|
for (let i = 0; i < steps.length; i++) {
|
|
3760
|
-
const product = (0,
|
|
3766
|
+
const product = (0, import_core14.productsForStep)(bundle, i).find((p) => p.id === pid);
|
|
3761
3767
|
const variant = product?.variants.nodes.find(
|
|
3762
|
-
(v) => (0,
|
|
3768
|
+
(v) => (0, import_core14.isVariantFulfillable)(v, rule.min)
|
|
3763
3769
|
);
|
|
3764
3770
|
if (!product || !variant) continue;
|
|
3765
3771
|
seeded[i].push({
|
|
@@ -3771,7 +3777,7 @@ function MultiStepBundle(props) {
|
|
|
3771
3777
|
featuredImage: variant.image?.url ?? product.featuredImage?.url ?? null,
|
|
3772
3778
|
price: parseFloat(variant.price.amount),
|
|
3773
3779
|
compareAtPrice: variant.compareAtPrice ? parseFloat(variant.compareAtPrice.amount) : null,
|
|
3774
|
-
unitPrice: (0,
|
|
3780
|
+
unitPrice: (0, import_core14.formatUnitPrice)(
|
|
3775
3781
|
variant.unitPrice,
|
|
3776
3782
|
variant.unitPriceMeasurement,
|
|
3777
3783
|
seedCurrency
|
|
@@ -3785,26 +3791,26 @@ function MultiStepBundle(props) {
|
|
|
3785
3791
|
setSelections(seeded);
|
|
3786
3792
|
}, [bundle, steps]);
|
|
3787
3793
|
const showStepper = bundle?.widgetConfig.mixMatchShowQuantitySelector !== false;
|
|
3788
|
-
const totalMin = (0,
|
|
3794
|
+
const totalMin = (0, import_react16.useMemo)(
|
|
3789
3795
|
() => steps.reduce((sum, s) => sum + s.minQuantity, 0),
|
|
3790
3796
|
[steps]
|
|
3791
3797
|
);
|
|
3792
|
-
const unitsByStep = (0,
|
|
3798
|
+
const unitsByStep = (0, import_react16.useMemo)(
|
|
3793
3799
|
() => selections.map((picks) => picks.reduce((sum, s) => sum + s.quantity, 0)),
|
|
3794
3800
|
[selections]
|
|
3795
3801
|
);
|
|
3796
|
-
const totalQuantity = (0,
|
|
3802
|
+
const totalQuantity = (0, import_react16.useMemo)(
|
|
3797
3803
|
() => unitsByStep.reduce((sum, u) => sum + u, 0),
|
|
3798
3804
|
[unitsByStep]
|
|
3799
3805
|
);
|
|
3800
|
-
const stepSatisfied = (0,
|
|
3806
|
+
const stepSatisfied = (0, import_react16.useCallback)(
|
|
3801
3807
|
(i) => (unitsByStep[i] ?? 0) >= (steps[i]?.minQuantity ?? 1),
|
|
3802
3808
|
[unitsByStep, steps]
|
|
3803
3809
|
);
|
|
3804
3810
|
const allSatisfied = steps.length > 0 && steps.every((_, i) => stepSatisfied(i));
|
|
3805
3811
|
const valid = !!bundle && allSatisfied;
|
|
3806
3812
|
const completedSteps = steps.filter((_, i) => stepSatisfied(i)).length;
|
|
3807
|
-
const summary = (0,
|
|
3813
|
+
const summary = (0, import_react16.useMemo)(() => {
|
|
3808
3814
|
let comparePrice = 0;
|
|
3809
3815
|
let salePrice = 0;
|
|
3810
3816
|
for (const stepPicks of selections) {
|
|
@@ -3812,7 +3818,7 @@ function MultiStepBundle(props) {
|
|
|
3812
3818
|
if (sel.quantity <= 0) continue;
|
|
3813
3819
|
comparePrice += sel.price * sel.quantity;
|
|
3814
3820
|
if (bundle) {
|
|
3815
|
-
salePrice += (0,
|
|
3821
|
+
salePrice += (0, import_core14.calculateDiscount)(
|
|
3816
3822
|
sel.price,
|
|
3817
3823
|
bundle.discountConfig.discountType,
|
|
3818
3824
|
bundle.discountConfig.discountValue
|
|
@@ -3824,7 +3830,7 @@ function MultiStepBundle(props) {
|
|
|
3824
3830
|
const savingsPercent = comparePrice > 0 && savings > 0 ? Math.round(savings / comparePrice * 100) : 0;
|
|
3825
3831
|
return { comparePrice, salePrice, savings, savingsPercent };
|
|
3826
3832
|
}, [selections, bundle]);
|
|
3827
|
-
const addSelection = (0,
|
|
3833
|
+
const addSelection = (0, import_react16.useCallback)((step, item) => {
|
|
3828
3834
|
setSelections((prev) => {
|
|
3829
3835
|
if (step < 0 || step >= prev.length) return prev;
|
|
3830
3836
|
if (prev[step].some((s) => s.variantId === item.variantId)) return prev;
|
|
@@ -3833,7 +3839,7 @@ function MultiStepBundle(props) {
|
|
|
3833
3839
|
return next;
|
|
3834
3840
|
});
|
|
3835
3841
|
}, []);
|
|
3836
|
-
const swapSelection = (0,
|
|
3842
|
+
const swapSelection = (0, import_react16.useCallback)(
|
|
3837
3843
|
(step, item) => {
|
|
3838
3844
|
setSelections((prev) => {
|
|
3839
3845
|
if (!bundle || step < 0 || step >= prev.length) return prev;
|
|
@@ -3868,7 +3874,7 @@ function MultiStepBundle(props) {
|
|
|
3868
3874
|
},
|
|
3869
3875
|
[bundle]
|
|
3870
3876
|
);
|
|
3871
|
-
const updateQuantity = (0,
|
|
3877
|
+
const updateQuantity = (0, import_react16.useCallback)(
|
|
3872
3878
|
(step, productId, variantId, quantity) => {
|
|
3873
3879
|
setSelections((prev) => {
|
|
3874
3880
|
if (step < 0 || step >= prev.length) return prev;
|
|
@@ -3887,7 +3893,7 @@ function MultiStepBundle(props) {
|
|
|
3887
3893
|
},
|
|
3888
3894
|
[]
|
|
3889
3895
|
);
|
|
3890
|
-
const removeVariant = (0,
|
|
3896
|
+
const removeVariant = (0, import_react16.useCallback)(
|
|
3891
3897
|
(step, variantId) => {
|
|
3892
3898
|
setSelections((prev) => {
|
|
3893
3899
|
if (step < 0 || step >= prev.length) return prev;
|
|
@@ -3903,7 +3909,7 @@ function MultiStepBundle(props) {
|
|
|
3903
3909
|
},
|
|
3904
3910
|
[bundle]
|
|
3905
3911
|
);
|
|
3906
|
-
const removeSlot = (0,
|
|
3912
|
+
const removeSlot = (0, import_react16.useCallback)(
|
|
3907
3913
|
(step, index) => {
|
|
3908
3914
|
setSelections((prev) => {
|
|
3909
3915
|
if (step < 0 || step >= prev.length) return prev;
|
|
@@ -3919,7 +3925,7 @@ function MultiStepBundle(props) {
|
|
|
3919
3925
|
},
|
|
3920
3926
|
[bundle]
|
|
3921
3927
|
);
|
|
3922
|
-
const handleAddToCart = (0,
|
|
3928
|
+
const handleAddToCart = (0, import_react16.useCallback)(async () => {
|
|
3923
3929
|
if (!bundle || !valid) return;
|
|
3924
3930
|
const grouped = /* @__PURE__ */ new Map();
|
|
3925
3931
|
for (const stepPicks of selections) {
|
|
@@ -3974,8 +3980,8 @@ function MultiStepBundle(props) {
|
|
|
3974
3980
|
([pid, rule]) => {
|
|
3975
3981
|
if (!rule.required) return false;
|
|
3976
3982
|
return !steps.some(
|
|
3977
|
-
(_, i) => (0,
|
|
3978
|
-
(p) => p.id === pid && p.variants.nodes.some((v) => (0,
|
|
3983
|
+
(_, i) => (0, import_core14.productsForStep)(bundle, i).some(
|
|
3984
|
+
(p) => p.id === pid && p.variants.nodes.some((v) => (0, import_core14.isVariantFulfillable)(v, rule.min))
|
|
3979
3985
|
)
|
|
3980
3986
|
);
|
|
3981
3987
|
}
|
|
@@ -4099,8 +4105,8 @@ function MultiStepBundle(props) {
|
|
|
4099
4105
|
),
|
|
4100
4106
|
item.variantTitle && item.variantTitle !== "Default Title" && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "lb-mix-match__filled-variant", children: item.variantTitle }),
|
|
4101
4107
|
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "lb-mix-match__filled-price", children: [
|
|
4102
|
-
item.compareAtPrice !== null && item.compareAtPrice > item.price && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("s", { className: "lb-mix-match__filled-compare", children: (0,
|
|
4103
|
-
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "lb-bundle-product-price", children: (0,
|
|
4108
|
+
item.compareAtPrice !== null && item.compareAtPrice > item.price && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("s", { className: "lb-mix-match__filled-compare", children: (0, import_core14.formatMoney)(item.compareAtPrice, currency) }),
|
|
4109
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "lb-bundle-product-price", children: (0, import_core14.formatMoney)(item.price, currency) }),
|
|
4104
4110
|
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "lb-bundle-qty-inline", children: `\xD7${item.quantity}` })
|
|
4105
4111
|
] }),
|
|
4106
4112
|
item.unitPrice && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "lb-bundle-product-unit-price", children: item.unitPrice })
|
|
@@ -4130,7 +4136,7 @@ function MultiStepBundle(props) {
|
|
|
4130
4136
|
))
|
|
4131
4137
|
}
|
|
4132
4138
|
),
|
|
4133
|
-
(0,
|
|
4139
|
+
(0, import_core14.stepHeadroom)(step.maxQuantity, unitsByStep[i] ?? 0) !== 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
|
|
4134
4140
|
"button",
|
|
4135
4141
|
{
|
|
4136
4142
|
type: "button",
|
|
@@ -4162,7 +4168,7 @@ function MultiStepBundle(props) {
|
|
|
4162
4168
|
bundle.widgetConfig.savingsBar.visible && summary.savings > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("p", { className: "lb-bundle-savings-line", "data-savings-bar": true, children: [
|
|
4163
4169
|
"You save",
|
|
4164
4170
|
" ",
|
|
4165
|
-
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { "data-savings-amount": true, children: (0,
|
|
4171
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { "data-savings-amount": true, children: (0, import_core14.formatMoney)(summary.savings, currency) }),
|
|
4166
4172
|
" ",
|
|
4167
4173
|
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { "data-savings-percent": true, children: [
|
|
4168
4174
|
"(",
|
|
@@ -4172,8 +4178,8 @@ function MultiStepBundle(props) {
|
|
|
4172
4178
|
] })
|
|
4173
4179
|
] }),
|
|
4174
4180
|
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "lb-bundle-summary__prices", children: [
|
|
4175
|
-
bundle.widgetConfig.pricing.showComparePrice && summary.savings > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "lb-bundle-compare-price", "data-compare-price": true, children: (0,
|
|
4176
|
-
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "lb-bundle-sale-price", "data-sale-price": true, children: (0,
|
|
4181
|
+
bundle.widgetConfig.pricing.showComparePrice && summary.savings > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "lb-bundle-compare-price", "data-compare-price": true, children: (0, import_core14.formatMoney)(summary.comparePrice, currency) }),
|
|
4182
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "lb-bundle-sale-price", "data-sale-price": true, children: (0, import_core14.formatMoney)(summary.salePrice, currency) })
|
|
4177
4183
|
] })
|
|
4178
4184
|
] }),
|
|
4179
4185
|
cartError && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { className: "lb-bundle__error", role: "alert", children: cartError }),
|
|
@@ -4190,7 +4196,6 @@ function MultiStepBundle(props) {
|
|
|
4190
4196
|
pickerOpenAt !== null && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
4191
4197
|
MultiStepPicker,
|
|
4192
4198
|
{
|
|
4193
|
-
outOfStockBehavior,
|
|
4194
4199
|
bundle,
|
|
4195
4200
|
currency,
|
|
4196
4201
|
initialStep: pickerOpenAt,
|
|
@@ -4217,9 +4222,9 @@ function MultiStepBundle(props) {
|
|
|
4217
4222
|
);
|
|
4218
4223
|
}
|
|
4219
4224
|
function stepHasInStockProduct(bundle, stepIndex) {
|
|
4220
|
-
return (0,
|
|
4225
|
+
return (0, import_core14.productsForStep)(bundle, stepIndex).some((product) => {
|
|
4221
4226
|
const rule = ruleFor4(bundle, product.id);
|
|
4222
|
-
return product.variants.nodes.some((v) => (0,
|
|
4227
|
+
return product.variants.nodes.some((v) => (0, import_core14.isVariantFulfillable)(v, rule.min));
|
|
4223
4228
|
});
|
|
4224
4229
|
}
|
|
4225
4230
|
function PlusIcon2() {
|
|
@@ -4264,21 +4269,21 @@ async function fetchShopCustomCss(options) {
|
|
|
4264
4269
|
}
|
|
4265
4270
|
|
|
4266
4271
|
// src/hooks/useBundlesForProduct.ts
|
|
4267
|
-
var
|
|
4268
|
-
var
|
|
4272
|
+
var import_react17 = require("react");
|
|
4273
|
+
var import_core15 = require("@lime-bundles/core");
|
|
4269
4274
|
var INITIAL_STATE2 = {
|
|
4270
4275
|
status: "loading",
|
|
4271
4276
|
bundles: null,
|
|
4272
4277
|
error: null
|
|
4273
4278
|
};
|
|
4274
4279
|
function useBundlesForProduct(options) {
|
|
4275
|
-
const [state, setState] = (0,
|
|
4280
|
+
const [state, setState] = (0, import_react17.useState)(
|
|
4276
4281
|
INITIAL_STATE2
|
|
4277
4282
|
);
|
|
4278
|
-
(0,
|
|
4283
|
+
(0, import_react17.useEffect)(() => {
|
|
4279
4284
|
const controller = new AbortController();
|
|
4280
4285
|
setState(INITIAL_STATE2);
|
|
4281
|
-
(0,
|
|
4286
|
+
(0, import_core15.fetchBundlesForProduct)({
|
|
4282
4287
|
shopDomain: options.shopDomain,
|
|
4283
4288
|
storefrontAccessToken: options.storefrontAccessToken,
|
|
4284
4289
|
productHandle: options.productHandle,
|
|
@@ -4301,7 +4306,7 @@ function useBundlesForProduct(options) {
|
|
|
4301
4306
|
signal: controller.signal
|
|
4302
4307
|
}).then((settings) => {
|
|
4303
4308
|
if (controller.signal.aborted) return;
|
|
4304
|
-
(0,
|
|
4309
|
+
(0, import_core15.injectCustomCss)(options.shopDomain, settings.customCss);
|
|
4305
4310
|
}).catch(() => {
|
|
4306
4311
|
});
|
|
4307
4312
|
return () => {
|
|
@@ -4317,8 +4322,8 @@ function useBundlesForProduct(options) {
|
|
|
4317
4322
|
}
|
|
4318
4323
|
|
|
4319
4324
|
// src/index.ts
|
|
4325
|
+
var import_core16 = require("@lime-bundles/core");
|
|
4320
4326
|
var import_core17 = require("@lime-bundles/core");
|
|
4321
|
-
var import_core18 = require("@lime-bundles/core");
|
|
4322
4327
|
// Annotate the CommonJS export names for ESM import in node:
|
|
4323
4328
|
0 && (module.exports = {
|
|
4324
4329
|
BUNDLES_FOR_PRODUCT_QUERY,
|
|
@@ -4326,7 +4331,6 @@ var import_core18 = require("@lime-bundles/core");
|
|
|
4326
4331
|
BogoBundle,
|
|
4327
4332
|
BundleCountdown,
|
|
4328
4333
|
BundleParseError,
|
|
4329
|
-
DEFAULT_OUT_OF_STOCK_BEHAVIOR,
|
|
4330
4334
|
FixedBundle,
|
|
4331
4335
|
MixMatchBundle,
|
|
4332
4336
|
MultiStepBundle,
|
|
@@ -4357,7 +4361,6 @@ var import_core18 = require("@lime-bundles/core");
|
|
|
4357
4361
|
parseCents,
|
|
4358
4362
|
parseMetaobjectBundle,
|
|
4359
4363
|
parseMetaobjectBundleStrict,
|
|
4360
|
-
parseOutOfStockBehavior,
|
|
4361
4364
|
percentageDiscountUnit,
|
|
4362
4365
|
productsForStep,
|
|
4363
4366
|
reportAddToCart,
|
|
@@ -4369,7 +4372,6 @@ var import_core18 = require("@lime-bundles/core");
|
|
|
4369
4372
|
useAnalytics,
|
|
4370
4373
|
useBundleData,
|
|
4371
4374
|
useBundlesForProduct,
|
|
4372
|
-
useShopSettings,
|
|
4373
4375
|
useVariantSelection,
|
|
4374
4376
|
useWidgetConfigVars,
|
|
4375
4377
|
validateQuantity
|