@lime-bundles/react 7.3.0 → 7.4.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.d.cts CHANGED
@@ -59,6 +59,14 @@ interface BundleComponentProps {
59
59
  * more than one product should pass it.
60
60
  */
61
61
  productId?: string;
62
+ /**
63
+ * Store→presentment currency rate. Merchant-configured amounts
64
+ * (fixed_amount / flat_price values, volume tier amounts) are stored in
65
+ * the shop's store currency; pass the rate for the buyer's currency so
66
+ * quoted sale prices match what the checkout's discount function
67
+ * charges. Omit (or pass 1) on store-currency storefronts.
68
+ */
69
+ currencyRate?: number;
62
70
  }
63
71
 
64
72
  declare function FixedBundle(props: BundleComponentProps): react_jsx_runtime.JSX.Element | null;
@@ -187,12 +195,24 @@ interface FetchBundleDataOptions {
187
195
  */
188
196
  buyer?: BuyerResolver;
189
197
  /**
190
- * Shopify Market GID for the current visitor. When set and the bundle's
191
- * `marketVisibility === "specific"`, the bundle is filtered out if its
192
- * `marketIds` doesn't include this market. Returned to the caller via
193
- * `BundleVisibilityWarning[]` for surfacing.
198
+ * Shopify Market GID for the current visitor. Legacy market-gate signal:
199
+ * when set and the bundle's `marketVisibility === "specific"`, a direct
200
+ * market-id match shows the bundle. Prefer letting the gate work from
201
+ * `country` (retail) and `buyer.companyLocationId` (B2B) — Shopify has
202
+ * deprecated market IDs as buyer-targeting signals, and a B2B buyer's
203
+ * company-location market has no storefront market context at all.
204
+ * Misses are returned via `BundleVisibilityWarning[]` for surfacing.
194
205
  */
195
206
  marketId?: string;
207
+ /**
208
+ * Store→presentment currency rate. Merchant-configured amounts
209
+ * (fixed_amount / flat_price values, volume tier amounts) are stored in
210
+ * the shop's store currency; pass the rate for the buyer's currency so
211
+ * quoted sale prices match what the checkout's discount function
212
+ * charges. Omit (or pass 1) on store-currency storefronts. See
213
+ * `convertBundleToPresentment` in `@lime-bundles/core`.
214
+ */
215
+ currencyRate?: number;
196
216
  }
197
217
  declare function fetchBundleData(options: FetchBundleDataOptions): Promise<ParsedBundle>;
198
218
 
@@ -229,6 +249,12 @@ interface UseBundleDataOptions {
229
249
  buyer?: BuyerResolver;
230
250
  /** Current visitor's Market GID — filters market-restricted bundles client-side. */
231
251
  marketId?: string;
252
+ /**
253
+ * Store→presentment currency rate for merchant-configured amounts
254
+ * (fixed_amount / flat_price values, volume tier amounts). See
255
+ * `FetchBundleDataOptions.currencyRate`.
256
+ */
257
+ currencyRate?: number;
232
258
  }
