@lime-bundles/react 7.0.0 → 7.2.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 CHANGED
@@ -43,6 +43,7 @@ Same props for `<VolumeBundle>` and `<MixMatchBundle>`.
43
43
  | `onError` | `(error: Error) => void` | | Fires on fetch, parse, or cart failure. |
44
44
  | `locale` | `string` | | BCP-47 tag forwarded to the Storefront API. |
45
45
  | `className` | `string` | | Applied to the outer wrapper. |
46
+ | `productId` | `string` | | The product page the component is rendered on — full GID or bare numeric id. `<VolumeBundle>` prices and sells this product; without it, it falls back to the bundle's first product, so always pass it for bundles spanning more than one product. |
46
47
 
47
48
  `CartLineInput` matches Shopify Hydrogen's shape. A structural-compat test guards the contract across versions. Always preserve `attributes[]` so `_lime_bundle_gid` reaches Shopify; the `orders/create` webhook keys off it for purchase attribution.
48
49
 
package/dist/index.cjs CHANGED
@@ -1004,7 +1004,9 @@ function FixedProductRow({ bundle, product, currency, isOos, onVariantChange })
1004
1004
  src: thumbImage.url,
1005
1005
  alt: thumbImage.altText ?? product.title,
1006
1006
  className: "lb-bundle__product-image",
1007
- loading: "lazy"
1007
+ loading: "lazy",
1008
+ width: thumbImage.width ?? void 0,
1009
+ height: thumbImage.height ?? void 0
1008
1010
  }
1009
1011
  ),
1010
1012
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "lb-bundle__product-info", children: [
@@ -1610,7 +1612,9 @@ function MixMatchPickerRow({
1610
1612
  {
1611
1613
  src: thumbImage.url,
1612
1614
  alt: thumbImage.altText ?? product.title,
1613
- loading: "lazy"
1615
+ loading: "lazy",
1616
+ width: thumbImage.width ?? void 0,
1617
+ height: thumbImage.height ?? void 0
1614
1618
  }
1615
1619
  ),
1616
1620
  variantInBundle && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "lb-mix-match__modal-added-badge", children: "Added" })
@@ -2336,7 +2340,8 @@ function VolumeBundle(props) {
2336
2340
  analyticsEnabled,
2337
2341
  onAddToCart,
2338
2342
  onError,
2339
- className
2343
+ className,
2344
+ productId
2340
2345
  } = props;
2341
2346
  const result = useBundleData({
2342
2347
  shopDomain,
@@ -2386,7 +2391,7 @@ function VolumeBundle(props) {
2386
2391
  );
2387
2392
  }
2388
2393
  }, [result, onError]);
2389
- const product = bundle?.products[0];
2394
+ const product = bundle ? (0, import_core11.resolveContextProduct)(bundle.products, { productId }) : void 0;
2390
2395
  const variants = (0, import_react13.useMemo)(() => product?.variants.nodes ?? [], [product]);
