@lime-bundles/widget 4.6.5 → 4.6.6

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
@@ -381,6 +381,55 @@ function buildOptionGroups(container, product, selected) {
381
381
  }
382
382
  }
383
383
 
384
+ // ../render/src/product/placeholder.ts
385
+ var SVG_NS = "http://www.w3.org/2000/svg";
386
+ var PLACEHOLDER_ATTR = "data-lb-placeholder";
387
+ var PLACEHOLDER_CLASS = "lb-thumb--placeholder";
388
+ function builtInMark() {
389
+ const svg = document.createElementNS(SVG_NS, "svg");
390
+ svg.setAttribute("class", "lb-bundle-placeholder-icon");
391
+ svg.setAttribute("viewBox", "0 0 48 48");
392
+ const rect = document.createElementNS(SVG_NS, "rect");
393
+ rect.setAttribute("x", "15");
394
+ rect.setAttribute("y", "15");
395
+ rect.setAttribute("width", "18");
396
+ rect.setAttribute("height", "18");
397
+ rect.setAttribute("rx", "2");
398
+ svg.appendChild(rect);
399
+ const dot = document.createElementNS(SVG_NS, "circle");
400
+ dot.setAttribute("cx", "20.5");
401
+ dot.setAttribute("cy", "20.5");
402
+ dot.setAttribute("r", "1.5");
403
+ svg.appendChild(dot);
404
+ const ridge = document.createElementNS(SVG_NS, "path");
405
+ ridge.setAttribute("d", "M33 27l-5-5L17 33");
406
+ svg.appendChild(ridge);
407
+ return svg;
408
+ }
409
+ var make = builtInMark;
410
+ function mark() {
411
+ const node = make();
412
+ const el2 = node.nodeType === 1 ? node : null;
413
+ if (el2) {
414
+ el2.setAttribute(PLACEHOLDER_ATTR, "");
415
+ el2.setAttribute("aria-hidden", "true");
416
+ el2.setAttribute("focusable", "false");
417
+ }
418
+ return node;
419
+ }
420
+ function showThumbnailPlaceholder(box2) {
421
+ const img = box2.querySelector("img");
422
+ if (img) img.remove();
423
+ box2.classList.add(PLACEHOLDER_CLASS);
424
+ if (box2.querySelector("[" + PLACEHOLDER_ATTR + "]")) return;
425
+ box2.appendChild(mark());
426
+ }
427
+ function hideThumbnailPlaceholder(box2) {
428
+ const existing = box2.querySelector("[" + PLACEHOLDER_ATTR + "]");
429
+ if (existing) existing.remove();
430
+ box2.classList.remove(PLACEHOLDER_CLASS);
431
+ }
432
+
384
433
  // ../render/src/product/row.ts
385
434
  var productImgSrc = /* @__PURE__ */ new WeakMap();
