@lime-bundles/widget 3.2.1 → 3.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/README.md +19 -0
- package/dist/index.cjs +135 -35
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +75 -0
- package/dist/index.d.ts +75 -0
- package/dist/index.js +138 -36
- package/dist/index.js.map +1 -1
- package/dist/lime-bundle.global.js +60 -38
- package/dist/lime-bundle.global.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,64 @@
|
|
|
1
|
+
import { BuyerResolver } from '@lime-bundles/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* <lime-bundle> Web Component — renders Lime Bundles on any storefront.
|
|
5
|
+
*
|
|
6
|
+
* ## The two modes
|
|
7
|
+
*
|
|
8
|
+
* Single-bundle (pinned):
|
|
9
|
+
* <lime-bundle
|
|
10
|
+
* shop-domain="my-shop.myshopify.com"
|
|
11
|
+
* storefront-token="shpat_..."
|
|
12
|
+
* bundle-gid="gid://shopify/Metaobject/42"
|
|
13
|
+
* ></lime-bundle>
|
|
14
|
+
*
|
|
15
|
+
* Product-aware (matches classic Liquid theme block behaviour — one snippet
|
|
16
|
+
* on the product page renders every active bundle configured against that
|
|
17
|
+
* product):
|
|
18
|
+
* <lime-bundle
|
|
19
|
+
* shop-domain="my-shop.myshopify.com"
|
|
20
|
+
* storefront-token="shpat_..."
|
|
21
|
+
* ></lime-bundle>
|
|
22
|
+
*
|
|
23
|
+
* Product resolution cascade (when no `bundle-gid` is set):
|
|
24
|
+
* 1. explicit `product-handle` attribute
|
|
25
|
+
* 2. <meta name="shopify:product-handle" content="..."> on the page
|
|
26
|
+
* 3. /products/<handle> segment of window.location.pathname
|
|
27
|
+
* 4. fallthrough: renders nothing, fires `lime-bundle:error`
|
|
28
|
+
*
|
|
29
|
+
* ## Add-to-cart behaviour
|
|
30
|
+
*
|
|
31
|
+
* Merchants who do nothing get a default: the widget calls Shopify's
|
|
32
|
+
* Storefront Cart API (tokenless — no extra scopes required) and redirects
|
|
33
|
+
* the browser to the returned checkoutUrl. One-click-to-checkout is the
|
|
34
|
+
* right UX for most merchants pasting the widget into Webflow / Wix /
|
|
35
|
+
* Squarespace / static HTML.
|
|
36
|
+
*
|
|
37
|
+
* Merchants with their own cart state (Hydrogen's useCart, a custom cart
|
|
38
|
+
* drawer, etc.) opt out by attaching a listener that calls
|
|
39
|
+
* `event.preventDefault()`:
|
|
40
|
+
*
|
|
41
|
+
* document.querySelector("lime-bundle").addEventListener(
|
|
42
|
+
* "lime-bundle:add-to-cart",
|
|
43
|
+
* (ev) => {
|
|
44
|
+
* ev.preventDefault(); // suppress the default redirect
|
|
45
|
+
* myCart.linesAdd(ev.detail.lines);
|
|
46
|
+
* },
|
|
47
|
+
* );
|
|
48
|
+
*
|
|
49
|
+
* The event is always dispatched; only the default action is conditional.
|
|
50
|
+
*/
|
|
51
|
+
|
|
1
52
|
declare class LimeBundleElement extends HTMLElement {
|
|
2
53
|
static observedAttributes: string[];
|
|
54
|
+
/**
|
|
55
|
+
* B2B buyer identity. Set programmatically — `element.buyer = {...}` or
|
|
56
|
+
* `element.buyer = () => fetchToken()`. NEVER expose as an HTML attribute
|
|
57
|
+
* because the customer access token would land in DOM snapshots (Sentry,
|
|
58
|
+
* analytics, browser extensions) and Referer headers. See the package
|
|
59
|
+
* README ("Markets & B2B").
|
|
60
|
+
*/
|
|
61
|
+
buyer: BuyerResolver | undefined;
|
|
3
62
|
private shadow;
|
|
4
63
|
private bundles;
|
|
5
64
|
private abortController;
|
|
@@ -28,6 +87,22 @@ declare class LimeBundleElement extends HTMLElement {
|
|
|
28
87
|
private get productHandleAttr();
|
|
29
88
|
private get appUrl();
|
|
30
89
|
private get analyticsEnabled();
|
|
90
|
+
private get country();
|
|
91
|
+
private get language();
|
|
92
|
+
private get marketId();
|
|
93
|
+
/**
|
|
94
|
+
* Returns the @inContext-wrapped query when any context field is set;
|
|
95
|
+
* otherwise the plain query. Keeps responses publicly cacheable when
|
|
96
|
+
* no buyer/country/language is set.
|
|
97
|
+
*/
|
|
98
|
+
private wrapQueryForContext;
|
|
99
|
+
/**
|
|
100
|
+
* Returns true if this bundle should be hidden for the current market.
|
|
101
|
+
* For "all" bundles, always returns false (visible). For "specific"
|
|
102
|
+
* bundles, returns true when no marketId was set or when the bundle's
|
|
103
|
+
* marketIds doesn't include the configured market.
|
|
104
|
+
*/
|
|
105
|
+
private isMarketHidden;
|
|
31
106
|
private fetchBundle;
|
|
32
107
|
/**
|
|
33
108
|
* Resolve the visitor's A/B bucket for every bundle with an active test
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,64 @@
|
|
|
1
|
+
import { BuyerResolver } from '@lime-bundles/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* <lime-bundle> Web Component — renders Lime Bundles on any storefront.
|
|
5
|
+
*
|
|
6
|
+
* ## The two modes
|
|
7
|
+
*
|
|
8
|
+
* Single-bundle (pinned):
|
|
9
|
+
* <lime-bundle
|
|
10
|
+
* shop-domain="my-shop.myshopify.com"
|
|
11
|
+
* storefront-token="shpat_..."
|
|
12
|
+
* bundle-gid="gid://shopify/Metaobject/42"
|
|
13
|
+
* ></lime-bundle>
|
|
14
|
+
*
|
|
15
|
+
* Product-aware (matches classic Liquid theme block behaviour — one snippet
|
|
16
|
+
* on the product page renders every active bundle configured against that
|
|
17
|
+
* product):
|
|
18
|
+
* <lime-bundle
|
|
19
|
+
* shop-domain="my-shop.myshopify.com"
|
|
20
|
+
* storefront-token="shpat_..."
|
|
21
|
+
* ></lime-bundle>
|
|
22
|
+
*
|
|
23
|
+
* Product resolution cascade (when no `bundle-gid` is set):
|
|
24
|
+
* 1. explicit `product-handle` attribute
|
|
25
|
+
* 2. <meta name="shopify:product-handle" content="..."> on the page
|
|
26
|
+
* 3. /products/<handle> segment of window.location.pathname
|
|
27
|
+
* 4. fallthrough: renders nothing, fires `lime-bundle:error`
|
|
28
|
+
*
|
|
29
|
+
* ## Add-to-cart behaviour
|
|
30
|
+
*
|
|
31
|
+
* Merchants who do nothing get a default: the widget calls Shopify's
|
|
32
|
+
* Storefront Cart API (tokenless — no extra scopes required) and redirects
|
|
33
|
+
* the browser to the returned checkoutUrl. One-click-to-checkout is the
|
|
34
|
+
* right UX for most merchants pasting the widget into Webflow / Wix /
|
|
35
|
+
* Squarespace / static HTML.
|
|
36
|
+
*
|
|
37
|
+
* Merchants with their own cart state (Hydrogen's useCart, a custom cart
|
|
38
|
+
* drawer, etc.) opt out by attaching a listener that calls
|
|
39
|
+
* `event.preventDefault()`:
|
|
40
|
+
*
|
|
41
|
+
* document.querySelector("lime-bundle").addEventListener(
|
|
42
|
+
* "lime-bundle:add-to-cart",
|
|
43
|
+
* (ev) => {
|
|
44
|
+
* ev.preventDefault(); // suppress the default redirect
|
|
45
|
+
* myCart.linesAdd(ev.detail.lines);
|
|
46
|
+
* },
|
|
47
|
+
* );
|
|
48
|
+
*
|
|
49
|
+
* The event is always dispatched; only the default action is conditional.
|
|
50
|
+
*/
|
|
51
|
+
|
|
1
52
|
declare class LimeBundleElement extends HTMLElement {
|
|
2
53
|
static observedAttributes: string[];
|
|
54
|
+
/**
|
|
55
|
+
* B2B buyer identity. Set programmatically — `element.buyer = {...}` or
|
|
56
|
+
* `element.buyer = () => fetchToken()`. NEVER expose as an HTML attribute
|
|
57
|
+
* because the customer access token would land in DOM snapshots (Sentry,
|
|
58
|
+
* analytics, browser extensions) and Referer headers. See the package
|
|
59
|
+
* README ("Markets & B2B").
|
|
60
|
+
*/
|
|
61
|
+
buyer: BuyerResolver | undefined;
|
|
3
62
|
private shadow;
|
|
4
63
|
private bundles;
|
|
5
64
|
private abortController;
|
|
@@ -28,6 +87,22 @@ declare class LimeBundleElement extends HTMLElement {
|
|
|
28
87
|
private get productHandleAttr();
|
|
29
88
|
private get appUrl();
|
|
30
89
|
private get analyticsEnabled();
|
|
90
|
+
private get country();
|
|
91
|
+
private get language();
|
|
92
|
+
private get marketId();
|
|
93
|
+
/**
|
|
94
|
+
* Returns the @inContext-wrapped query when any context field is set;
|
|
95
|
+
* otherwise the plain query. Keeps responses publicly cacheable when
|
|
96
|
+
* no buyer/country/language is set.
|
|
97
|
+
*/
|
|
98
|
+
private wrapQueryForContext;
|
|
99
|
+
/**
|
|
100
|
+
* Returns true if this bundle should be hidden for the current market.
|
|
101
|
+
* For "all" bundles, always returns false (visible). For "specific"
|
|
102
|
+
* bundles, returns true when no marketId was set or when the bundle's
|
|
103
|
+
* marketIds doesn't include the configured market.
|
|
104
|
+
*/
|
|
105
|
+
private isMarketHidden;
|
|
31
106
|
private fetchBundle;
|
|
32
107
|
/**
|
|
33
108
|
* Resolve the visitor's A/B bucket for every bundle with an active test
|
package/dist/index.js
CHANGED
|
@@ -13,7 +13,9 @@ import {
|
|
|
13
13
|
injectCustomCss,
|
|
14
14
|
sanitizeCustomCss,
|
|
15
15
|
getABTestAssignment,
|
|
16
|
-
applyABVariantB
|
|
16
|
+
applyABVariantB,
|
|
17
|
+
hasInContext,
|
|
18
|
+
withInContext
|
|
17
19
|
} from "@lime-bundles/core";
|
|
18
20
|
|
|
19
21
|
// src/renderers/fixed.ts
|
|
@@ -142,6 +144,7 @@ function bindDropdown(select) {
|
|
|
142
144
|
let optionEls = [];
|
|
143
145
|
let instance;
|
|
144
146
|
function syncFromSelect() {
|
|
147
|
+
trigger.disabled = select.disabled;
|
|
145
148
|
const opts = readOptions(select);
|
|
146
149
|
const idx = select.selectedIndex;
|
|
147
150
|
triggerLabel.textContent = idx >= 0 && opts[idx] ? opts[idx].label : "";
|
|
@@ -1051,20 +1054,26 @@ function renderMixMatchBundle(container, bundle, onAddToCart, onCleanup) {
|
|
|
1051
1054
|
root.appendChild(pricingSection.el);
|
|
1052
1055
|
const savingsBar = wc.savingsBar.visible ? renderSavingsBar2() : null;
|
|
1053
1056
|
if (savingsBar) root.appendChild(savingsBar.el);
|
|
1057
|
+
const cta = buildCtaButton(`Select ${requiredQty} items to unlock`);
|
|
1058
|
+
cta.disabled = true;
|
|
1059
|
+
cta.addEventListener("click", () => {
|
|
1060
|
+
if (cta.disabled) return;
|
|
1061
|
+
onAddToCart(buildCartLines(selections, bundle));
|
|
1062
|
+
});
|
|
1054
1063
|
const modal = renderModal(bundle, eligible, currency, {
|
|
1055
1064
|
showSearch: wc.showSearch,
|
|
1056
1065
|
showQtySelector,
|
|
1057
1066
|
selections,
|
|
1058
1067
|
onAdd: (product, variant, quantity) => addSelection(product, variant, quantity),
|
|
1059
|
-
isComplete: () => selections.length >= requiredQty
|
|
1068
|
+
isComplete: () => selections.length >= requiredQty,
|
|
1069
|
+
getFocusAfterAdd: () => {
|
|
1070
|
+
const empty = slotsContainer.querySelector(
|
|
1071
|
+
".lb-mix-match__slot--empty"
|
|
1072
|
+
);
|
|
1073
|
+
return empty ?? cta;
|
|
1074
|
+
}
|
|
1060
1075
|
});
|
|
1061
1076
|
root.appendChild(modal.el);
|
|
1062
|
-
const cta = buildCtaButton(`Select ${requiredQty} items to unlock`);
|
|
1063
|
-
cta.disabled = true;
|
|
1064
|
-
cta.addEventListener("click", () => {
|
|
1065
|
-
if (cta.disabled) return;
|
|
1066
|
-
onAddToCart(buildCartLines(selections, bundle));
|
|
1067
|
-
});
|
|
1068
1077
|
root.appendChild(cta);
|
|
1069
1078
|
root.appendChild(
|
|
1070
1079
|
el("p", "lb-bundle-error", { "data-error": "", "aria-live": "polite" })
|
|
@@ -1196,12 +1205,14 @@ function renderProgress(requiredQty) {
|
|
|
1196
1205
|
const wrap = el("div", "lb-mix-match__progress");
|
|
1197
1206
|
const labels = el("div", "lb-mix-match__progress-labels");
|
|
1198
1207
|
const count = el("span", "lb-mix-match__progress-count", {
|
|
1199
|
-
"data-progress-count": ""
|
|
1208
|
+
"data-progress-count": "",
|
|
1209
|
+
"aria-live": "polite"
|
|
1200
1210
|
});
|
|
1201
1211
|
count.textContent = `0 of ${requiredQty} selected`;
|
|
1202
1212
|
labels.appendChild(count);
|
|
1203
1213
|
const remaining = el("span", "lb-mix-match__progress-remaining", {
|
|
1204
|
-
"data-progress-remaining": ""
|
|
1214
|
+
"data-progress-remaining": "",
|
|
1215
|
+
"aria-live": "polite"
|
|
1205
1216
|
});
|
|
1206
1217
|
remaining.textContent = `${requiredQty} more to go`;
|
|
1207
1218
|
labels.appendChild(remaining);
|
|
@@ -1576,12 +1587,12 @@ function renderModal(bundle, eligible, currency, handlers) {
|
|
|
1576
1587
|
refreshFromSelections: () => {
|
|
1577
1588
|
}
|
|
1578
1589
|
};
|
|
1590
|
+
const optionSelects = [];
|
|
1579
1591
|
if (availableVariants.length > 1) {
|
|
1580
1592
|
const optionNames = availableVariants[0].selectedOptions.map(
|
|
1581
1593
|
(o) => o.name
|
|
1582
1594
|
);
|
|
1583
1595
|
const productIdTail = ep.product.id.replace(/^.*\//, "");
|
|
1584
|
-
const optionSelects = [];
|
|
1585
1596
|
const resolveVariant = (values) => availableVariants.find(
|
|
1586
1597
|
(v) => v.selectedOptions.length === values.length && v.selectedOptions.every((o, i) => o.value === values[i])
|
|
1587
1598
|
) ?? null;
|
|
@@ -1690,7 +1701,13 @@ function renderModal(bundle, eligible, currency, handlers) {
|
|
|
1690
1701
|
productEl.appendChild(info);
|
|
1691
1702
|
let addBtn = null;
|
|
1692
1703
|
const refreshAddState = () => {
|
|
1693
|
-
if (!addBtn)
|
|
1704
|
+
if (!addBtn) {
|
|
1705
|
+
optionSelects.forEach((sel) => {
|
|
1706
|
+
sel.disabled = true;
|
|
1707
|
+
});
|
|
1708
|
+
if (stepper) stepper.setExternallyDisabled(true);
|
|
1709
|
+
return;
|
|
1710
|
+
}
|
|
1694
1711
|
const { cap } = computeStepperBounds();
|
|
1695
1712
|
const already = alreadyInBundleFor(
|
|
1696
1713
|
handlers.selections,
|
|
@@ -1709,12 +1726,19 @@ function renderModal(bundle, eligible, currency, handlers) {
|
|
|
1709
1726
|
if (productInBundle) {
|
|
1710
1727
|
addBtn.disabled = true;
|
|
1711
1728
|
addBtn.textContent = "Added";
|
|
1729
|
+
addBtn.setAttribute("aria-label", `Added ${ep.product.title}`);
|
|
1712
1730
|
productEl.classList.add("lb-mix-match__modal-product--in-bundle");
|
|
1713
1731
|
} else {
|
|
1714
1732
|
addBtn.textContent = "Add";
|
|
1733
|
+
addBtn.setAttribute("aria-label", `Add ${ep.product.title}`);
|
|
1715
1734
|
productEl.classList.remove("lb-mix-match__modal-product--in-bundle");
|
|
1716
1735
|
addBtn.disabled = handlers.isComplete() || cap < rule.min;
|
|
1717
1736
|
}
|
|
1737
|
+
const rowDisabled = addBtn.disabled;
|
|
1738
|
+
optionSelects.forEach((sel) => {
|
|
1739
|
+
sel.disabled = rowDisabled;
|
|
1740
|
+
});
|
|
1741
|
+
if (stepper) stepper.setExternallyDisabled(rowDisabled);
|
|
1718
1742
|
};
|
|
1719
1743
|
if (!ep.isOos) {
|
|
1720
1744
|
const actions = el("div", "lb-mix-match__modal-product-actions");
|
|
@@ -1723,12 +1747,15 @@ function renderModal(bundle, eligible, currency, handlers) {
|
|
|
1723
1747
|
addBtn.type = "button";
|
|
1724
1748
|
addBtn.className = "lb-mix-match__modal-add";
|
|
1725
1749
|
addBtn.textContent = "Add";
|
|
1750
|
+
addBtn.setAttribute("aria-label", `Add ${ep.product.title}`);
|
|
1726
1751
|
addBtn.addEventListener("click", () => {
|
|
1727
1752
|
if (!addBtn || addBtn.disabled) return;
|
|
1728
1753
|
if (handlers.isComplete()) return;
|
|
1729
1754
|
const qty = stepper ? stepper.value() : rule.min;
|
|
1730
1755
|
handlers.onAdd(ep.product, currentVariant, qty);
|
|
1731
1756
|
if (stepper) stepper.reset(rule.min);
|
|
1757
|
+
const nextFocus = handlers.getFocusAfterAdd();
|
|
1758
|
+
if (nextFocus) lastFocused = nextFocus;
|
|
1732
1759
|
close();
|
|
1733
1760
|
});
|
|
1734
1761
|
actions.appendChild(addBtn);
|
|
@@ -1824,6 +1851,7 @@ function renderQtyStepper(opts) {
|
|
|
1824
1851
|
plus.innerHTML = STEPPER_PLUS_SVG;
|
|
1825
1852
|
wrap.appendChild(plus);
|
|
1826
1853
|
let current = clamp2(opts.initial, opts.getBounds());
|
|
1854
|
+
let externallyDisabled = false;
|
|
1827
1855
|
function clamp2(n, b) {
|
|
1828
1856
|
return Math.max(b.min, Math.min(b.max, n));
|
|
1829
1857
|
}
|
|
@@ -1831,8 +1859,8 @@ function renderQtyStepper(opts) {
|
|
|
1831
1859
|
const b = opts.getBounds();
|
|
1832
1860
|
current = clamp2(current, b);
|
|
1833
1861
|
valueEl.textContent = String(current);
|
|
1834
|
-
minus.disabled = current <= b.min;
|
|
1835
|
-
plus.disabled = current >= b.max;
|
|
1862
|
+
minus.disabled = externallyDisabled || current <= b.min;
|
|
1863
|
+
plus.disabled = externallyDisabled || current >= b.max;
|
|
1836
1864
|
}
|
|
1837
1865
|
minus.addEventListener("click", () => {
|
|
1838
1866
|
const b = opts.getBounds();
|
|
@@ -1852,7 +1880,11 @@ function renderQtyStepper(opts) {
|
|
|
1852
1880
|
current = clamp2(qty, opts.getBounds());
|
|
1853
1881
|
paint();
|
|
1854
1882
|
},
|
|
1855
|
-
refresh: paint
|
|
1883
|
+
refresh: paint,
|
|
1884
|
+
setExternallyDisabled(disabled) {
|
|
1885
|
+
externallyDisabled = disabled;
|
|
1886
|
+
paint();
|
|
1887
|
+
}
|
|
1856
1888
|
};
|
|
1857
1889
|
}
|
|
1858
1890
|
function buildEligibleProducts(bundle, oosBehavior) {
|
|
@@ -2499,6 +2531,13 @@ var BUNDLE_BASE_CSS = `/* Lime Bundles \u2014 shared base styles for all bundle
|
|
|
2499
2531
|
text-decoration: underline;
|
|
2500
2532
|
}
|
|
2501
2533
|
|
|
2534
|
+
a.lb-bundle-product-name:focus-visible {
|
|
2535
|
+
outline: 2px solid var(--lb-primary-color);
|
|
2536
|
+
outline-offset: 0;
|
|
2537
|
+
box-shadow: none;
|
|
2538
|
+
border-radius: 2px;
|
|
2539
|
+
}
|
|
2540
|
+
|
|
2502
2541
|
.lb-bundle-product-price {
|
|
2503
2542
|
/* --lb-product-price-display fallback is the "on" branch; Liquid sets 'none' when merchant disables. */
|
|
2504
2543
|
display: var(--lb-product-price-display, block);
|
|
@@ -2947,6 +2986,7 @@ var BUNDLE_FIXED_CSS = `/* Lime Bundles \u2014 Fixed bundle styles */
|
|
|
2947
2986
|
.lb-bundle-variant-select:focus-visible {
|
|
2948
2987
|
outline: 2px solid var(--lb-primary-color);
|
|
2949
2988
|
outline-offset: 2px;
|
|
2989
|
+
box-shadow: none;
|
|
2950
2990
|
}
|
|
2951
2991
|
`;
|
|
2952
2992
|
var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
@@ -3024,7 +3064,8 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
3024
3064
|
|
|
3025
3065
|
.lb-mix-match__slot--empty:focus-visible {
|
|
3026
3066
|
outline: 2px solid var(--lb-primary-color);
|
|
3027
|
-
outline-offset: 2px;
|
|
3067
|
+
outline-offset: -2px;
|
|
3068
|
+
box-shadow: none;
|
|
3028
3069
|
border-radius: 8px;
|
|
3029
3070
|
}
|
|
3030
3071
|
|
|
@@ -3091,6 +3132,13 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
3091
3132
|
text-decoration: underline;
|
|
3092
3133
|
}
|
|
3093
3134
|
|
|
3135
|
+
.lb-mix-match__slot--filled a.lb-mix-match__filled-title:focus-visible {
|
|
3136
|
+
outline: 2px solid var(--lb-primary-color);
|
|
3137
|
+
outline-offset: 2px;
|
|
3138
|
+
box-shadow: none;
|
|
3139
|
+
border-radius: 2px;
|
|
3140
|
+
}
|
|
3141
|
+
|
|
3094
3142
|
/* .lb-mix-match__filled-variant \u2014 styled jointly with
|
|
3095
3143
|
.lb-bundle-variant-badge above to keep the variant text consistent
|
|
3096
3144
|
across mix-match and fixed bundle widgets. */
|
|
@@ -3136,7 +3184,8 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
3136
3184
|
|
|
3137
3185
|
.lb-mix-match__slot-remove:focus-visible {
|
|
3138
3186
|
outline: 2px solid var(--lb-primary-color);
|
|
3139
|
-
outline-offset: 2px;
|
|
3187
|
+
outline-offset: -2px;
|
|
3188
|
+
box-shadow: none;
|
|
3140
3189
|
}
|
|
3141
3190
|
|
|
3142
3191
|
/* === Modal Overlay === */
|
|
@@ -3216,7 +3265,8 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
3216
3265
|
|
|
3217
3266
|
.lb-mix-match__modal-close:focus-visible {
|
|
3218
3267
|
outline: 2px solid var(--lb-primary-color);
|
|
3219
|
-
outline-offset:
|
|
3268
|
+
outline-offset: 0;
|
|
3269
|
+
box-shadow: none;
|
|
3220
3270
|
}
|
|
3221
3271
|
|
|
3222
3272
|
/* === Modal Search === */
|
|
@@ -3244,9 +3294,10 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
3244
3294
|
color: color-mix(in srgb, var(--lb-picker-text) 50%, transparent);
|
|
3245
3295
|
}
|
|
3246
3296
|
|
|
3247
|
-
.lb-mix-match__modal-search-input:focus {
|
|
3248
|
-
outline:
|
|
3249
|
-
|
|
3297
|
+
.lb-mix-match__modal-search-input:focus-visible {
|
|
3298
|
+
outline: 2px solid var(--lb-primary-color);
|
|
3299
|
+
outline-offset: 2px;
|
|
3300
|
+
box-shadow: none;
|
|
3250
3301
|
}
|
|
3251
3302
|
|
|
3252
3303
|
.lb-mix-match__modal-search-clear {
|
|
@@ -3379,6 +3430,7 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
3379
3430
|
.lb-mix-match__variant-select:focus-visible {
|
|
3380
3431
|
outline: 2px solid var(--lb-primary-color);
|
|
3381
3432
|
outline-offset: 2px;
|
|
3433
|
+
box-shadow: none;
|
|
3382
3434
|
}
|
|
3383
3435
|
|
|
3384
3436
|
/* Per-pick quantity stepper inside the picker modal product row.
|
|
@@ -3433,13 +3485,10 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
3433
3485
|
justify-content: center;
|
|
3434
3486
|
}
|
|
3435
3487
|
|
|
3436
|
-
.lb-mix-match__qty-stepper-button:hover:not(:disabled) {
|
|
3437
|
-
background: color-mix(in srgb, var(--lb-picker-text) 6%, transparent);
|
|
3438
|
-
}
|
|
3439
|
-
|
|
3440
3488
|
.lb-mix-match__qty-stepper-button:focus-visible {
|
|
3441
3489
|
outline: 2px solid var(--lb-primary-color);
|
|
3442
3490
|
outline-offset: -2px;
|
|
3491
|
+
box-shadow: none;
|
|
3443
3492
|
}
|
|
3444
3493
|
|
|
3445
3494
|
.lb-mix-match__qty-stepper-button:disabled {
|
|
@@ -3495,6 +3544,7 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
3495
3544
|
.lb-mix-match__modal-add:focus-visible {
|
|
3496
3545
|
outline: 2px solid var(--lb-primary-color);
|
|
3497
3546
|
outline-offset: 2px;
|
|
3547
|
+
box-shadow: none;
|
|
3498
3548
|
}
|
|
3499
3549
|
|
|
3500
3550
|
.lb-mix-match__modal-add:disabled {
|
|
@@ -3612,12 +3662,14 @@ var BUNDLE_VOLUME_CSS = `/* Lime Bundles \u2014 Volume / Quantity Breaks styles
|
|
|
3612
3662
|
border-color: var(--lb-tier-selected-border-color);
|
|
3613
3663
|
}
|
|
3614
3664
|
|
|
3615
|
-
/* Suppress the UA default focus outline so a
|
|
3616
|
-
tier doesn't briefly show the 1px focus ring
|
|
3617
|
-
|
|
3618
|
-
|
|
3665
|
+
/* Suppress the UA default focus outline + Dawn's focus shadow so a
|
|
3666
|
+
freshly-clicked selected tier doesn't briefly show the 1px focus ring
|
|
3667
|
+
or shadow on top of (or in place of) the custom selected-state
|
|
3668
|
+
outline below. Keyboard focus is still indicated by the
|
|
3669
|
+
:focus-visible rule. */
|
|
3619
3670
|
.lb-volume__tier:focus {
|
|
3620
3671
|
outline: none;
|
|
3672
|
+
box-shadow: none;
|
|
3621
3673
|
}
|
|
3622
3674
|
|
|
3623
3675
|
/* Keyboard focus indicator \u2014 explicitly excludes the selected tier so
|
|
@@ -3625,7 +3677,8 @@ var BUNDLE_VOLUME_CSS = `/* Lime Bundles \u2014 Volume / Quantity Breaks styles
|
|
|
3625
3677
|
outline property when both states apply at once. */
|
|
3626
3678
|
.lb-volume__tier:focus-visible:not([aria-checked="true"]) {
|
|
3627
3679
|
outline: 2px solid var(--lb-primary-color);
|
|
3628
|
-
outline-offset: 2px;
|
|
3680
|
+
outline-offset: -2px;
|
|
3681
|
+
box-shadow: none;
|
|
3629
3682
|
}
|
|
3630
3683
|
|
|
3631
3684
|
.lb-volume__tier[aria-checked="true"] {
|
|
@@ -3773,6 +3826,7 @@ var BUNDLE_DROPDOWN_CSS = `/**
|
|
|
3773
3826
|
.lb-dropdown-trigger:focus-visible {
|
|
3774
3827
|
outline: 2px solid var(--lb-primary-color);
|
|
3775
3828
|
outline-offset: 2px;
|
|
3829
|
+
box-shadow: none;
|
|
3776
3830
|
}
|
|
3777
3831
|
|
|
3778
3832
|
.lb-dropdown-trigger[aria-expanded="true"] {
|
|
@@ -4085,8 +4139,19 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
4085
4139
|
"product-handle",
|
|
4086
4140
|
"app-url",
|
|
4087
4141
|
"analytics",
|
|
4088
|
-
"locale"
|
|
4142
|
+
"locale",
|
|
4143
|
+
"country",
|
|
4144
|
+
"language",
|
|
4145
|
+
"market-id"
|
|
4089
4146
|
];
|
|
4147
|
+
/**
|
|
4148
|
+
* B2B buyer identity. Set programmatically — `element.buyer = {...}` or
|
|
4149
|
+
* `element.buyer = () => fetchToken()`. NEVER expose as an HTML attribute
|
|
4150
|
+
* because the customer access token would land in DOM snapshots (Sentry,
|
|
4151
|
+
* analytics, browser extensions) and Referer headers. See the package
|
|
4152
|
+
* README ("Markets & B2B").
|
|
4153
|
+
*/
|
|
4154
|
+
buyer = void 0;
|
|
4090
4155
|
shadow;
|
|
4091
4156
|
bundles = [];
|
|
4092
4157
|
abortController = null;
|
|
@@ -4149,6 +4214,40 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
4149
4214
|
get analyticsEnabled() {
|
|
4150
4215
|
return this.getAttribute("analytics") !== "false";
|
|
4151
4216
|
}
|
|
4217
|
+
get country() {
|
|
4218
|
+
return this.getAttribute("country") ?? void 0;
|
|
4219
|
+
}
|
|
4220
|
+
get language() {
|
|
4221
|
+
return this.getAttribute("language") ?? void 0;
|
|
4222
|
+
}
|
|
4223
|
+
get marketId() {
|
|
4224
|
+
return this.getAttribute("market-id") ?? void 0;
|
|
4225
|
+
}
|
|
4226
|
+
/**
|
|
4227
|
+
* Returns the @inContext-wrapped query when any context field is set;
|
|
4228
|
+
* otherwise the plain query. Keeps responses publicly cacheable when
|
|
4229
|
+
* no buyer/country/language is set.
|
|
4230
|
+
*/
|
|
4231
|
+
wrapQueryForContext(query) {
|
|
4232
|
+
return hasInContext({
|
|
4233
|
+
shopDomain: this.shopDomain,
|
|
4234
|
+
accessToken: this.storefrontToken,
|
|
4235
|
+
country: this.country,
|
|
4236
|
+
language: this.language,
|
|
4237
|
+
buyer: this.buyer
|
|
4238
|
+
}) ? withInContext(query) : query;
|
|
4239
|
+
}
|
|
4240
|
+
/**
|
|
4241
|
+
* Returns true if this bundle should be hidden for the current market.
|
|
4242
|
+
* For "all" bundles, always returns false (visible). For "specific"
|
|
4243
|
+
* bundles, returns true when no marketId was set or when the bundle's
|
|
4244
|
+
* marketIds doesn't include the configured market.
|
|
4245
|
+
*/
|
|
4246
|
+
isMarketHidden(bundle) {
|
|
4247
|
+
if (bundle.marketVisibility !== "specific") return false;
|
|
4248
|
+
if (!this.marketId) return true;
|
|
4249
|
+
return !bundle.marketIds.includes(this.marketId);
|
|
4250
|
+
}
|
|
4152
4251
|
async fetchBundle() {
|
|
4153
4252
|
if (!this.shopDomain || !this.storefrontToken) {
|
|
4154
4253
|
this.renderError(
|
|
@@ -4162,7 +4261,10 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
4162
4261
|
this.renderLoading();
|
|
4163
4262
|
const client = createStorefrontClient({
|
|
4164
4263
|
shopDomain: this.shopDomain,
|
|
4165
|
-
accessToken: this.storefrontToken
|
|
4264
|
+
accessToken: this.storefrontToken,
|
|
4265
|
+
country: this.country,
|
|
4266
|
+
language: this.language,
|
|
4267
|
+
buyer: this.buyer
|
|
4166
4268
|
});
|
|
4167
4269
|
try {
|
|
4168
4270
|
let bundlePromise;
|
|
@@ -4246,7 +4348,7 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
4246
4348
|
}
|
|
4247
4349
|
async fetchSingleBundle(client, signal) {
|
|
4248
4350
|
const data = await client.query(
|
|
4249
|
-
BUNDLE_METAOBJECT_QUERY,
|
|
4351
|
+
this.wrapQueryForContext(BUNDLE_METAOBJECT_QUERY),
|
|
4250
4352
|
{ id: this.bundleGid },
|
|
4251
4353
|
{ signal }
|
|
4252
4354
|
);
|
|
@@ -4258,11 +4360,11 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
4258
4360
|
data.metaobject.id,
|
|
4259
4361
|
data.metaobject.fields
|
|
4260
4362
|
);
|
|
4261
|
-
this.bundles = parsed ? [parsed] : [];
|
|
4363
|
+
this.bundles = parsed && !this.isMarketHidden(parsed) ? [parsed] : [];
|
|
4262
4364
|
}
|
|
4263
4365
|
async fetchProductBundles(client, signal, productHandle) {
|
|
4264
4366
|
const data = await client.query(
|
|
4265
|
-
BUNDLES_FOR_PRODUCT_QUERY,
|
|
4367
|
+
this.wrapQueryForContext(BUNDLES_FOR_PRODUCT_QUERY),
|
|
4266
4368
|
{ handle: productHandle },
|
|
4267
4369
|
{ signal }
|
|
4268
4370
|
);
|
|
@@ -4274,7 +4376,7 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
4274
4376
|
const bundles = [];
|
|
4275
4377
|
for (const ref of refs) {
|
|
4276
4378
|
const parsed = parseMetaobjectBundle(ref.id, ref.fields);
|
|
4277
|
-
if (parsed) bundles.push(parsed);
|
|
4379
|
+
if (parsed && !this.isMarketHidden(parsed)) bundles.push(parsed);
|
|
4278
4380
|
}
|
|
4279
4381
|
this.bundles = bundles;
|
|
4280
4382
|
}
|