2391
2396
  const optionNames = (0, import_react13.useMemo)(() => {
2392
2397
  const first = variants[0];
@@ -2519,7 +2524,9 @@ function VolumeBundle(props) {
2519
2524
  src: thumbImage.url,
2520
2525
  alt: thumbImage.altText ?? product.title,
2521
2526
  className: "lb-bundle__product-image",
2522
- loading: "lazy"
2527
+ loading: "lazy",
2528
+ width: thumbImage.width ?? void 0,
2529
+ height: thumbImage.height ?? void 0
2523
2530
  }
2524
2531
  ),
2525
2532
  /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "lb-bundle__product-info", children: [
@@ -2963,7 +2970,9 @@ function BogoProductRow({ side, product, quantity, percent, badge, currency, onV
2963
2970
  src: thumbImage.url,
2964
2971
  alt: thumbImage.altText ?? product.title,
2965
2972
  className: "lb-bundle__product-image",
2966
- loading: "lazy"
2973
+ loading: "lazy",
2974
+ width: thumbImage.width ?? void 0,
2975
+ height: thumbImage.height ?? void 0
2967
2976
  }
2968
2977
  ),
2969
2978
  /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "lb-bundle__product-info", children: [
@@ -3515,7 +3524,9 @@ function MultiStepPickerRow({
3515
3524
  {
3516
3525
  src: thumbImage.url,
3517
3526
  alt: thumbImage.altText ?? product.title,
3518
- loading: "lazy"
3527
+ loading: "lazy",
3528
+ width: thumbImage.width ?? void 0,
3529
+ height: thumbImage.height ?? void 0
3519
3530
  }
3520
3531
  ),
3521
3532
  variantInStep && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "lb-mix-match__modal-added-badge", children: "Added" })
@@ -3809,6 +3820,9 @@ function MultiStepBundle(props) {
3809
3820
  const allSatisfied = steps.length > 0 && steps.every((_, i) => stepSatisfied(i));
3810
3821
  const valid = !!bundle && allSatisfied;
3811
3822
  const completedSteps = steps.filter((_, i) => stepSatisfied(i)).length;
3823
+ const firstUnsatisfied = steps.findIndex((_, i) => !stepSatisfied(i));
3824
+ const remainingSteps = steps.length - completedSteps;
3825
+ const anyPicks = selections.some((s) => s.length > 0);
3812
3826
  const summary = (0, import_react16.useMemo)(() => {
3813
3827
  let comparePrice = 0;
3814
3828
  let salePrice = 0;
@@ -4051,115 +4065,156 @@ function MultiStepBundle(props) {
4051
4065
  )
4052
4066
  ] })
4053
4067
  ] }),
