@lime-bundles/widget 3.2.2 → 3.4.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 +85 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +88 -7
- package/dist/index.d.ts +88 -7
- package/dist/index.js +88 -25
- package/dist/index.js.map +1 -1
- package/dist/lime-bundle.global.js +18 -18
- package/dist/lime-bundle.global.js.map +1 -1
- package/docs/react-nextjs.md +14 -5
- package/docs/web-component.md +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,16 +87,38 @@ 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
|
-
* Resolve the visitor's
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* the
|
|
108
|
+
* Resolve the visitor's assigned variant for every primary bundle in a link
|
|
109
|
+
* group. Runs in parallel; any assignment failure logs internally and still
|
|
110
|
+
* renders the primary (safe default). The `getLinkGroupAssignment` helper
|
|
111
|
+
* persists the bucket via a first-party cookie; variant attribution is
|
|
112
|
+
* recorded server-side from the cart-line `_lime_bundle_gid` + `_lime_link_group`
|
|
113
|
+
* attributes the cart-add path stamps when an assignment lands on a non-primary
|
|
114
|
+
* variant.
|
|
115
|
+
*
|
|
116
|
+
* When the assignment points to a non-primary variant the SDK swaps in that
|
|
117
|
+
* variant's `ParsedBundle` from the primary's `linkGroup.variants` list. The
|
|
118
|
+
* variant data must already be present in the storefront query response — the
|
|
119
|
+
* SDK does NOT issue a separate metaobject fetch on the hot path.
|
|
39
120
|
*/
|
|
40
|
-
private
|
|
121
|
+
private applyLinkGroupVariants;
|
|
41
122
|
private fetchSingleBundle;
|
|
42
123
|
private fetchProductBundles;
|
|
43
124
|
/**
|
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,16 +87,38 @@ 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
|
-
* Resolve the visitor's
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* the
|
|
108
|
+
* Resolve the visitor's assigned variant for every primary bundle in a link
|
|
109
|
+
* group. Runs in parallel; any assignment failure logs internally and still
|
|
110
|
+
* renders the primary (safe default). The `getLinkGroupAssignment` helper
|
|
111
|
+
* persists the bucket via a first-party cookie; variant attribution is
|
|
112
|
+
* recorded server-side from the cart-line `_lime_bundle_gid` + `_lime_link_group`
|
|
113
|
+
* attributes the cart-add path stamps when an assignment lands on a non-primary
|
|
114
|
+
* variant.
|
|
115
|
+
*
|
|
116
|
+
* When the assignment points to a non-primary variant the SDK swaps in that
|
|
117
|
+
* variant's `ParsedBundle` from the primary's `linkGroup.variants` list. The
|
|
118
|
+
* variant data must already be present in the storefront query response — the
|
|
119
|
+
* SDK does NOT issue a separate metaobject fetch on the hot path.
|
|
39
120
|
*/
|
|
40
|
-
private
|
|
121
|
+
private applyLinkGroupVariants;
|
|
41
122
|
private fetchSingleBundle;
|
|
42
123
|
private fetchProductBundles;
|
|
43
124
|
/**
|
package/dist/index.js
CHANGED
|
@@ -12,8 +12,9 @@ import {
|
|
|
12
12
|
reportAddToCart,
|
|
13
13
|
injectCustomCss,
|
|
14
14
|
sanitizeCustomCss,
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
getLinkGroupAssignment,
|
|
16
|
+
hasInContext,
|
|
17
|
+
withInContext
|
|
17
18
|
} from "@lime-bundles/core";
|
|
18
19
|
|
|
19
20
|
// src/renderers/fixed.ts
|
|
@@ -4137,8 +4138,19 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
4137
4138
|
"product-handle",
|
|
4138
4139
|
"app-url",
|
|
4139
4140
|
"analytics",
|
|
4140
|
-
"locale"
|
|
4141
|
+
"locale",
|
|
4142
|
+
"country",
|
|
4143
|
+
"language",
|
|
4144
|
+
"market-id"
|
|
4141
4145
|
];
|
|
4146
|
+
/**
|
|
4147
|
+
* B2B buyer identity. Set programmatically — `element.buyer = {...}` or
|
|
4148
|
+
* `element.buyer = () => fetchToken()`. NEVER expose as an HTML attribute
|
|
4149
|
+
* because the customer access token would land in DOM snapshots (Sentry,
|
|
4150
|
+
* analytics, browser extensions) and Referer headers. See the package
|
|
4151
|
+
* README ("Markets & B2B").
|
|
4152
|
+
*/
|
|
4153
|
+
buyer = void 0;
|
|
4142
4154
|
shadow;
|
|
4143
4155
|
bundles = [];
|
|
4144
4156
|
abortController = null;
|
|
@@ -4201,6 +4213,40 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
4201
4213
|
get analyticsEnabled() {
|
|
4202
4214
|
return this.getAttribute("analytics") !== "false";
|
|
4203
4215
|
}
|
|
4216
|
+
get country() {
|
|
4217
|
+
return this.getAttribute("country") ?? void 0;
|
|
4218
|
+
}
|
|
4219
|
+
get language() {
|
|
4220
|
+
return this.getAttribute("language") ?? void 0;
|
|
4221
|
+
}
|
|
4222
|
+
get marketId() {
|
|
4223
|
+
return this.getAttribute("market-id") ?? void 0;
|
|
4224
|
+
}
|
|
4225
|
+
/**
|
|
4226
|
+
* Returns the @inContext-wrapped query when any context field is set;
|
|
4227
|
+
* otherwise the plain query. Keeps responses publicly cacheable when
|
|
4228
|
+
* no buyer/country/language is set.
|
|
4229
|
+
*/
|
|
4230
|
+
wrapQueryForContext(query) {
|
|
4231
|
+
return hasInContext({
|
|
4232
|
+
shopDomain: this.shopDomain,
|
|
4233
|
+
accessToken: this.storefrontToken,
|
|
4234
|
+
country: this.country,
|
|
4235
|
+
language: this.language,
|
|
4236
|
+
buyer: this.buyer
|
|
4237
|
+
}) ? withInContext(query) : query;
|
|
4238
|
+
}
|
|
4239
|
+
/**
|
|
4240
|
+
* Returns true if this bundle should be hidden for the current market.
|
|
4241
|
+
* For "all" bundles, always returns false (visible). For "specific"
|
|
4242
|
+
* bundles, returns true when no marketId was set or when the bundle's
|
|
4243
|
+
* marketIds doesn't include the configured market.
|
|
4244
|
+
*/
|
|
4245
|
+
isMarketHidden(bundle) {
|
|
4246
|
+
if (bundle.marketVisibility !== "specific") return false;
|
|
4247
|
+
if (!this.marketId) return true;
|
|
4248
|
+
return !bundle.marketIds.includes(this.marketId);
|
|
4249
|
+
}
|
|
4204
4250
|
async fetchBundle() {
|
|
4205
4251
|
if (!this.shopDomain || !this.storefrontToken) {
|
|
4206
4252
|
this.renderError(
|
|
@@ -4214,7 +4260,10 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
4214
4260
|
this.renderLoading();
|
|
4215
4261
|
const client = createStorefrontClient({
|
|
4216
4262
|
shopDomain: this.shopDomain,
|
|
4217
|
-
accessToken: this.storefrontToken
|
|
4263
|
+
accessToken: this.storefrontToken,
|
|
4264
|
+
country: this.country,
|
|
4265
|
+
language: this.language,
|
|
4266
|
+
buyer: this.buyer
|
|
4218
4267
|
});
|
|
4219
4268
|
try {
|
|
4220
4269
|
let bundlePromise;
|
|
@@ -4253,7 +4302,7 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
4253
4302
|
const sanitized = sanitizeCustomCss(css.shop.metafield.value);
|
|
4254
4303
|
if (sanitized.ok) this.shopCustomCss = sanitized.css;
|
|
4255
4304
|
}
|
|
4256
|
-
await this.
|
|
4305
|
+
await this.applyLinkGroupVariants();
|
|
4257
4306
|
this.renderBundles();
|
|
4258
4307
|
} catch (err) {
|
|
4259
4308
|
if (controller.signal.aborted) return;
|
|
@@ -4265,40 +4314,54 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
4265
4314
|
}
|
|
4266
4315
|
}
|
|
4267
4316
|
/**
|
|
4268
|
-
* Resolve the visitor's
|
|
4269
|
-
*
|
|
4270
|
-
*
|
|
4271
|
-
*
|
|
4272
|
-
*
|
|
4273
|
-
* the
|
|
4317
|
+
* Resolve the visitor's assigned variant for every primary bundle in a link
|
|
4318
|
+
* group. Runs in parallel; any assignment failure logs internally and still
|
|
4319
|
+
* renders the primary (safe default). The `getLinkGroupAssignment` helper
|
|
4320
|
+
* persists the bucket via a first-party cookie; variant attribution is
|
|
4321
|
+
* recorded server-side from the cart-line `_lime_bundle_gid` + `_lime_link_group`
|
|
4322
|
+
* attributes the cart-add path stamps when an assignment lands on a non-primary
|
|
4323
|
+
* variant.
|
|
4324
|
+
*
|
|
4325
|
+
* When the assignment points to a non-primary variant the SDK swaps in that
|
|
4326
|
+
* variant's `ParsedBundle` from the primary's `linkGroup.variants` list. The
|
|
4327
|
+
* variant data must already be present in the storefront query response — the
|
|
4328
|
+
* SDK does NOT issue a separate metaobject fetch on the hot path.
|
|
4274
4329
|
*/
|
|
4275
|
-
async
|
|
4330
|
+
async applyLinkGroupVariants() {
|
|
4276
4331
|
if (this.bundles.length === 0) return;
|
|
4332
|
+
const bundleLookupByMetaobjectId = /* @__PURE__ */ new Map();
|
|
4333
|
+
for (const b of this.bundles) {
|
|
4334
|
+
bundleLookupByMetaobjectId.set(b.id, b);
|
|
4335
|
+
}
|
|
4277
4336
|
const results = await Promise.all(
|
|
4278
4337
|
this.bundles.map(async (bundle) => {
|
|
4279
|
-
|
|
4338
|
+
const lg = bundle.linkGroup;
|
|
4339
|
+
if (!lg || !lg.isPrimary || !lg.variants || lg.variants.length < 2) {
|
|
4340
|
+
return bundle;
|
|
4341
|
+
}
|
|
4280
4342
|
try {
|
|
4281
|
-
const assignment = await
|
|
4282
|
-
|
|
4283
|
-
|
|
4343
|
+
const assignment = await getLinkGroupAssignment(lg.id, lg.variants);
|
|
4344
|
+
if (!assignment) return bundle;
|
|
4345
|
+
if (assignment.variantMetaobjectId === bundle.id) return bundle;
|
|
4346
|
+
const swap = bundleLookupByMetaobjectId.get(
|
|
4347
|
+
assignment.variantMetaobjectId
|
|
4284
4348
|
);
|
|
4285
|
-
if (
|
|
4286
|
-
|
|
4287
|
-
}
|
|
4349
|
+
if (!swap) return bundle;
|
|
4350
|
+
return swap;
|
|
4288
4351
|
} catch (err) {
|
|
4289
4352
|
console.warn(
|
|
4290
|
-
`[lime-bundle]
|
|
4353
|
+
`[lime-bundle] link-group assignment failed for bundle ${bundle.id}; falling back to primary.`,
|
|
4291
4354
|
err
|
|
4292
4355
|
);
|
|
4356
|
+
return bundle;
|
|
4293
4357
|
}
|
|
4294
|
-
return bundle;
|
|
4295
4358
|
})
|
|
4296
4359
|
);
|
|
4297
4360
|
this.bundles = results;
|
|
4298
4361
|
}
|
|
4299
4362
|
async fetchSingleBundle(client, signal) {
|
|
4300
4363
|
const data = await client.query(
|
|
4301
|
-
BUNDLE_METAOBJECT_QUERY,
|
|
4364
|
+
this.wrapQueryForContext(BUNDLE_METAOBJECT_QUERY),
|
|
4302
4365
|
{ id: this.bundleGid },
|
|
4303
4366
|
{ signal }
|
|
4304
4367
|
);
|
|
@@ -4310,11 +4373,11 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
4310
4373
|
data.metaobject.id,
|
|
4311
4374
|
data.metaobject.fields
|
|
4312
4375
|
);
|
|
4313
|
-
this.bundles = parsed ? [parsed] : [];
|
|
4376
|
+
this.bundles = parsed && !this.isMarketHidden(parsed) ? [parsed] : [];
|
|
4314
4377
|
}
|
|
4315
4378
|
async fetchProductBundles(client, signal, productHandle) {
|
|
4316
4379
|
const data = await client.query(
|
|
4317
|
-
BUNDLES_FOR_PRODUCT_QUERY,
|
|
4380
|
+
this.wrapQueryForContext(BUNDLES_FOR_PRODUCT_QUERY),
|
|
4318
4381
|
{ handle: productHandle },
|
|
4319
4382
|
{ signal }
|
|
4320
4383
|
);
|
|
@@ -4326,7 +4389,7 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
4326
4389
|
const bundles = [];
|
|
4327
4390
|
for (const ref of refs) {
|
|
4328
4391
|
const parsed = parseMetaobjectBundle(ref.id, ref.fields);
|
|
4329
|
-
if (parsed) bundles.push(parsed);
|
|
4392
|
+
if (parsed && !this.isMarketHidden(parsed)) bundles.push(parsed);
|
|
4330
4393
|
}
|
|
4331
4394
|
this.bundles = bundles;
|
|
4332
4395
|
}
|