@lime-bundles/widget 2.1.0 → 2.2.1
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 +118 -51
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +102 -33
- package/dist/index.js.map +1 -1
- package/dist/lime-bundle.global.js +82 -27
- package/dist/lime-bundle.global.js.map +1 -1
- package/docs/web-component.md +19 -0
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -16,6 +16,11 @@ import {
|
|
|
16
16
|
applyABVariantB
|
|
17
17
|
} from "@lime-bundles/core";
|
|
18
18
|
|
|
19
|
+
// src/renderers/fixed.ts
|
|
20
|
+
import {
|
|
21
|
+
resolveBundleQty
|
|
22
|
+
} from "@lime-bundles/core";
|
|
23
|
+
|
|
19
24
|
// src/renderers/pricing.ts
|
|
20
25
|
import {
|
|
21
26
|
parseCents,
|
|
@@ -70,6 +75,32 @@ function parseIso(iso) {
|
|
|
70
75
|
return Number.isFinite(t) ? t : null;
|
|
71
76
|
}
|
|
72
77
|
|
|
78
|
+
// src/renderers/cta-button.ts
|
|
79
|
+
function buildCtaButton(label) {
|
|
80
|
+
const button = document.createElement("button");
|
|
81
|
+
button.type = "button";
|
|
82
|
+
button.className = "lb-bundle-cta";
|
|
83
|
+
button.setAttribute("data-add-bundle", "");
|
|
84
|
+
const labelSpan = document.createElement("span");
|
|
85
|
+
labelSpan.className = "lb-cta-label";
|
|
86
|
+
labelSpan.setAttribute("data-cta-label", "");
|
|
87
|
+
labelSpan.textContent = label;
|
|
88
|
+
button.appendChild(labelSpan);
|
|
89
|
+
const spinnerSpan = document.createElement("span");
|
|
90
|
+
spinnerSpan.className = "lb-cta-spinner";
|
|
91
|
+
spinnerSpan.setAttribute("data-cta-spinner", "");
|
|
92
|
+
spinnerSpan.setAttribute("aria-hidden", "true");
|
|
93
|
+
spinnerSpan.innerHTML = '<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><path d="M12 2a10 10 0 0 1 10 10" /></svg>';
|
|
94
|
+
button.appendChild(spinnerSpan);
|
|
95
|
+
return button;
|
|
96
|
+
}
|
|
97
|
+
function setCtaLabel(button, text) {
|
|
98
|
+
const label = button.querySelector("[data-cta-label]");
|
|
99
|
+
if (label) {
|
|
100
|
+
label.textContent = text;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
73
104
|
// src/renderers/dom.ts
|
|
74
105
|
function el(tag, className, attrs = {}) {
|
|
75
106
|
const node = document.createElement(tag);
|
|
@@ -95,11 +126,7 @@ var PLACEHOLDER_THUMB_SVG = `
|
|
|
95
126
|
</svg>`;
|
|
96
127
|
function renderFixedBundle(container, bundle, onAddToCart, onCleanup) {
|
|
97
128
|
const wc = bundle.widgetConfig;
|
|
98
|
-
const qtyFor = (productId, variantId) =>
|
|
99
|
-
const vq = bundle.variantQuantities[variantId];
|
|
100
|
-
if (vq !== void 0) return vq;
|
|
101
|
-
return bundle.productQuantities[productId] ?? 1;
|
|
102
|
-
};
|
|
129
|
+
const qtyFor = (productId, variantId) => resolveBundleQty(bundle, productId, variantId);
|
|
103
130
|
const rows = [];
|
|
104
131
|
let oosCount = 0;
|
|
105
132
|
bundle.products.forEach((product, idx) => {
|
|
@@ -190,9 +217,7 @@ function buildRowState(bundle, product, productIndex) {
|
|
|
190
217
|
const eligibleVariants = selectedVariantIds && selectedVariantIds.length > 0 ? available.filter((v) => selectedVariantIds.includes(v.id)) : available;
|
|
191
218
|
const isOos = eligibleVariants.length === 0;
|
|
192
219
|
const selected = eligibleVariants[0] ?? null;
|
|
193
|
-
const
|
|
194
|
-
const variantQty = selected ? bundle.variantQuantities[selected.id] : void 0;
|
|
195
|
-
const qty = variantQty ?? productQty;
|
|
220
|
+
const qty = selected ? resolveBundleQty(bundle, product.id, selected.id) : 1;
|
|
196
221
|
return { product, eligibleVariants, selected, qty, isOos };
|
|
197
222
|
}
|
|
198
223
|
function renderHeader(bundle, currency) {
|
|
@@ -410,15 +435,11 @@ function renderSavingsBar() {
|
|
|
410
435
|
};
|
|
411
436
|
}
|
|
412
437
|
function renderCta(bundle, oosCount, onClick) {
|
|
413
|
-
const
|
|
414
|
-
button
|
|
415
|
-
button.className = "lb-bundle-cta";
|
|
416
|
-
button.setAttribute("data-add-bundle", "");
|
|
438
|
+
const label = oosCount > 0 ? `${oosCount} item${oosCount === 1 ? "" : "s"} out of stock` : bundle.widgetConfig.cta.ctaText || "Add to cart";
|
|
439
|
+
const button = buildCtaButton(label);
|
|
417
440
|
if (oosCount > 0) {
|
|
418
441
|
button.disabled = true;
|
|
419
|
-
button.textContent = `${oosCount} item${oosCount === 1 ? "" : "s"} out of stock`;
|
|
420
442
|
} else {
|
|
421
|
-
button.textContent = bundle.widgetConfig.cta.ctaText || "Add to cart";
|
|
422
443
|
button.addEventListener("click", () => {
|
|
423
444
|
if (button.disabled) return;
|
|
424
445
|
onClick();
|
|
@@ -515,12 +536,8 @@ function renderMixMatchBundle(container, bundle, onAddToCart, onCleanup) {
|
|
|
515
536
|
isOverMax: () => selections.length >= maxQty
|
|
516
537
|
});
|
|
517
538
|
root.appendChild(modal.el);
|
|
518
|
-
const cta =
|
|
519
|
-
cta.type = "button";
|
|
520
|
-
cta.className = "lb-bundle-cta";
|
|
521
|
-
cta.setAttribute("data-add-bundle", "");
|
|
539
|
+
const cta = buildCtaButton(`Select ${requiredQty} items to unlock`);
|
|
522
540
|
cta.disabled = true;
|
|
523
|
-
cta.textContent = `Select ${requiredQty} items to unlock`;
|
|
524
541
|
cta.addEventListener("click", () => {
|
|
525
542
|
if (cta.disabled) return;
|
|
526
543
|
const lines = selections.map((s) => ({
|
|
@@ -613,10 +630,10 @@ function renderMixMatchBundle(container, bundle, onAddToCart, onCleanup) {
|
|
|
613
630
|
const count = selections.length;
|
|
614
631
|
if (count < requiredQty) {
|
|
615
632
|
cta.disabled = true;
|
|
616
|
-
cta
|
|
633
|
+
setCtaLabel(cta, `Select ${requiredQty - count} more to unlock`);
|
|
617
634
|
} else {
|
|
618
635
|
cta.disabled = false;
|
|
619
|
-
cta
|
|
636
|
+
setCtaLabel(cta, wc.cta.ctaText || "Add to cart");
|
|
620
637
|
}
|
|
621
638
|
}
|
|
622
639
|
}
|
|
@@ -1351,12 +1368,9 @@ function renderSavingsBar3(resolved, selectedIndex, currency) {
|
|
|
1351
1368
|
function renderCta2(bundle, onClick) {
|
|
1352
1369
|
const product = bundle.products[0];
|
|
1353
1370
|
const isAvailable = product?.variants.nodes.some((v) => v.availableForSale);
|
|
1354
|
-
const
|
|
1355
|
-
button
|
|
1356
|
-
button.className = "lb-bundle-cta";
|
|
1357
|
-
button.setAttribute("data-add-bundle", "");
|
|
1371
|
+
const label = isAvailable ? bundle.widgetConfig.cta.ctaText || "Add to cart" : "Sold out";
|
|
1372
|
+
const button = buildCtaButton(label);
|
|
1358
1373
|
if (!isAvailable) button.disabled = true;
|
|
1359
|
-
button.textContent = isAvailable ? bundle.widgetConfig.cta.ctaText || "Add to cart" : "Sold out";
|
|
1360
1374
|
button.addEventListener("click", () => {
|
|
1361
1375
|
if (button.disabled) return;
|
|
1362
1376
|
onClick();
|
|
@@ -1470,11 +1484,20 @@ var BUNDLE_BASE_CSS = `/* Lime Bundles \u2014 shared base styles for all bundle
|
|
|
1470
1484
|
display: none;
|
|
1471
1485
|
}
|
|
1472
1486
|
|
|
1473
|
-
/*
|
|
1474
|
-
|
|
1487
|
+
/* Sibling widget spacing \u2014 separates multiple bundles on the same product page.
|
|
1488
|
+
Uses \`~\` (general sibling) rather than \`+\` (adjacent) because each Liquid
|
|
1489
|
+
loop iteration emits a {% style %} block before its widget div, so the
|
|
1490
|
+
rendered DOM alternates <style><widget><style><widget>. The \`+\` combinator
|
|
1491
|
+
requires immediate adjacency and would match nothing; \`~\` matches every
|
|
1492
|
+
widget after the first regardless of elements between. Single-widget pages
|
|
1493
|
+
stay unaffected (no prior \`.lb-bundle-widget\` sibling to match against).
|
|
1494
|
+
Only \`margin-top\` \u2014 do NOT override \`padding-top\` here. The gradient header
|
|
1495
|
+
uses \`margin-top: calc(-1 * var(--lb-widget-pad))\` to reach the widget's
|
|
1496
|
+
inner border edge, assuming padding-top == --lb-widget-pad. Changing
|
|
1497
|
+
padding-top on the subsequent widget breaks that math and leaves a visible
|
|
1498
|
+
gap above the header. */
|
|
1499
|
+
.lb-bundle-widget ~ .lb-bundle-widget {
|
|
1475
1500
|
margin-top: 24px;
|
|
1476
|
-
padding-top: 24px;
|
|
1477
|
-
border-top: 1px solid var(--lb-border);
|
|
1478
1501
|
}
|
|
1479
1502
|
|
|
1480
1503
|
/* Header \u2014 gradient banner with title + savings badge */
|
|
@@ -1743,9 +1766,16 @@ var BUNDLE_BASE_CSS = `/* Lime Bundles \u2014 shared base styles for all bundle
|
|
|
1743
1766
|
display: none;
|
|
1744
1767
|
}
|
|
1745
1768
|
|
|
1746
|
-
/* CTA button
|
|
1769
|
+
/* CTA button.
|
|
1770
|
+
* Label and spinner share a single 1\xD71 grid cell so the button's intrinsic
|
|
1771
|
+
* width/height stays fixed when swapping between them \u2014 no layout shift when
|
|
1772
|
+
* entering the loading state. Visibility (not display) is used so the hidden
|
|
1773
|
+
* child still contributes to the cell's min-content sizing. See the
|
|
1774
|
+
* [data-loading="true"] rules below. */
|
|
1747
1775
|
.lb-bundle-cta {
|
|
1748
|
-
display:
|
|
1776
|
+
display: grid;
|
|
1777
|
+
grid-template-rows: 1fr;
|
|
1778
|
+
grid-template-columns: 1fr;
|
|
1749
1779
|
width: 100%;
|
|
1750
1780
|
padding: 16px;
|
|
1751
1781
|
border: var(--lb-cta-border-width) solid var(--lb-cta-border-color);
|
|
@@ -1774,12 +1804,51 @@ var BUNDLE_BASE_CSS = `/* Lime Bundles \u2014 shared base styles for all bundle
|
|
|
1774
1804
|
cursor: not-allowed;
|
|
1775
1805
|
}
|
|
1776
1806
|
|
|
1807
|
+
/* Both the label and the spinner occupy grid cell (1, 1). Only one is
|
|
1808
|
+
* visible at a time; the other keeps its box for sizing but is invisible. */
|
|
1809
|
+
.lb-cta-label,
|
|
1810
|
+
.lb-cta-spinner {
|
|
1811
|
+
grid-row: 1;
|
|
1812
|
+
grid-column: 1;
|
|
1813
|
+
display: flex;
|
|
1814
|
+
align-items: center;
|
|
1815
|
+
justify-content: center;
|
|
1816
|
+
min-width: 0;
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
.lb-cta-spinner {
|
|
1820
|
+
visibility: hidden;
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1823
|
+
.lb-cta-spinner svg {
|
|
1824
|
+
width: 20px;
|
|
1825
|
+
height: 20px;
|
|
1826
|
+
animation: lb-cta-spin 0.8s linear infinite;
|
|
1827
|
+
}
|
|
1828
|
+
|
|
1829
|
+
@keyframes lb-cta-spin {
|
|
1830
|
+
to { transform: rotate(360deg); }
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1777
1833
|
.lb-bundle-cta[data-loading="true"] {
|
|
1778
|
-
opacity: 0.7;
|
|
1779
1834
|
cursor: wait;
|
|
1780
1835
|
pointer-events: none;
|
|
1781
1836
|
}
|
|
1782
1837
|
|
|
1838
|
+
.lb-bundle-cta[data-loading="true"] .lb-cta-label {
|
|
1839
|
+
visibility: hidden;
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1842
|
+
.lb-bundle-cta[data-loading="true"] .lb-cta-spinner {
|
|
1843
|
+
visibility: visible;
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1846
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1847
|
+
.lb-cta-spinner svg {
|
|
1848
|
+
animation-duration: 2.5s;
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1783
1852
|
/* Error message */
|
|
1784
1853
|
.lb-bundle-error {
|
|
1785
1854
|
font-size: 16px;
|