4054
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "lb-multi-step__groups", "data-step-groups": true, children: steps.map((step, i) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
4055
- "section",
4056
- {
4057
- className: "lb-multi-step__group",
4058
- "data-step-group": i,
4059
- children: [
4060
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "lb-multi-step__group-heading", children: [
4061
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "lb-multi-step__group-name", children: step.name }),
4068
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "lb-multi-step__groups", "data-step-groups": true, children: steps.map((step, i) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_react16.Fragment, { children: [
4069
+ i === firstUnsatisfied && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
4070
+ "button",
4071
+ {
4072
+ type: "button",
4073
+ className: "lb-mix-match__add-product lb-multi-step__door",
4074
+ "data-step-door": firstUnsatisfied,
4075
+ "aria-label": `${anyPicks ? "Continue building" : "Choose your products"}: ${step.name}`,
4076
+ onClick: () => setPickerOpenAt(firstUnsatisfied),
4077
+ children: [
4062
4078
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4063
4079
  "span",
4064
4080
  {
4065
- className: `lb-multi-step__group-count${stepSatisfied(i) ? " lb-multi-step__group-count--met" : ""}`,
4066
- "data-step-count": i,
4067
- children: `${Math.min(unitsByStep[i] ?? 0, step.minQuantity)} of ${step.minQuantity} added`
4081
+ className: "lb-mix-match__add-product-icon",
4082
+ "aria-hidden": "true",
4083
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(PlusIcon2, {})
4068
4084
  }
4069
- )
4070
- ] }),
4071
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4072
- "div",
4073
- {
4074
- className: "lb-mix-match__slots lb-edge-fade lb-multi-step__group-slots",
4075
- "data-step-slots": i,
4076
- children: (selections[i] ?? []).map((item, index) => (
4077
- /* Whole card reopens the wizard AT this step. The inner
4078
- title button provides the keyboard/AT path (its
4079
- activation bubbles here); × stops propagation. */
4080
- // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions -- keyboard path is the inner "Edit selection" title button whose activation bubbles to this handler
4081
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
4082
- "div",
4083
- {
4084
- className: "lb-mix-match__slot lb-mix-match__slot--filled",
4085
- onClick: () => setPickerOpenAt(i),
4086
- children: [
4087
- item.featuredImage && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "lb-bundle-thumbnail", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4088
- "img",
4089
- {
4090
- src: item.featuredImage,
4091
- alt: item.title,
4092
- loading: "lazy"
4093
- }
4094
- ) }),
4095
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "lb-mix-match__filled-info", children: [
4096
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4097
- "button",
4098
- {
4099
- type: "button",
4100
- className: "lb-mix-match__filled-title",
4101
- "aria-label": `Edit selection: ${item.title}`,
4102
- children: item.title
4103
- }
4104
- ),
4105
- item.variantTitle && item.variantTitle !== "Default Title" && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "lb-mix-match__filled-variant", children: item.variantTitle }),
4106
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "lb-mix-match__filled-price", children: [
4107
- 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) }),
4108
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "lb-bundle-product-price", children: (0, import_core14.formatMoney)(item.price, currency) }),
4109
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "lb-bundle-qty-inline", children: `\xD7${item.quantity}` })
4110
- ] }),
4111
- item.unitPrice && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "lb-bundle-product-unit-price", children: item.unitPrice })
4112
- ] }),
4113
- canRemoveItem2(bundle, selections, item) ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4114
- "button",
4115
- {
4116
- type: "button",
4117
- className: "lb-mix-match__slot-remove",
4118
- "aria-label": `Remove ${item.title}`,
4119
- onClick: (e) => {
4120
- e.stopPropagation();
4121
- removeSlot(i, index);
4122
- },
4123
- children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(CloseIcon4, {})
4124
- }
4125
- ) : (
4126
- /* Required pick at its floor — quiet state chip instead
4127
- of the ×; removal returns once another slot covers
4128
- the product's minimum (variant swap flow). */
4129
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "lb-mix-match__slot-required", children: "Required" })
4130
- )
4131
- ]
4132
- },
4133
- `${item.productId}:${item.variantId}:${index}`
4134
- )
4135
- ))
4136
- }
4137
- ),
4138
- (0, import_core14.stepHeadroom)(step.maxQuantity, unitsByStep[i] ?? 0) !== 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
4085
+ ),
4086
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "lb-multi-step__door-copy", children: [
4087
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "lb-mix-match__add-product-label", children: anyPicks ? "Continue building" : "Choose your products" }),
4088
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "lb-multi-step__door-sub", children: anyPicks ? `${remainingSteps} ${remainingSteps === 1 ? "step" : "steps"} to go` : `${steps.length} quick ${steps.length === 1 ? "step" : "steps"}` })
4089
+ ] })
4090
+ ]
4091
+ }
4092
+ ),
4093
+ (selections[i] ?? []).length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4094
+ "section",
4095
+ {
4096
+ className: "lb-multi-step__group",
4097
+ "data-step-group": i,
4098
+ "data-step-mode": "row",
4099
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
4139
4100
  "button",
4140
4101
  {
4141
4102
  type: "button",
4142
- className: "lb-mix-match__add-product lb-multi-step__step-trigger",
4143
- "data-step-trigger": i,
4103
+ className: "lb-multi-step__step-row",
4104
+ "data-step-row": i,
4144
4105
  "aria-label": `${step.minQuantity === 1 && step.maxQuantity === 1 ? "Choose a product" : "Choose products"}: ${step.name}`,
4145
4106
  onClick: () => setPickerOpenAt(i),
4146
4107
  children: [
4147
4108
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4148
4109
  "span",
4149
4110
  {
4150
- className: "lb-mix-match__add-product-icon",
4111
+ className: "lb-multi-step__step-ix",
4151
4112
  "aria-hidden": "true",
4152
- children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(PlusIcon2, {})
4113
+ children: i + 1
4153
4114
  }
4154
4115
  ),
4155
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "lb-mix-match__add-product-label", children: step.minQuantity === 1 && step.maxQuantity === 1 ? "Choose a product" : "Choose products" })
4116
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "lb-multi-step__group-name", children: step.name }),
4117
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4118
+ "span",
4119
+ {
4120
+ className: `lb-multi-step__group-count${stepSatisfied(i) ? " lb-multi-step__group-count--met" : ""}`,
4121
+ "data-step-count": i,
4122
+ children: `${Math.min(unitsByStep[i] ?? 0, step.minQuantity)} of ${step.minQuantity} added`
4123
+ }
4124
+ )
4156
4125
  ]
