@lime-bundles/widget 2.2.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -25,6 +25,9 @@ __export(index_exports, {
25
25
  module.exports = __toCommonJS(index_exports);
26
26
 
27
27
  // src/lime-bundle.ts
28
+ var import_core5 = require("@lime-bundles/core");
29
+
30
+ // src/renderers/fixed.ts
28
31
  var import_core4 = require("@lime-bundles/core");
29
32
 
30
33
  // src/renderers/pricing.ts
@@ -123,11 +126,7 @@ var PLACEHOLDER_THUMB_SVG = `
123
126
  </svg>`;
124
127
  function renderFixedBundle(container, bundle, onAddToCart, onCleanup) {
125
128
  const wc = bundle.widgetConfig;
126
- const qtyFor = (productId, variantId) => {
127
- const vq = bundle.variantQuantities[variantId];
128
- if (vq !== void 0) return vq;
129
- return bundle.productQuantities[productId] ?? 1;
130
- };
129
+ const qtyFor = (productId, variantId) => (0, import_core4.resolveBundleQty)(bundle, productId, variantId);
131
130
  const rows = [];
132
131
  let oosCount = 0;
133
132
  bundle.products.forEach((product, idx) => {
@@ -218,9 +217,7 @@ function buildRowState(bundle, product, productIndex) {
218
217
  const eligibleVariants = selectedVariantIds && selectedVariantIds.length > 0 ? available.filter((v) => selectedVariantIds.includes(v.id)) : available;
219
218
  const isOos = eligibleVariants.length === 0;
220
219
  const selected = eligibleVariants[0] ?? null;
221
- const productQty = bundle.productQuantities[product.id] ?? 1;
222
- const variantQty = selected ? bundle.variantQuantities[selected.id] : void 0;
223
- const qty = variantQty ?? productQty;
220
+ const qty = selected ? (0, import_core4.resolveBundleQty)(bundle, product.id, selected.id) : 1;
224
221
  return { product, eligibleVariants, selected, qty, isOos };
225
222
  }
226
223
  function renderHeader(bundle, currency) {
@@ -335,6 +332,11 @@ function renderProductRow(state, currency, qtyFor, onVariantChange) {
335
332
  prices.appendChild(compare);
336
333
  prices.appendChild(priceEl);
337
334
  info.appendChild(prices);
335
+ const unitPriceEl = el("span", "lb-bundle-product-unit-price", {
336
+ "data-product-unit-price": ""
337
+ });
338
+ unitPriceEl.setAttribute("hidden", "");
339
+ info.appendChild(unitPriceEl);
338
340
  const applyVariantToRow = (variant) => {
339
341
  const unit = (0, import_core.parseCents)(variant.price.amount);
340
342
  priceEl.textContent = (0, import_core.formatCents)(unit, currency);
@@ -349,6 +351,17 @@ function renderProductRow(state, currency, qtyFor, onVariantChange) {
349
351
  } else {
350
352
  compare.setAttribute("hidden", "");
351
353
  }
354
+ const unitText = (0, import_core.formatUnitPrice)(
355
+ variant.unitPrice,
356
+ variant.unitPriceMeasurement,
357
+ currency
358
+ );
359
+ if (unitText) {
360
+ unitPriceEl.textContent = unitText;
361
+ unitPriceEl.removeAttribute("hidden");
362
+ } else {
363
+ unitPriceEl.setAttribute("hidden", "");
364
+ }
352
365
  };
353
366
  applyVariantToRow(state.selected);
354
367
  if (state.eligibleVariants.length > 1) {
@@ -959,6 +972,19 @@ function renderModal(bundle, eligible, currency, handlers) {
959
972
  const price = el("span", "lb-mix-match__modal-product-price");
960
973
  price.textContent = (0, import_core.formatCents)((0, import_core.parseCents)(variant.price.amount), currency);
961
974
  info.appendChild(price);
975
+ const unitPriceText = (0, import_core.formatUnitPrice)(
976
+ variant.unitPrice,
977
+ variant.unitPriceMeasurement,
978
+ currency
979
+ );
980
+ if (unitPriceText) {
981
+ const unitPrice = el(
982
+ "span",
983
+ "lb-mix-match__modal-product-unit-price lb-bundle-product-unit-price"
984
+ );
985
+ unitPrice.textContent = unitPriceText;
986
+ info.appendChild(unitPrice);
987
+ }
962
988
  if (ep.isOos) {
963
989
  const soldOut = el("span", "lb-mix-match__modal-sold-out-label");
964
990
  soldOut.textContent = "Sold out";
@@ -1412,7 +1438,7 @@ function clamp(n, min, max) {
1412
1438
  }
1413
1439
 
1414
1440
  // src/lime-bundle.ts
1415
- var import_core5 = require("@lime-bundles/core");
1441
+ var import_core6 = require("@lime-bundles/core");
1416
1442
 
1417
1443
  // src/styles/bundle-css.ts
1418
1444
  var BUNDLE_BASE_CSS = `/* Lime Bundles \u2014 shared base styles for all bundle widget types */
@@ -1424,7 +1450,6 @@ var BUNDLE_BASE_CSS = `/* Lime Bundles \u2014 shared base styles for all bundle
1424
1450
 
1425
1451
  .lb-bundle-widget {
1426
1452
  /* Internal CSS-only vars (not merchant-configurable). */
1427
- --lb-text-muted: #666666;
1428
1453
  --lb-thumbnail-bg: #F0F0F0;
1429
1454
  --lb-widget-pad: 20px;
1430
1455
  --lb-progress-color: var(--lb-primary-color);
@@ -1447,7 +1472,7 @@ var BUNDLE_BASE_CSS = `/* Lime Bundles \u2014 shared base styles for all bundle
1447
1472
  margin: 0 calc(-1 * var(--lb-widget-pad)) 20px;
1448
1473
  padding: 12px 20px;
1449
1474
  background: var(--lb-countdown-bg);
1450
- border-top: 1px solid rgba(0, 0, 0, 0.06);
1475
+ border-top: 1px solid color-mix(in srgb, var(--lb-text) 6%, transparent);
1451
1476
  display: flex;
1452
1477
  align-items: center;
1453
1478
  justify-content: space-between;
@@ -1487,11 +1512,20 @@ var BUNDLE_BASE_CSS = `/* Lime Bundles \u2014 shared base styles for all bundle
1487
1512
  display: none;
1488
1513
  }
1489
1514
 
1490
- /* Adjacent widget spacing \u2014 separates multiple bundles on the same product page */
1491
- .lb-bundle-widget + .lb-bundle-widget {
1515
+ /* Sibling widget spacing \u2014 separates multiple bundles on the same product page.
1516
+ Uses \`~\` (general sibling) rather than \`+\` (adjacent) because each Liquid
1517
+ loop iteration emits a {% style %} block before its widget div, so the
1518
+ rendered DOM alternates <style><widget><style><widget>. The \`+\` combinator
1519
+ requires immediate adjacency and would match nothing; \`~\` matches every
1520
+ widget after the first regardless of elements between. Single-widget pages
1521
+ stay unaffected (no prior \`.lb-bundle-widget\` sibling to match against).
1522
+ Only \`margin-top\` \u2014 do NOT override \`padding-top\` here. The gradient header
1523
+ uses \`margin-top: calc(-1 * var(--lb-widget-pad))\` to reach the widget's
1524
+ inner border edge, assuming padding-top == --lb-widget-pad. Changing
1525
+ padding-top on the subsequent widget breaks that math and leaves a visible
1526
+ gap above the header. */
1527
+ .lb-bundle-widget ~ .lb-bundle-widget {
1492
1528
  margin-top: 24px;
1493
- padding-top: 24px;
1494
- border-top: 1px solid var(--lb-border);
1495
1529
  }
1496
1530
 
1497
1531
  /* Header \u2014 gradient banner with title + savings badge */
@@ -1534,7 +1568,7 @@ var BUNDLE_BASE_CSS = `/* Lime Bundles \u2014 shared base styles for all bundle
1534
1568
  font-size: 16px;
1535
1569
  font-weight: 400;
1536
1570
  line-height: 20px;
1537
- color: var(--lb-text-muted);
1571
+ color: color-mix(in srgb, var(--lb-text) 60%, transparent);
1538
1572
  margin: 4px 0 0;
1539
1573
  }
1540
1574
 
@@ -1603,7 +1637,7 @@ var BUNDLE_BASE_CSS = `/* Lime Bundles \u2014 shared base styles for all bundle
1603
1637
  .lb-bundle-thumbnail svg {
1604
1638
  width: 28px;
1605
1639
  height: 28px;
1606
- color: #BBBBBB;
1640
+ color: color-mix(in srgb, var(--lb-text) 35%, transparent);
1607
1641
  }
1608
1642
 
1609
1643
  .lb-bundle-product-info {
@@ -1658,6 +1692,22 @@ var BUNDLE_BASE_CSS = `/* Lime Bundles \u2014 shared base styles for all bundle
1658
1692
  display: none;
1659
1693
  }
1660
1694
 
1695
+ /* Unit price (e.g. "$0.50/100ml") \u2014 only rendered when the merchant has
1696
+ configured unit pricing on the variant in the Shopify admin. No merchant
1697
+ toggle: present in admin \u2192 shown; absent \u2192 hidden. Styled as muted
1698
+ secondary text beneath the price row so it doesn't compete visually. */
1699
+ .lb-bundle-product-unit-price {
1700
+ display: block;
1701
+ font-size: 11px;
1702
+ line-height: 16px;
1703
+ color: color-mix(in srgb, var(--lb-text) 60%, transparent);
1704
+ margin-top: 2px;
1705
+ }
1706
+
1707
+ .lb-bundle-product-unit-price[hidden] {
1708
+ display: none;
1709
+ }
1710
+
1661
1711
  .lb-bundle-variant-badge {
1662
1712
  display: inline-block;
1663
1713
  border: var(--lb-variant-border-width) solid var(--lb-variant-border-color);
@@ -1665,14 +1715,14 @@ var BUNDLE_BASE_CSS = `/* Lime Bundles \u2014 shared base styles for all bundle
1665
1715
  padding: 8px 12px;
1666
1716
  font-size: 12px;
1667
1717
  line-height: 16px;
1668
- color: var(--lb-text-muted);
1718
+ color: color-mix(in srgb, var(--lb-text) 60%, transparent);
1669
1719
  margin-top: 8px;
1670
1720
  }
1671
1721
 
1672
1722
  .lb-bundle-quantity {
1673
1723
  font-size: 12px;
1674
1724
  line-height: 16px;
1675
- color: var(--lb-text-muted);
1725
+ color: color-mix(in srgb, var(--lb-text) 60%, transparent);
1676
1726
  margin-left: 8px;
1677
1727
  white-space: nowrap;
1678
1728
  }
@@ -1727,7 +1777,7 @@ var BUNDLE_BASE_CSS = `/* Lime Bundles \u2014 shared base styles for all bundle
1727
1777
  font-size: 16px;
1728
1778
  font-weight: 600;
1729
1779
  line-height: 20px;
1730
- padding: 12px 16px;
1780
+ padding: 8px 12px;
1731
1781
  border-radius: var(--lb-savings-bar-radius);
1732
1782
  margin-bottom: 12px;
1733
1783
  }
@@ -1872,7 +1922,7 @@ var BUNDLE_BASE_CSS = `/* Lime Bundles \u2014 shared base styles for all bundle
1872
1922
  .lb-bundle-placeholder-icon {
1873
1923
  width: 28px;
1874
1924
  height: 28px;
1875
- stroke: #BBBBBB;
1925
+ stroke: color-mix(in srgb, var(--lb-text) 35%, transparent);
1876
1926
  stroke-width: 1.5;
1877
1927
  fill: none;
1878
1928
  }
@@ -2289,7 +2339,7 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
2289
2339
  border: var(--lb-picker-product-border-width) solid var(--lb-picker-product-border-color);
2290
2340
  box-sizing: border-box;
2291
2341
  overflow: visible;
2292
- background: #F0F0F0;
2342
+ background: var(--lb-thumbnail-bg);
2293
2343
  }
2294
2344
 
2295
2345
  /* Qty badge inside the picker modal inherits picker-product border (width + color) plus
@@ -2330,6 +2380,17 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
2330
2380
  margin: 4px 0 0;
2331
2381
  }
2332
2382
 
2383
+ .lb-mix-match__modal-product-unit-price {
2384
+ font-size: 11px;
2385
+ line-height: 16px;
2386
+ color: color-mix(in srgb, var(--lb-text) 60%, transparent);
2387
+ margin: 2px 0 0;
2388
+ }
2389
+
2390
+ .lb-mix-match__modal-product-unit-price[hidden] {
2391
+ display: none;
2392
+ }
2393
+
2333
2394
  .lb-mix-match__variant-select {
2334
2395
  font-size: 12px;
2335
2396
  margin: 4px 0 0;
@@ -2748,7 +2809,7 @@ var LimeBundleElement = class extends HTMLElement {
2748
2809
  const controller = new AbortController();
2749
2810
  this.abortController = controller;
2750
2811
  this.renderLoading();
2751
- const client = (0, import_core4.createStorefrontClient)({
2812
+ const client = (0, import_core5.createStorefrontClient)({
2752
2813
  shopDomain: this.shopDomain,
2753
2814
  accessToken: this.storefrontToken
2754
2815
  });
@@ -2773,7 +2834,7 @@ var LimeBundleElement = class extends HTMLElement {
2773
2834
  handle
2774
2835
  );
2775
2836
  }
2776
- const cssPromise = client.query(import_core4.SHOP_CUSTOM_CSS_QUERY, void 0, {
2837
+ const cssPromise = client.query(import_core5.SHOP_CUSTOM_CSS_QUERY, void 0, {
2777
2838
  signal: controller.signal
2778
2839
  }).catch(() => null);
2779
2840
  await bundlePromise;
@@ -2785,8 +2846,8 @@ var LimeBundleElement = class extends HTMLElement {
2785
2846
  }
2786
2847
  const css = await cssPromise;
2787
2848
  if (css?.shop?.metafield?.value) {
2788
- (0, import_core4.injectCustomCss)(this.shopDomain, css.shop.metafield.value);
2789
- const sanitized = (0, import_core4.sanitizeCustomCss)(css.shop.metafield.value);
2849
+ (0, import_core5.injectCustomCss)(this.shopDomain, css.shop.metafield.value);
2850
+ const sanitized = (0, import_core5.sanitizeCustomCss)(css.shop.metafield.value);
2790
2851
  if (sanitized.ok) this.shopCustomCss = sanitized.css;
2791
2852
  }
2792
2853
  await this.applyABVariants();
@@ -2814,14 +2875,14 @@ var LimeBundleElement = class extends HTMLElement {
2814
2875
  this.bundles.map(async (bundle) => {
2815
2876
  if (!bundle.abTestId || !bundle.abVariantB) return bundle;
2816
2877
  try {
2817
- const assignment = await (0, import_core4.getABTestAssignment)(
2878
+ const assignment = await (0, import_core5.getABTestAssignment)(
2818
2879
  this.appUrl,
2819
2880
  this.shopDomain,
2820
2881
  bundle.abTestId,
2821
2882
  bundle.id
2822
2883
  );
2823
2884
  if (assignment?.variant === "B") {
2824
- return (0, import_core4.applyABVariantB)(bundle);
2885
+ return (0, import_core5.applyABVariantB)(bundle);
2825
2886
  }
2826
2887
  } catch (err) {
2827
2888
  console.warn(
@@ -2836,7 +2897,7 @@ var LimeBundleElement = class extends HTMLElement {
2836
2897
  }
2837
2898
  async fetchSingleBundle(client, signal) {
2838
2899
  const data = await client.query(
2839
- import_core4.BUNDLE_METAOBJECT_QUERY,
2900
+ import_core5.BUNDLE_METAOBJECT_QUERY,
2840
2901
  { id: this.bundleGid },
2841
2902
  { signal }
2842
2903
  );
@@ -2844,7 +2905,7 @@ var LimeBundleElement = class extends HTMLElement {
2844
2905
  this.bundles = [];
2845
2906
  return;
2846
2907
  }
2847
- const parsed = (0, import_core4.parseMetaobjectBundle)(
2908
+ const parsed = (0, import_core5.parseMetaobjectBundle)(
2848
2909
  data.metaobject.id,
2849
2910
  data.metaobject.fields
2850
2911
  );
@@ -2852,7 +2913,7 @@ var LimeBundleElement = class extends HTMLElement {
2852
2913
  }
2853
2914
  async fetchProductBundles(client, signal, productHandle) {
2854
2915
  const data = await client.query(
2855
- import_core4.BUNDLES_FOR_PRODUCT_QUERY,
2916
+ import_core5.BUNDLES_FOR_PRODUCT_QUERY,
2856
2917
  { handle: productHandle },
2857
2918
  { signal }
2858
2919
  );
@@ -2863,7 +2924,7 @@ var LimeBundleElement = class extends HTMLElement {
2863
2924
  const refs = data.product.metafield?.references?.nodes ?? [];
2864
2925
  const bundles = [];
2865
2926
  for (const ref of refs) {
2866
- const parsed = (0, import_core4.parseMetaobjectBundle)(ref.id, ref.fields);
2927
+ const parsed = (0, import_core5.parseMetaobjectBundle)(ref.id, ref.fields);
2867
2928
  if (parsed) bundles.push(parsed);
2868
2929
  }
2869
2930
  this.bundles = bundles;
@@ -2896,7 +2957,7 @@ var LimeBundleElement = class extends HTMLElement {
2896
2957
  */
2897
2958
  async defaultAddToCart(lines) {
2898
2959
  if (typeof window === "undefined") return;
2899
- const client = (0, import_core4.createStorefrontClient)({
2960
+ const client = (0, import_core5.createStorefrontClient)({
2900
2961
  shopDomain: this.shopDomain,
2901
2962
  accessToken: this.storefrontToken
2902
2963
  });
@@ -2907,7 +2968,7 @@ var LimeBundleElement = class extends HTMLElement {
2907
2968
  let checkoutUrl = null;
2908
2969
  if (existingCartId) {
2909
2970
  const res = await client.query(
2910
- import_core4.CART_LINES_ADD_MUTATION,
2971
+ import_core5.CART_LINES_ADD_MUTATION,
2911
2972
  { cartId: existingCartId, lines }
2912
2973
  );
2913
2974
  const payload = res.cartLinesAdd;
@@ -2919,7 +2980,7 @@ var LimeBundleElement = class extends HTMLElement {
2919
2980
  }
2920
2981
  if (!checkoutUrl) {
2921
2982
  const res = await client.query(
2922
- import_core4.CART_CREATE_MUTATION,
2983
+ import_core5.CART_CREATE_MUTATION,
2923
2984
  { input: { lines } }
2924
2985
  );
2925
2986
  const payload = res.cartCreate;
@@ -2965,7 +3026,7 @@ var LimeBundleElement = class extends HTMLElement {
2965
3026
  const price = variant ? parseFloat(variant.price.amount) : 0;
2966
3027
  return sum + price * line.quantity;
2967
3028
  }, 0);
2968
- (0, import_core4.reportAddToCart)(
3029
+ (0, import_core5.reportAddToCart)(
2969
3030
  { shopDomain: this.shopDomain, appUrl: this.appUrl },
2970
3031
  {
2971
3032
  bundleGid: bundle.id,
@@ -3001,7 +3062,7 @@ var LimeBundleElement = class extends HTMLElement {
3001
3062
  container.setAttribute("aria-label", bundle.title);
3002
3063
  container.setAttribute("data-bundle-type", bundle.bundleType);
3003
3064
  container.setAttribute("data-bundle-gid", bundle.id);
3004
- (0, import_core5.applyWidgetConfigVars)(container, bundle.widgetConfig);
3065
+ (0, import_core6.applyWidgetConfigVars)(container, bundle.widgetConfig);
3005
3066
  const dispatch = (lines) => this.handleAddToCart(bundle, lines);
3006
3067
  const registerCleanup = (fn) => this.renderCleanups.push(fn);
3007
3068
  switch (bundle.bundleType) {
@@ -3036,8 +3097,8 @@ var LimeBundleElement = class extends HTMLElement {
3036
3097
  }
3037
3098
  setupImpressionFor(bundle, element) {
3038
3099
  if (!this.analyticsEnabled || !this.appUrl) return;
3039
- const cleanup = (0, import_core4.observeImpression)(element, () => {
3040
- (0, import_core4.reportImpression)(
3100
+ const cleanup = (0, import_core5.observeImpression)(element, () => {
3101
+ (0, import_core5.reportImpression)(
3041
3102
  { shopDomain: this.shopDomain, appUrl: this.appUrl },
3042
3103
  {
3043
3104
  bundleGid: bundle.id,