386
435
  function updateThumbnail(row, variant, productImage) {
@@ -391,8 +440,12 @@ function updateThumbnail(row, variant, productImage) {
391
440
  const seed = productImage || (img ? img.src : null);
392
441
  if (seed) productImgSrc.set(row, seed);
393
442
  }
394
- const nextSrc = variant.image || productImgSrc.get(row) || null;
395
- if (!nextSrc) return;
443
+ const nextSrc = variant && variant.image || productImgSrc.get(row) || null;
444
+ if (!nextSrc) {
445
+ showThumbnailPlaceholder(thumb);
446
+ return;
447
+ }
448
+ hideThumbnailPlaceholder(thumb);
396
449
  if (img) {
397
450
  img.src = nextSrc;
398
451
  img.srcset = "";
@@ -411,7 +464,10 @@ function updateInlineQuantity(row, qty) {
411
464
  el2.textContent = "\xD7" + qty;
412
465
  }
413
466
  function applyVariant(row, variant, product, formatMoney) {
414
- if (!variant) return;
467
+ if (!variant) {
468
+ updateThumbnail(row, null, product.featuredImage);
469
+ return;
470
+ }
415
471
  updateThumbnail(row, variant, product.featuredImage);
416
472
  updateInlineQuantity(row, variant.quantity || product.quantity || 1);
417
473
  const priceEl = row.querySelector("[data-product-price]");
@@ -461,8 +517,11 @@ function buildRowSkeleton(product, t, opts) {
461
517
  if (opts.bogoRole) row.setAttribute("data-bogo-role", opts.bogoRole);
462
518
  const thumb = box("div", "lb-bundle-thumbnail", { "data-thumbnail": "" });
463
519
  const ratio = product.featuredImageRatio;
464
- if (opts.thumbnailRatio === "original" && ratio != null && ratio > 0) {
465
- thumb.style.setProperty("--lb-row-thumb-ratio", String(ratio));
520
+ if (opts.thumbnailRatio === "original") {
521
+ thumb.style.setProperty(
522
+ "--lb-row-thumb-ratio",
523
+ String(ratio != null && ratio > 0 ? ratio : 1)
524
+ );
466
525
  }
467
526
  row.appendChild(thumb);
468
527
  const info = box("div", "lb-bundle-product-info");
@@ -1427,17 +1486,25 @@ function buildPickerRow(p, rowIndex, ctx) {
1427
1486
  addedBadge.textContent = t.addedItem || "Added";
1428
1487
  addedBadge.hidden = true;
1429
1488
  thumbDiv.appendChild(addedBadge);
1489
+ const paintThumb = (src) => {
1490
+ if (!src) {
1491
+ showThumbnailPlaceholder(thumbDiv);
1492
+ return;
1493
+ }
1494
+ hideThumbnailPlaceholder(thumbDiv);
1495
+ let img = thumbDiv.querySelector("img");
1496
+ if (!img) {
1497
+ img = document.createElement("img");
1498
+ img.sizes = PICKER_SIZES;
1499
+ img.alt = p.title;
1500
+ img.loading = "lazy";
1501
+ thumbDiv.appendChild(img);
1502
+ }
1503
+ img.src = imgWidth(src, PICKER_BASE_WIDTH);
1504
+ img.srcset = srcset(src, PICKER_WIDTHS);
1505
+ };
1430
1506
  const firstAvailable = p.variants.find((v) => v.available) ?? null;
1431
- const initialThumbSrc = firstAvailable && firstAvailable.image || p.featuredImage;
1432
- if (initialThumbSrc) {
1433
- const img = document.createElement("img");
1434
- img.src = imgWidth(initialThumbSrc, PICKER_BASE_WIDTH);
1435
- img.srcset = srcset(initialThumbSrc, PICKER_WIDTHS);
1436
- img.sizes = PICKER_SIZES;
1437
- img.alt = p.title;
1438
- img.loading = "lazy";
1439
- thumbDiv.appendChild(img);
1440
- }
1507
+ paintThumb(firstAvailable && firstAvailable.image || p.featuredImage);
1441
1508
  row.appendChild(thumbDiv);
1442
1509
  const infoDiv = document.createElement("div");
1443
1510
  infoDiv.className = "lb-mix-match__modal-product-info";
@@ -1648,12 +1715,7 @@ function buildPickerRow(p, rowIndex, ctx) {
1648
1715
  unitPriceEl.textContent = "";
1649
1716
  unitPriceEl.hidden = true;
1650
1717
  }
1651
- const rowImg = thumbDiv.querySelector("img");
1652
- const imgSrc = v.image || p.featuredImage;
1653
- if (rowImg && imgSrc) {
1654
- rowImg.src = imgWidth(imgSrc, PICKER_BASE_WIDTH);
1655
- rowImg.srcset = srcset(imgSrc, PICKER_WIDTHS);
1656
- }
1718
+ paintThumb(v.image || p.featuredImage);
1657
1719
  setBoundsForVariant?.(v, capacity.variantUnitsElsewhere(v.id));
1658
1720
  if (v.options) recomputeDisabled(v.options);
1659
1721
  refresh();
@@ -2231,6 +2293,8 @@ function buildSlotCard(item, trans, formatMoney, removable, onRemove, onEdit) {
2231
2293
  img.alt = item.title;
2232
2294
  img.loading = "lazy";
2233
2295
  thumb.appendChild(img);
2296
+ } else {
2297
+ showThumbnailPlaceholder(thumb);
2234
2298
  }
2235
2299
  card.appendChild(thumb);
2236
2300
  const info = document.createElement("div");
@@ -4536,6 +4600,15 @@ var BUNDLE_BASE_CSS = `/* Lime Bundles \u2014 shared base styles for all bundle
4536
4600
  display: block;
4537
4601
  }
4538
4602
 
4603
+ /* Same reset, one more victim. A thumbnail is emitted empty and filled at
4604
+ hydration, so between the two it matched \`div:empty\` and vanished \u2014 taking
4605
+ the row's gap with it and starting that title 72px left of every other
4606
+ row's (issue #552). The box holds its place either way now: the product's
4607
+ image lands in it, or the placeholder mark does. */
4608
+ .lb-bundle-thumbnail:empty {
4609
+ display: flex;
4610
+ }
4611
+
4539
4612
  /* Divider */
4540
4613
  .lb-bundle-divider {
4541
4614
  height: var(--lb-border-width);
@@ -4574,11 +4647,6 @@ var BUNDLE_BASE_CSS = `/* Lime Bundles \u2014 shared base styles for all bundle
4574
4647
  object-fit: var(--lb-thumbnail-img-fit, cover);
4575
4648
  }
4576
4649
 
4577
- .lb-bundle-thumbnail svg {
4578
- width: 28px;
4579
- height: 28px;
4580
- color: color-mix(in srgb, var(--lb-text) 35%, transparent);
4581
- }
4582
4650
 
4583
4651
  .lb-bundle-product-info {
4584
4652
  flex: 1;
@@ -5020,15 +5088,33 @@ a.lb-bundle-product-name:focus-visible {
5020
5088
  border: 0 !important;
5021
5089
  }
5022
5090
 
5023
- /* Placeholder SVG icon for missing images */
5091
+ /* Missing-image placeholder.
5092
+ Two marks share these rules: Shopify's own \`placeholder_svg_tag\`
5093
+ illustration, which the theme scripts clone out of the <template> in
5094
+ bundle-widget.liquid, and the line glyph the headless renderer falls back to
5095
+ when there is no Liquid to supply one. */
5096
+ .lb-thumb--placeholder > [data-lb-placeholder] {
5097
+ display: block;
5098
+ width: 100%;
5099
+ /* The mark carries the box's height, not the other way round: the
5100
+ "original" thumbnail ratio sizes a box to its image's proportions and an
5101
+ imageless row has no image, so without this the box would be 60px by
5102
+ nothing. A box that already has a ratio caps the mark instead. */
5103
+ height: auto;
5104
+ max-height: 100%;
5105
+ aspect-ratio: 1 / 1;
5106
+ }
5107
+
5024
5108
  .lb-bundle-placeholder-icon {
5025
- width: 28px;
5026
- height: 28px;
5027
5109
  stroke: color-mix(in srgb, var(--lb-text) 35%, transparent);
5028
5110
  stroke-width: 1.5;
5029
5111
  fill: none;
5030
5112
  }
5031
5113
 
5114
+ .lb-bundle-placeholder-svg {
5115
+ fill: color-mix(in srgb, var(--lb-text) 22%, transparent);
5116
+ }
5117
+
5032
5118
  /* Out-of-stock product row */
5033
5119
  .lb-bundle-product-row--oos {
5034
5120
  opacity: 0.5;
package/dist/index.js CHANGED
@@ -380,6 +380,55 @@ function buildOptionGroups(container, product, selected) {
380
380
  }
381
381
  }
382
382
 
383
+ // ../render/src/product/placeholder.ts
384
+ var SVG_NS = "http://www.w3.org/2000/svg";
385
+ var PLACEHOLDER_ATTR = "data-lb-placeholder";
386
+ var PLACEHOLDER_CLASS = "lb-thumb--placeholder";
387
+ function builtInMark() {
388
+ const svg = document.createElementNS(SVG_NS, "svg");
389
+ svg.setAttribute("class", "lb-bundle-placeholder-icon");
390
+ svg.setAttribute("viewBox", "0 0 48 48");
391
+ const rect = document.createElementNS(SVG_NS, "rect");
392
+ rect.setAttribute("x", "15");
393
+ rect.setAttribute("y", "15");
394
+ rect.setAttribute("width", "18");
395
+ rect.setAttribute("height", "18");
396
+ rect.setAttribute("rx", "2");
397
+ svg.appendChild(rect);
398
+ const dot = document.createElementNS(SVG_NS, "circle");
399
+ dot.setAttribute("cx", "20.5");
400
+ dot.setAttribute("cy", "20.5");
401
+ dot.setAttribute("r", "1.5");
402
+ svg.appendChild(dot);
403
+ const ridge = document.createElementNS(SVG_NS, "path");
404
+ ridge.setAttribute("d", "M33 27l-5-5L17 33");
405
+ svg.appendChild(ridge);
406
+ return svg;
407
+ }
408
+ var make = builtInMark;
409
+ function mark() {
410
+ const node = make();
411
+ const el2 = node.nodeType === 1 ? node : null;
412
+ if (el2) {
413
+ el2.setAttribute(PLACEHOLDER_ATTR, "");
414
+ el2.setAttribute("aria-hidden", "true");
415
+ el2.setAttribute("focusable", "false");
416
+ }
417
+ return node;
418
+ }
419
+ function showThumbnailPlaceholder(box2) {
420
+ const img = box2.querySelector("img");
421
+ if (img) img.remove();
422
+ box2.classList.add(PLACEHOLDER_CLASS);
423
+ if (box2.querySelector("[" + PLACEHOLDER_ATTR + "]")) return;
424
+ box2.appendChild(mark());
425
+ }
426
+ function hideThumbnailPlaceholder(box2) {
427
+ const existing = box2.querySelector("[" + PLACEHOLDER_ATTR + "]");
428
+ if (existing) existing.remove();
429
+ box2.classList.remove(PLACEHOLDER_CLASS);
430
+ }
431
+
383
432
  // ../render/src/product/row.ts
384
433
  var productImgSrc = /* @__PURE__ */ new WeakMap();
385
434
  function updateThumbnail(row, variant, productImage) {
@@ -390,8 +439,12 @@ function updateThumbnail(row, variant, productImage) {
390
439
  const seed = productImage || (img ? img.src : null);
391
440
  if (seed) productImgSrc.set(row, seed);
392
441
  }
393
- const nextSrc = variant.image || productImgSrc.get(row) || null;
394
- if (!nextSrc) return;
442
+ const nextSrc = variant && variant.image || productImgSrc.get(row) || null;
443
+ if (!nextSrc) {
444
+ showThumbnailPlaceholder(thumb);
445
+ return;
446
+ }
447
+ hideThumbnailPlaceholder(thumb);
395
448
  if (img) {
396
449
  img.src = nextSrc;
397
450
  img.srcset = "";
@@ -410,7 +463,10 @@ function updateInlineQuantity(row, qty) {
410
463
  el2.textContent = "\xD7" + qty;
411
464
  }
412
465
  function applyVariant(row, variant, product, formatMoney) {
413
- if (!variant) return;
466
+ if (!variant) {
467
+ updateThumbnail(row, null, product.featuredImage);
468
+ return;
469
+ }
414
470
  updateThumbnail(row, variant, product.featuredImage);
415
471
  updateInlineQuantity(row, variant.quantity || product.quantity || 1);
416
472
  const priceEl = row.querySelector("[data-product-price]");
@@ -460,8 +516,11 @@ function buildRowSkeleton(product, t, opts) {
460
516
  if (opts.bogoRole) row.setAttribute("data-bogo-role", opts.bogoRole);
461
517
  const thumb = box("div", "lb-bundle-thumbnail", { "data-thumbnail": "" });
462
518
  const ratio = product.featuredImageRatio;
463
- if (opts.thumbnailRatio === "original" && ratio != null && ratio > 0) {
464
- thumb.style.setProperty("--lb-row-thumb-ratio", String(ratio));
519
+ if (opts.thumbnailRatio === "original") {
520
+ thumb.style.setProperty(
521
+ "--lb-row-thumb-ratio",
522
+ String(ratio != null && ratio > 0 ? ratio : 1)
523
+ );
465
524
  }
466
525
  row.appendChild(thumb);
467
526
  const info = box("div", "lb-bundle-product-info");
@@ -1426,17 +1485,25 @@ function buildPickerRow(p, rowIndex, ctx) {
1426
1485
  addedBadge.textContent = t.addedItem || "Added";
1427
1486
  addedBadge.hidden = true;
1428
1487
  thumbDiv.appendChild(addedBadge);
1488
+ const paintThumb = (src) => {
1489
+ if (!src) {
1490
+ showThumbnailPlaceholder(thumbDiv);
1491
+ return;
1492
+ }
1493
+ hideThumbnailPlaceholder(thumbDiv);
1494
+ let img = thumbDiv.querySelector("img");
1495
+ if (!img) {
1496
+ img = document.createElement("img");
1497
+ img.sizes = PICKER_SIZES;
1498
+ img.alt = p.title;
1499
+ img.loading = "lazy";
1500
+ thumbDiv.appendChild(img);
1501
+ }
1502
+ img.src = imgWidth(src, PICKER_BASE_WIDTH);
1503
+ img.srcset = srcset(src, PICKER_WIDTHS);
1504
+ };
1429
1505
  const firstAvailable = p.variants.find((v) => v.available) ?? null;
1430
- const initialThumbSrc = firstAvailable && firstAvailable.image || p.featuredImage;
1431
- if (initialThumbSrc) {
1432
- const img = document.createElement("img");
1433
- img.src = imgWidth(initialThumbSrc, PICKER_BASE_WIDTH);
1434
- img.srcset = srcset(initialThumbSrc, PICKER_WIDTHS);
1435
- img.sizes = PICKER_SIZES;
1436
- img.alt = p.title;
1437
- img.loading = "lazy";
1438
- thumbDiv.appendChild(img);
1439
- }
1506
+ paintThumb(firstAvailable && firstAvailable.image || p.featuredImage);
1440
1507
  row.appendChild(thumbDiv);
1441
1508
  const infoDiv = document.createElement("div");
1442
1509
  infoDiv.className = "lb-mix-match__modal-product-info";
@@ -1647,12 +1714,7 @@ function buildPickerRow(p, rowIndex, ctx) {
1647
1714
  unitPriceEl.textContent = "";
1648
1715
  unitPriceEl.hidden = true;
1649
1716
  }
1650
- const rowImg = thumbDiv.querySelector("img");
1651
- const imgSrc = v.image || p.featuredImage;
1652
- if (rowImg && imgSrc) {
1653
- rowImg.src = imgWidth(imgSrc, PICKER_BASE_WIDTH);
1654
- rowImg.srcset = srcset(imgSrc, PICKER_WIDTHS);
1655
- }
1717
+ paintThumb(v.image || p.featuredImage);
1656
1718
  setBoundsForVariant?.(v, capacity.variantUnitsElsewhere(v.id));
1657
1719
  if (v.options) recomputeDisabled(v.options);
1658
1720
  refresh();
@@ -2230,6 +2292,8 @@ function buildSlotCard(item, trans, formatMoney, removable, onRemove, onEdit) {
2230
2292
  img.alt = item.title;
2231
2293
  img.loading = "lazy";
2232
2294
  thumb.appendChild(img);
2295
+ } else {
2296
+ showThumbnailPlaceholder(thumb);
2233
2297
  }
2234
2298
  card.appendChild(thumb);
2235
2299
  const info = document.createElement("div");
@@ -4546,6 +4610,15 @@ var BUNDLE_BASE_CSS = `/* Lime Bundles \u2014 shared base styles for all bundle
4546
4610
  display: block;
4547
4611
  }
4548
4612
 
4613
+ /* Same reset, one more victim. A thumbnail is emitted empty and filled at
4614
+ hydration, so between the two it matched \`div:empty\` and vanished \u2014 taking
4615
+ the row's gap with it and starting that title 72px left of every other
4616
+ row's (issue #552). The box holds its place either way now: the product's
4617
+ image lands in it, or the placeholder mark does. */
4618
+ .lb-bundle-thumbnail:empty {
4619
+ display: flex;
4620
+ }
4621
+
4549
4622
  /* Divider */
4550
4623
  .lb-bundle-divider {
4551
4624
  height: var(--lb-border-width);
@@ -4584,11 +4657,6 @@ var BUNDLE_BASE_CSS = `/* Lime Bundles \u2014 shared base styles for all bundle
4584
4657
  object-fit: var(--lb-thumbnail-img-fit, cover);
4585
4658
  }
4586
4659
 
4587
- .lb-bundle-thumbnail svg {
4588
- width: 28px;
4589
- height: 28px;
4590
- color: color-mix(in srgb, var(--lb-text) 35%, transparent);
4591
- }
4592
4660
 
4593
4661
  .lb-bundle-product-info {
4594
4662
  flex: 1;
@@ -5030,15 +5098,33 @@ a.lb-bundle-product-name:focus-visible {
5030
5098
  border: 0 !important;
5031
5099
  }
5032
5100
 
5033
- /* Placeholder SVG icon for missing images */
5101
+ /* Missing-image placeholder.
5102
+ Two marks share these rules: Shopify's own \`placeholder_svg_tag\`
5103
+ illustration, which the theme scripts clone out of the <template> in
5104
+ bundle-widget.liquid, and the line glyph the headless renderer falls back to
5105
+ when there is no Liquid to supply one. */
5106
+ .lb-thumb--placeholder > [data-lb-placeholder] {
5107
+ display: block;
5108
+ width: 100%;
5109
+ /* The mark carries the box's height, not the other way round: the
5110
+ "original" thumbnail ratio sizes a box to its image's proportions and an
5111
+ imageless row has no image, so without this the box would be 60px by
5112
+ nothing. A box that already has a ratio caps the mark instead. */
5113
+ height: auto;
5114
+ max-height: 100%;
5115
+ aspect-ratio: 1 / 1;
5116
+ }
5117
+
5034
5118
  .lb-bundle-placeholder-icon {
5035
- width: 28px;
5036
- height: 28px;
5037
5119
  stroke: color-mix(in srgb, var(--lb-text) 35%, transparent);
5038
5120
  stroke-width: 1.5;
5039
5121
  fill: none;
5040
5122
  }
5041
5123
 
5124
+ .lb-bundle-placeholder-svg {
5125
+ fill: color-mix(in srgb, var(--lb-text) 22%, transparent);
5126
+ }
5127
+
5042
5128
  /* Out-of-stock product row */
5043
5129
  .lb-bundle-product-row--oos {
5044
5130
  opacity: 0.5;