4157
4126
  }
4158
4127
  )
4159
- ]
4160
- },
4161
- i
4162
- )) }),
4128
+ }
4129
+ ) : /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
4130
+ "section",
4131
+ {
4132
+ className: "lb-multi-step__group",
4133
+ "data-step-group": i,
4134
+ "data-step-mode": "open",
4135
+ children: [
4136
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "lb-multi-step__group-heading", children: [
4137
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "lb-multi-step__group-name", children: step.name }),
4138
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4139
+ "span",
4140
+ {
4141
+ className: `lb-multi-step__group-count${stepSatisfied(i) ? " lb-multi-step__group-count--met" : ""}`,
4142
+ "data-step-count": i,
4143
+ children: `${Math.min(unitsByStep[i] ?? 0, step.minQuantity)} of ${step.minQuantity} added`
4144
+ }
4145
+ )
4146
+ ] }),
4147
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4148
+ "div",
4149
+ {
4150
+ className: "lb-mix-match__slots lb-edge-fade lb-multi-step__group-slots",
4151
+ "data-step-slots": i,
4152
+ children: (selections[i] ?? []).map((item, index) => (
4153
+ /* Whole card reopens the wizard AT this step. The inner
4154
+ title button provides the keyboard/AT path (its
4155
+ activation bubbles here); × stops propagation. */
4156
+ // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions -- keyboard path is the inner "Edit selection" title button whose activation bubbles to this handler
4157
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
4158
+ "div",
4159
+ {
4160
+ className: "lb-mix-match__slot lb-mix-match__slot--filled",
4161
+ onClick: () => setPickerOpenAt(i),
4162
+ children: [
4163
+ item.featuredImage && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "lb-bundle-thumbnail", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4164
+ "img",
4165
+ {
4166
+ src: item.featuredImage,
4167
+ alt: item.title,
4168
+ loading: "lazy"
4169
+ }
4170
+ ) }),
4171
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "lb-mix-match__filled-info", children: [
4172
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4173
+ "button",
4174
+ {
4175
+ type: "button",
4176
+ className: "lb-mix-match__filled-title",
4177
+ "aria-label": `Edit selection: ${item.title}`,
4178
+ children: item.title
4179
+ }
4180
+ ),
4181
+ item.variantTitle && item.variantTitle !== "Default Title" && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "lb-mix-match__filled-variant", children: item.variantTitle }),
4182
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "lb-mix-match__filled-price", children: [
4183
+ 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) }),
4184
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "lb-bundle-product-price", children: (0, import_core14.formatMoney)(item.price, currency) }),
4185
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "lb-bundle-qty-inline", children: `\xD7${item.quantity}` })
4186
+ ] }),
4187
+ item.unitPrice && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "lb-bundle-product-unit-price", children: item.unitPrice })
4188
+ ] }),
4189
+ canRemoveItem2(bundle, selections, item) ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4190
+ "button",
4191
+ {
4192
+ type: "button",
4193
+ className: "lb-mix-match__slot-remove",
4194
+ "aria-label": `Remove ${item.title}`,
4195
+ onClick: (e) => {
4196
+ e.stopPropagation();
4197
+ removeSlot(i, index);
4198
+ },
4199
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(CloseIcon4, {})
4200
+ }
4201
+ ) : (
4202
+ /* Required pick at its floor — quiet state chip instead
4203
+ of the ×; removal returns once another slot covers
4204
+ the product's minimum (variant swap flow). */
4205
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "lb-mix-match__slot-required", children: "Required" })
4206
+ )
4207
+ ]
4208
+ },
4209
+ `${item.productId}:${item.variantId}:${index}`
4210
+ )
4211
+ ))
4212
+ }
4213
+ )
4214
+ ]
4215
+ }
4216
+ )
4217
+ ] }, i)) }),
4163
4218
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "lb-bundle-divider" }),
4164
4219
  allSatisfied && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "lb-bundle-summary", "data-pricing-section": true, children: [
4165
4220
  /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "lb-bundle-summary__text", children: [