233
259
  type UseBundleDataResult = {
234
260
  status: "loading";
@@ -251,6 +277,12 @@ interface UseBundlesForProductOptions {
251
277
  productHandle: string;
252
278
  /** Forwarded as `Shopify-Storefront-Buyer-IP` — required for SSR paths. */
253
279
  buyerIp?: string;
280
+ /**
281
+ * Store→presentment currency rate for merchant-configured amounts
282
+ * (fixed_amount / flat_price values, volume tier amounts). See
283
+ * `FetchBundlesForProductOptions.currencyRate`.
284
+ */
285
+ currencyRate?: number;
254
286
  }
255
287
  type UseBundlesForProductResult = {
256
288
  status: "loading";
package/dist/index.d.ts CHANGED
@@ -59,6 +59,14 @@ interface BundleComponentProps {
59
59
  * more than one product should pass it.
60
60
  */
61
61
  productId?: string;
62
+ /**
63
+ * Store→presentment currency rate. Merchant-configured amounts
64
+ * (fixed_amount / flat_price values, volume tier amounts) are stored in
65
+ * the shop's store currency; pass the rate for the buyer's currency so
66
+ * quoted sale prices match what the checkout's discount function
67
+ * charges. Omit (or pass 1) on store-currency storefronts.
68
+ */
69
+ currencyRate?: number;
62
70
  }
63
71
 
64
72
  declare function FixedBundle(props: BundleComponentProps): react_jsx_runtime.JSX.Element | null;
@@ -187,12 +195,24 @@ interface FetchBundleDataOptions {
187
195
  */
188
196
  buyer?: BuyerResolver;
189
197
  /**
190
- * Shopify Market GID for the current visitor. When set and the bundle's
191
- * `marketVisibility === "specific"`, the bundle is filtered out if its
192
- * `marketIds` doesn't include this market. Returned to the caller via
193
- * `BundleVisibilityWarning[]` for surfacing.
198
+ * Shopify Market GID for the current visitor. Legacy market-gate signal:
199
+ * when set and the bundle's `marketVisibility === "specific"`, a direct
200
+ * market-id match shows the bundle. Prefer letting the gate work from
201
+ * `country` (retail) and `buyer.companyLocationId` (B2B) — Shopify has
202
+ * deprecated market IDs as buyer-targeting signals, and a B2B buyer's
203
+ * company-location market has no storefront market context at all.
204
+ * Misses are returned via `BundleVisibilityWarning[]` for surfacing.
194
205
  */
195
206
  marketId?: string;
207
+ /**
208
+ * Store→presentment currency rate. Merchant-configured amounts
209
+ * (fixed_amount / flat_price values, volume tier amounts) are stored in
210
+ * the shop's store currency; pass the rate for the buyer's currency so
211
+ * quoted sale prices match what the checkout's discount function
212
+ * charges. Omit (or pass 1) on store-currency storefronts. See
213
+ * `convertBundleToPresentment` in `@lime-bundles/core`.
214
+ */
215
+ currencyRate?: number;
196
216
  }
197
217
  declare function fetchBundleData(options: FetchBundleDataOptions): Promise<ParsedBundle>;
198
218
 
@@ -229,6 +249,12 @@ interface UseBundleDataOptions {
229
249
  buyer?: BuyerResolver;
230
250
  /** Current visitor's Market GID — filters market-restricted bundles client-side. */
231
251
  marketId?: string;
252
+ /**
253
+ * Store→presentment currency rate for merchant-configured amounts
254
+ * (fixed_amount / flat_price values, volume tier amounts). See
255
+ * `FetchBundleDataOptions.currencyRate`.
256
+ */
257
+ currencyRate?: number;
232
258
  }
233
259
  type UseBundleDataResult = {
234
260
  status: "loading";
@@ -251,6 +277,12 @@ interface UseBundlesForProductOptions {
251
277
  productHandle: string;
252
278
  /** Forwarded as `Shopify-Storefront-Buyer-IP` — required for SSR paths. */
253
279
  buyerIp?: string;
280
+ /**
281
+ * Store→presentment currency rate for merchant-configured amounts
282
+ * (fixed_amount / flat_price values, volume tier amounts). See
283
+ * `FetchBundlesForProductOptions.currencyRate`.
284
+ */
285
+ currencyRate?: number;
254
286
  }
255
287
  type UseBundlesForProductResult = {
256
288
  status: "loading";
package/dist/index.js CHANGED
@@ -22,7 +22,8 @@ import {
22
22
  BUNDLE_METAOBJECT_QUERY,
23
23
  withInContext,
24
24
  parseMetaobjectBundleStrict,
25
- isVisibleInMarket,
25
+ convertBundleToPresentment,
26
+ isVisibleToBuyer,
26
27
  BundleParseError
27
28
  } from "@lime-bundles/core";
28
29
  async function fetchBundleData(options) {
@@ -36,6 +37,7 @@ async function fetchBundleData(options) {
36
37
  return result.bundle;
37
38
  }
38
39
  async function fetchBundleDataWithWarnings(options) {
40
+ const buyer = typeof options.buyer === "function" ? await options.buyer() : options.buyer;
39
41
  const config = {
40
42
  shopDomain: options.shopDomain,
41
43
  accessToken: options.storefrontAccessToken,
@@ -43,7 +45,7 @@ async function fetchBundleDataWithWarnings(options) {
43
45
  apiVersion: options.apiVersion,
44
46
  country: options.country,
45
47
  language: options.language,
46
- buyer: options.buyer
48
+ buyer
47
49
  };
48
50
  const client = createStorefrontClient(config);
49
51
  const query = hasInContext(config) ? withInContext(BUNDLE_METAOBJECT_QUERY) : BUNDLE_METAOBJECT_QUERY;
@@ -58,11 +60,16 @@ async function fetchBundleDataWithWarnings(options) {
58
60
  "not_found"
59
61
  );
60
62
  }
61
- const bundle = parseMetaobjectBundleStrict(
62
- data.metaobject.id,
63
- data.metaobject.fields
63
+ const bundle = convertBundleToPresentment(
64
+ parseMetaobjectBundleStrict(data.metaobject.id, data.metaobject.fields),
65
+ options.currencyRate
64
66
  );
65
- if (!isVisibleInMarket(bundle, options.marketId)) {
67
+ const buyerContext = {
68
+ marketId: options.marketId,
69
+ countryCode: options.country,
70
+ companyLocationId: buyer?.companyLocationId
71
+ };
72
+ if (!isVisibleToBuyer(bundle, buyerContext)) {
66
73
  return {
67
74
  bundle: null,
68
75
  warnings: [
@@ -70,7 +77,11 @@ async function fetchBundleDataWithWarnings(options) {
70
77
  bundleGid: options.bundleGid,
71
78
  reason: "market_mismatch",
72
79
  currentMarketId: options.marketId ?? null,
73
- allowedMarketIds: bundle.marketIds
80
+ currentCountryCode: options.country ?? null,
81
+ currentCompanyLocationId: buyer?.companyLocationId ?? null,
82
+ allowedMarketIds: bundle.marketIds,
83
+ allowedCountryCodes: bundle.countryCodes,
84
+ allowedCompanyLocationIds: bundle.companyLocationIds
74
85
  }
75
86
  ]
76
87
  };
@@ -140,7 +151,8 @@ function useBundleData(options) {
140
151
  country: options.country,
141
152
  language: options.language,
142
153
  buyer: options.buyer,
143
- marketId: options.marketId
154
+ marketId: options.marketId,
155
+ currencyRate: options.currencyRate
144
156
  }).then((bundle) => {
145
157
  if (controller.signal.aborted) return;
146
158
  setState({ status: "success", bundle, error: null });
@@ -171,7 +183,8 @@ function useBundleData(options) {
171
183
  options.country,
172
184
  options.language,
173
185
  options.buyer,
174
- options.marketId
186
+ options.marketId,
187
+ options.currencyRate
175
188
  ]);
176
189
  return state;
177
190
  }
@@ -706,12 +719,22 @@ function FixedBundle(props) {
706
719
  analyticsEnabled,
707
720
  onAddToCart,
708
721
  onError,
709
- className
722
+ className,
723
+ country,
724
+ language,
725
+ buyer,
726
+ marketId,
727
+ currencyRate
710
728
  } = props;
711
729
  const result = useBundleData({
712
730
  shopDomain,
713
731
  storefrontAccessToken,
714
- bundleGid
732
+ bundleGid,
733
+ country,
734
+ language,
735
+ buyer,
736
+ marketId,
737
+ currencyRate
715
738
  });
716
739
  const { elementRef, trackAddToCart } = useAnalytics({
717
740
  shopDomain,
@@ -1784,12 +1807,22 @@ function MixMatchBundle(props) {
1784
1807
  onAddToCart,
1785
1808
  onError,
1786
1809
  className,
1787
- productHandle
1810
+ productHandle,
1811
+ country,
1812
+ language,
1813
+ buyer,
1814
+ marketId,
1815
+ currencyRate
1788
1816
  } = props;
1789
1817
  const result = useBundleData({
1790
1818
  shopDomain,
1791
1819
  storefrontAccessToken,
1792
- bundleGid
1820
+ bundleGid,
1821
+ country,
1822
+ language,
1823
+ buyer,
1824
+ marketId,
1825
+ currencyRate
1793
1826
  });
1794
1827
  const { elementRef, trackAddToCart } = useAnalytics({
1795
1828
  shopDomain,
@@ -2327,12 +2360,22 @@ function VolumeBundle(props) {
2327
2360
  onAddToCart,
2328
2361
  onError,
2329
2362
  className,
2330
- productId
2363
+ productId,
2364
+ country,
2365
+ language,
2366
+ buyer,
2367
+ marketId,
2368
+ currencyRate
2331
2369
  } = props;
2332
2370
  const result = useBundleData({
2333
2371
  shopDomain,
2334
2372
  storefrontAccessToken,
2335
- bundleGid
2373
+ bundleGid,
2374
+ country,
2375
+ language,
2376
+ buyer,
2377
+ marketId,
2378
+ currencyRate
2336
2379
  });
2337
2380
  const { elementRef, trackAddToCart } = useAnalytics({
2338
2381
  shopDomain,
@@ -2712,12 +2755,22 @@ function BogoBundle(props) {
2712
2755
  analyticsEnabled,
2713
2756
  onAddToCart,
2714
2757
  onError,
2715
- className
2758
+ className,
2759
+ country,
2760
+ language,
2761
+ buyer,
2762
+ marketId,
2763
+ currencyRate
2716
2764
  } = props;
2717
2765
  const result = useBundleData({
2718
2766
  shopDomain,
2719
2767
  storefrontAccessToken,
2720
- bundleGid
2768
+ bundleGid,
2769
+ country,
2770
+ language,
2771
+ buyer,
2772
+ marketId,
2773
+ currencyRate
2721
2774
  });
2722
2775
  const { elementRef, trackAddToCart } = useAnalytics({
2723
2776
  shopDomain,
@@ -3076,6 +3129,8 @@ function normalizeText2(str) {
3076
3129
  return str.normalize("NFD").replace(/[̀-ͯ]/g, "").toLowerCase();
3077
3130
  }
3078
3131
  var FILTERS_ALL2 = "__all__";
3132
+ var STEP_CASCADE_MS = 500;
3133
+ var AUTO_ADVANCE_HOLD_MS = 160;
3079
3134
  function MultiStepPicker({
3080
3135
  bundle,
3081
3136
  currency,
@@ -3099,6 +3154,8 @@ function MultiStepPicker({
3099
3154
  const closeBtnRef = useRef7(null);
3100
3155
  const listRef = useRef7(null);
3101
3156
  const mouseDownTarget = useRef7(null);
3157
+ const cascadeTimer = useRef7(null);
3158
+ const autoAdvanceTimer = useRef7(null);
3102
3159
  const [open, setOpen] = useState11(false);
3103
3160
  const [stepIndex, setStepIndex] = useState11(
3104
3161
  () => Math.max(0, Math.min(steps.length - 1, initialStep))
@@ -3134,6 +3191,14 @@ function MultiStepPicker({
3134
3191
  const id = requestAnimationFrame(() => setOpen(true));
3135
3192
  return () => cancelAnimationFrame(id);
3136
3193
  }, []);
3194
+ useEffect14(() => {
3195
+ return () => {
3196
+ if (cascadeTimer.current !== null) clearTimeout(cascadeTimer.current);
3197
+ if (autoAdvanceTimer.current !== null) {
3198
+ clearTimeout(autoAdvanceTimer.current);
3199
+ }
3200
+ };
3201
+ }, []);
3137
3202
  useScrollLock(true);
3138
3203
  useFocusTrap({
3139
3204
  active: true,
@@ -3162,12 +3227,36 @@ function MultiStepPicker({
3162
3227
  lastSeen.current = { step: stepIndex, units: unitsInStep };
3163
3228
  if (!autoAdvance || isLastStep || prev.step !== stepIndex) return;
3164
3229
  if (unitsInStep <= prev.units) return;
3165
- if (stepHeadroom(step?.maxQuantity ?? null, unitsInStep) === 0) {
3166
- goToStep(stepIndex + 1);
3230
+ if (stepHeadroom(step?.maxQuantity ?? null, unitsInStep) !== 0) return;
3231
+ const from = stepIndex;
3232
+ if (autoAdvanceTimer.current !== null) {
3233
+ clearTimeout(autoAdvanceTimer.current);
3167
3234
  }
3235
+ autoAdvanceTimer.current = setTimeout(() => {
3236
+ autoAdvanceTimer.current = null;
3237
+ goToStep(from + 1);
3238
+ }, AUTO_ADVANCE_HOLD_MS);
3168
3239
  });
3169
3240
  function goToStep(i) {
3170
3241
  const clamped = Math.max(0, Math.min(steps.length - 1, i));
3242
+ if (autoAdvanceTimer.current !== null) {
3243
+ clearTimeout(autoAdvanceTimer.current);
3244
+ autoAdvanceTimer.current = null;
3245
+ }
3246
+ const dialog = dialogRef.current;
3247
+ if (dialog && clamped !== stepIndex) {
3248
+ if (cascadeTimer.current !== null) clearTimeout(cascadeTimer.current);
3249
+ dialog.removeAttribute("data-step-transition");
3250
+ void dialog.offsetHeight;
3251
+ dialog.setAttribute(
3252
+ "data-step-transition",
3253
+ clamped > stepIndex ? "forward" : "back"
3254
+ );
3255
+ cascadeTimer.current = setTimeout(() => {
3256
+ cascadeTimer.current = null;
3257
+ dialog.removeAttribute("data-step-transition");
3258
+ }, STEP_CASCADE_MS);
3259
+ }
3171
3260
  setStepIndex(clamped);
3172
3261
  setQuery("");
3173
3262
  setActiveType(FILTERS_ALL2);
@@ -3752,12 +3841,22 @@ function MultiStepBundle(props) {
3752
3841
  analyticsEnabled,
3753
3842
  onAddToCart,
3754
3843
  onError,
3755
- className
3844
+ className,
3845
+ country,
3846
+ language,
3847
+ buyer,
3848
+ marketId,
3849
+ currencyRate
3756
3850
  } = props;
3757
3851
  const result = useBundleData({
3758
3852
  shopDomain,
3759
3853
  storefrontAccessToken,
3760
- bundleGid
3854
+ bundleGid,
3855
+ country,
3856
+ language,
3857
+ buyer,
3858
+ marketId,
3859
+ currencyRate
3761
3860
  });
3762
3861
  const { elementRef, trackAddToCart } = useAnalytics({
3763
3862
  shopDomain,
@@ -4366,7 +4465,8 @@ function useBundlesForProduct(options) {
4366
4465
  storefrontAccessToken: options.storefrontAccessToken,
4367
4466
  productHandle: options.productHandle,
4368
4467
  buyerIp: options.buyerIp,
4369
- signal: controller.signal
4468
+ signal: controller.signal,
4469
+ currencyRate: options.currencyRate
4370
4470
  }).then((bundles) => {
4371
4471
  if (controller.signal.aborted) return;
4372
4472
  setState({ status: "success", bundles, error: null });
@@ -4394,7 +4494,8 @@ function useBundlesForProduct(options) {
4394
4494
  options.shopDomain,
4395
4495
  options.storefrontAccessToken,
4396
4496
  options.productHandle,
4397
- options.buyerIp
4497
+ options.buyerIp,
4498
+ options.currencyRate
4398
4499
  ]);
4399
4500
  return state;
4400
4501
  }