@occluze/tether 0.3.6 → 0.5.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 +12 -7
- package/dist/esm/astro.js +2 -0
- package/dist/esm/chunks/chunk-HHMOPDZG.js +1 -0
- package/dist/esm/chunks/chunk-HXKV6QGB.js +4 -0
- package/dist/esm/chunks/chunk-RBVRRQIU.js +1 -0
- package/dist/esm/chunks/chunk-RNZMTQTX.js +1 -0
- package/dist/esm/chunks/chunk-VC46IEJQ.js +1 -0
- package/dist/esm/chunks/diagnose-J667GHGS.js +1 -0
- package/dist/esm/chunks/markets-ui-EGWIEUOZ.js +1 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/server.js +1 -0
- package/dist/presets/barba.esm.js +4 -5
- package/dist/tether.diagnose.js +320 -0
- package/dist/tether.diagnose.min.js +1 -0
- package/dist/tether.esm.js +2 -2608
- package/dist/tether.js +637 -833
- package/dist/tether.markets.js +213 -0
- package/dist/tether.markets.min.js +1 -0
- package/dist/tether.min.js +3 -132
- package/dist/tether.min.js.map +4 -4
- package/package.json +52 -11
- package/src/astro.js +73 -0
- package/src/cart.js +79 -7
- package/src/client.js +25 -15
- package/src/config.js +4 -0
- package/src/diagnose.js +1 -1
- package/src/dom.js +32 -9
- package/src/fragments.js +12 -11
- package/src/index.js +80 -160
- package/src/initial.js +66 -0
- package/src/lazy-iife.js +43 -0
- package/src/lazy.js +9 -0
- package/src/loader.js +50 -7
- package/src/markets-ui.js +89 -0
- package/src/markets.js +1 -1
- package/src/mounting.js +129 -0
- package/src/parts/diagnose.js +3 -0
- package/src/parts/markets.js +3 -0
- package/src/product.js +86 -61
- package/src/server.js +179 -0
- package/src/status.js +2 -6
- package/types/astro.d.ts +15 -0
- package/types/barba.d.ts +16 -0
- package/types/index.d.ts +347 -0
- package/types/server.d.ts +94 -0
package/dist/tether.js
CHANGED
|
@@ -6,6 +6,16 @@
|
|
|
6
6
|
};
|
|
7
7
|
|
|
8
8
|
// src/config.js
|
|
9
|
+
var config_exports = {};
|
|
10
|
+
__export(config_exports, {
|
|
11
|
+
DEFAULTS: () => DEFAULTS,
|
|
12
|
+
cfg: () => cfg,
|
|
13
|
+
configure: () => configure,
|
|
14
|
+
isConfigured: () => isConfigured,
|
|
15
|
+
log: () => log,
|
|
16
|
+
reportError: () => reportError,
|
|
17
|
+
warn: () => warn
|
|
18
|
+
});
|
|
9
19
|
var DEFAULTS = {
|
|
10
20
|
// Shopify Storefront API
|
|
11
21
|
domain: "",
|
|
@@ -36,11 +46,15 @@
|
|
|
36
46
|
sellingPlansFirst: 25,
|
|
37
47
|
collectionFirst: 24,
|
|
38
48
|
retries: 2,
|
|
49
|
+
revalidate: true,
|
|
50
|
+
// products from the server (tether/server hydrate()): refresh once in the background
|
|
39
51
|
// Cart
|
|
40
52
|
storageKey: null,
|
|
41
53
|
// defaults to `tether:cart:<domain>`
|
|
42
54
|
cartDrawerOpenClass: "is-open",
|
|
43
55
|
openCartOnAdd: true,
|
|
56
|
+
cartFocus: true,
|
|
57
|
+
// move focus into the open cart, keep Tab inside it, restore it on close
|
|
44
58
|
checkoutTarget: "_self",
|
|
45
59
|
// '_self' or '_blank'
|
|
46
60
|
// Subscriptions
|
|
@@ -51,6 +65,10 @@
|
|
|
51
65
|
// mount document on DOMContentLoaded
|
|
52
66
|
observe: false,
|
|
53
67
|
// auto-mount nodes added later (CMS load-more, filters)
|
|
68
|
+
astro: true,
|
|
69
|
+
// mount on Astro View Transitions (astro:page-load); false if you do it yourself
|
|
70
|
+
partsUrl: null,
|
|
71
|
+
// folder of tether.markets/diagnose(.min).js for the <script> build (default: next to it)
|
|
54
72
|
// Formatting
|
|
55
73
|
formatMoney: null,
|
|
56
74
|
// (money, locale) => string
|
|
@@ -98,6 +116,15 @@
|
|
|
98
116
|
}
|
|
99
117
|
|
|
100
118
|
// src/client.js
|
|
119
|
+
var client_exports = {};
|
|
120
|
+
__export(client_exports, {
|
|
121
|
+
TetherError: () => TetherError,
|
|
122
|
+
endpoint: () => endpoint,
|
|
123
|
+
gql: () => gql,
|
|
124
|
+
request: () => request,
|
|
125
|
+
unwrap: () => unwrap,
|
|
126
|
+
withContext: () => withContext
|
|
127
|
+
});
|
|
101
128
|
var TetherError = class extends Error {
|
|
102
129
|
constructor(message, { status, errors, userErrors, code } = {}) {
|
|
103
130
|
super(message);
|
|
@@ -108,40 +135,38 @@
|
|
|
108
135
|
this.userErrors = userErrors;
|
|
109
136
|
}
|
|
110
137
|
};
|
|
111
|
-
function endpoint() {
|
|
112
|
-
return `https://${
|
|
138
|
+
function endpoint(conf = cfg) {
|
|
139
|
+
return `https://${conf.domain}/api/${conf.apiVersion}/graphql.json`;
|
|
113
140
|
}
|
|
114
141
|
function withContext(query, ctx = cfg) {
|
|
115
142
|
const args = [];
|
|
116
143
|
if (ctx.country) args.push(`country: ${String(ctx.country).toUpperCase()}`);
|
|
117
144
|
if (ctx.language) args.push(`language: ${String(ctx.language).toUpperCase().replace("-", "_")}`);
|
|
118
145
|
if (!args.length || /@inContext/.test(query)) return query;
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
);
|
|
146
|
+
const tag = `@inContext(${args.join(", ")})`;
|
|
147
|
+
if (/^\s*\{/.test(query)) return `query ${tag} ${query.trimStart()}`;
|
|
148
|
+
return query.replace(/^(\s*(?:query|mutation)(?:\s+\w+)?\s*(?:\([^)]*\))?)/, `$1 ${tag}`);
|
|
123
149
|
}
|
|
124
150
|
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
125
|
-
async function
|
|
126
|
-
|
|
127
|
-
if (!cfg.domain || !cfg.token) {
|
|
151
|
+
async function request(conf, query, variables = {}, { context = true } = {}) {
|
|
152
|
+
if (!conf.domain || !conf.token) {
|
|
128
153
|
throw new TetherError("Tether is not configured: domain and token are required", { code: "NOT_CONFIGURED" });
|
|
129
154
|
}
|
|
130
|
-
const body = JSON.stringify({ query: context ? withContext(query) : query, variables });
|
|
155
|
+
const body = JSON.stringify({ query: context ? withContext(query, conf) : query, variables });
|
|
131
156
|
let attempt = 0;
|
|
132
157
|
for (; ; ) {
|
|
133
158
|
let res;
|
|
134
159
|
try {
|
|
135
|
-
res = await fetch(endpoint(), {
|
|
160
|
+
res = await (conf.fetch || fetch)(endpoint(conf), {
|
|
136
161
|
method: "POST",
|
|
137
162
|
headers: {
|
|
138
163
|
"Content-Type": "application/json",
|
|
139
|
-
"X-Shopify-Storefront-Access-Token":
|
|
164
|
+
"X-Shopify-Storefront-Access-Token": conf.token
|
|
140
165
|
},
|
|
141
166
|
body
|
|
142
167
|
});
|
|
143
168
|
} catch (err) {
|
|
144
|
-
if (attempt++ <
|
|
169
|
+
if (attempt++ < conf.retries) {
|
|
145
170
|
await sleep(300 * 2 ** attempt);
|
|
146
171
|
continue;
|
|
147
172
|
}
|
|
@@ -149,7 +174,7 @@
|
|
|
149
174
|
}
|
|
150
175
|
if (res.status === 430) throw new TetherError("Shopify security rejection (430)", { status: 430 });
|
|
151
176
|
const retryable = res.status === 429 || res.status >= 500;
|
|
152
|
-
if (retryable && attempt++ <
|
|
177
|
+
if (retryable && attempt++ < conf.retries) {
|
|
153
178
|
log("retry", res.status, attempt);
|
|
154
179
|
await sleep(300 * 2 ** attempt);
|
|
155
180
|
continue;
|
|
@@ -160,17 +185,17 @@
|
|
|
160
185
|
} catch {
|
|
161
186
|
throw new TetherError(`Invalid response (${res.status})`, { status: res.status });
|
|
162
187
|
}
|
|
163
|
-
if (
|
|
188
|
+
if (json.errors?.length) {
|
|
164
189
|
const inventoryDenied = json.errors.some((e) => /quantityAvailable/.test(e.message || ""));
|
|
165
190
|
if (inventoryDenied && /\bquantityAvailable\b/.test(body)) {
|
|
166
191
|
log("no inventory permission, retrying without quantityAvailable");
|
|
167
|
-
return
|
|
192
|
+
return request(conf, query.replace(/\bquantityAvailable\b/g, ""), variables, { context });
|
|
168
193
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
if (throttled && attempt++ <
|
|
194
|
+
if (json.errors.some((e) => /'warnings'/.test(e.message || "")) && /warnings \{ code message \}/.test(query)) {
|
|
195
|
+
return request(conf, query.replace(/ warnings \{ code message \}/g, ""), variables, { context });
|
|
196
|
+
}
|
|
197
|
+
const throttled = json.errors.some((e) => e.extensions?.code === "THROTTLED");
|
|
198
|
+
if (throttled && attempt++ < conf.retries) {
|
|
174
199
|
await sleep(500 * 2 ** attempt);
|
|
175
200
|
continue;
|
|
176
201
|
}
|
|
@@ -183,6 +208,7 @@
|
|
|
183
208
|
return json.data;
|
|
184
209
|
}
|
|
185
210
|
}
|
|
211
|
+
var gql = (query, variables, opts) => request(cfg, query, variables, opts);
|
|
186
212
|
function unwrap(payload, name) {
|
|
187
213
|
if (!payload) throw new TetherError(`${name}: empty response`);
|
|
188
214
|
const errs = payload.userErrors || [];
|
|
@@ -192,11 +218,28 @@
|
|
|
192
218
|
return payload;
|
|
193
219
|
}
|
|
194
220
|
|
|
221
|
+
// src/cart.js
|
|
222
|
+
var cart_exports = {};
|
|
223
|
+
__export(cart_exports, {
|
|
224
|
+
Cart: () => Cart,
|
|
225
|
+
focusables: () => focusables,
|
|
226
|
+
toLineInput: () => toLineInput
|
|
227
|
+
});
|
|
228
|
+
|
|
195
229
|
// src/fragments.js
|
|
230
|
+
var fragments_exports = {};
|
|
231
|
+
__export(fragments_exports, {
|
|
232
|
+
ALLOCATION_FIELDS: () => ALLOCATION_FIELDS,
|
|
233
|
+
SELLING_PLAN_FIELDS: () => SELLING_PLAN_FIELDS,
|
|
234
|
+
cartFragment: () => cartFragment,
|
|
235
|
+
collectionFields: () => collectionFields,
|
|
236
|
+
metafieldIdentifiers: () => metafieldIdentifiers,
|
|
237
|
+
productFragment: () => productFragment
|
|
238
|
+
});
|
|
196
239
|
var MONEY = "amount currencyCode";
|
|
197
240
|
var IMAGE = "url altText width height";
|
|
198
|
-
function metafieldIdentifiers() {
|
|
199
|
-
return (
|
|
241
|
+
function metafieldIdentifiers(o = cfg) {
|
|
242
|
+
return (o.metafields || []).map((m) => {
|
|
200
243
|
if (typeof m === "string") {
|
|
201
244
|
const i = m.indexOf(".");
|
|
202
245
|
return { namespace: m.slice(0, i), key: m.slice(i + 1) };
|
|
@@ -204,116 +247,19 @@
|
|
|
204
247
|
return m;
|
|
205
248
|
}).filter((m) => m.namespace && m.key);
|
|
206
249
|
}
|
|
207
|
-
function metafieldsField() {
|
|
208
|
-
const ids = metafieldIdentifiers();
|
|
250
|
+
function metafieldsField(o = cfg) {
|
|
251
|
+
const ids = metafieldIdentifiers(o);
|
|
209
252
|
if (!ids.length) return "";
|
|
210
253
|
const list = ids.map((m) => `{namespace: ${JSON.stringify(m.namespace)}, key: ${JSON.stringify(m.key)}}`).join(", ");
|
|
211
|
-
return `metafields(identifiers:
|
|
212
|
-
namespace key type value
|
|
213
|
-
reference { ... on MediaImage { image { ${IMAGE} } } }
|
|
214
|
-
}`;
|
|
215
|
-
}
|
|
216
|
-
var SELLING_PLAN_FIELDS = `
|
|
217
|
-
id name description recurringDeliveries
|
|
218
|
-
options { name value }
|
|
219
|
-
priceAdjustments {
|
|
220
|
-
orderCount
|
|
221
|
-
adjustmentValue {
|
|
222
|
-
__typename
|
|
223
|
-
... on SellingPlanPercentagePriceAdjustment { adjustmentPercentage }
|
|
224
|
-
... on SellingPlanFixedAmountPriceAdjustment { adjustmentAmount { ${MONEY} } }
|
|
225
|
-
... on SellingPlanFixedPriceAdjustment { price { ${MONEY} } }
|
|
226
|
-
}
|
|
227
|
-
}`;
|
|
228
|
-
var ALLOCATION_FIELDS = `
|
|
229
|
-
sellingPlan { id }
|
|
230
|
-
checkoutChargeAmount { ${MONEY} }
|
|
231
|
-
remainingBalanceChargeAmount { ${MONEY} }
|
|
232
|
-
priceAdjustments {
|
|
233
|
-
price { ${MONEY} }
|
|
234
|
-
compareAtPrice { ${MONEY} }
|
|
235
|
-
perDeliveryPrice { ${MONEY} }
|
|
236
|
-
unitPrice { ${MONEY} }
|
|
237
|
-
}`;
|
|
238
|
-
function productFragment() {
|
|
239
|
-
return `
|
|
240
|
-
fragment TetherProduct on Product {
|
|
241
|
-
id handle title description descriptionHtml productType vendor tags
|
|
242
|
-
availableForSale publishedAt onlineStoreUrl requiresSellingPlan
|
|
243
|
-
seo { title description }
|
|
244
|
-
featuredImage { ${IMAGE} }
|
|
245
|
-
images(first: ${cfg.imagesFirst}) { nodes { ${IMAGE} } }
|
|
246
|
-
options { name optionValues { name swatch { color image { previewImage { ${IMAGE} } } } } }
|
|
247
|
-
priceRange { minVariantPrice { ${MONEY} } maxVariantPrice { ${MONEY} } }
|
|
248
|
-
compareAtPriceRange { minVariantPrice { ${MONEY} } maxVariantPrice { ${MONEY} } }
|
|
249
|
-
${metafieldsField()}
|
|
250
|
-
sellingPlanGroups(first: ${cfg.sellingPlanGroupsFirst}) {
|
|
251
|
-
nodes {
|
|
252
|
-
name appName
|
|
253
|
-
options { name values }
|
|
254
|
-
sellingPlans(first: ${cfg.sellingPlansFirst}) { nodes { ${SELLING_PLAN_FIELDS} } }
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
variants(first: ${cfg.variantsFirst}) {
|
|
258
|
-
nodes {
|
|
259
|
-
id title availableForSale currentlyNotInStock quantityAvailable sku barcode
|
|
260
|
-
selectedOptions { name value }
|
|
261
|
-
image { ${IMAGE} }
|
|
262
|
-
price { ${MONEY} }
|
|
263
|
-
compareAtPrice { ${MONEY} }
|
|
264
|
-
unitPrice { ${MONEY} }
|
|
265
|
-
sellingPlanAllocations(first: ${cfg.sellingPlansFirst}) { nodes { ${ALLOCATION_FIELDS} } }
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
}`;
|
|
254
|
+
return `metafields(identifiers:[${list}]){namespace key type value reference{... on MediaImage{image{${IMAGE}}}}}`;
|
|
269
255
|
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
attributes { key value }
|
|
275
|
-
buyerIdentity { countryCode email }
|
|
276
|
-
discountCodes { code applicable }
|
|
277
|
-
cost {
|
|
278
|
-
subtotalAmount { ${MONEY} }
|
|
279
|
-
totalAmount { ${MONEY} }
|
|
280
|
-
checkoutChargeAmount { ${MONEY} }
|
|
281
|
-
}
|
|
282
|
-
lines(first: 100) {
|
|
283
|
-
nodes {
|
|
284
|
-
id quantity
|
|
285
|
-
attributes { key value }
|
|
286
|
-
cost {
|
|
287
|
-
totalAmount { ${MONEY} }
|
|
288
|
-
subtotalAmount { ${MONEY} }
|
|
289
|
-
amountPerQuantity { ${MONEY} }
|
|
290
|
-
compareAtAmountPerQuantity { ${MONEY} }
|
|
291
|
-
}
|
|
292
|
-
discountAllocations { discountedAmount { ${MONEY} } }
|
|
293
|
-
sellingPlanAllocation {
|
|
294
|
-
sellingPlan { id name description recurringDeliveries options { name value } }
|
|
295
|
-
checkoutChargeAmount { ${MONEY} }
|
|
296
|
-
priceAdjustments { price { ${MONEY} } compareAtPrice { ${MONEY} } perDeliveryPrice { ${MONEY} } }
|
|
297
|
-
}
|
|
298
|
-
merchandise {
|
|
299
|
-
... on ProductVariant {
|
|
300
|
-
id title sku availableForSale requiresShipping
|
|
301
|
-
image { ${IMAGE} }
|
|
302
|
-
price { ${MONEY} }
|
|
303
|
-
compareAtPrice { ${MONEY} }
|
|
304
|
-
selectedOptions { name value }
|
|
305
|
-
product { id title handle vendor featuredImage { ${IMAGE} } }
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
... on ComponentizableCartLine {
|
|
309
|
-
lineComponents {
|
|
310
|
-
id quantity
|
|
311
|
-
merchandise { ... on ProductVariant { id title product { title handle } } }
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
}
|
|
256
|
+
var SELLING_PLAN_FIELDS = ` id name description recurringDeliveries options{name value}priceAdjustments{orderCount adjustmentValue{__typename ... on SellingPlanPercentagePriceAdjustment{adjustmentPercentage}... on SellingPlanFixedAmountPriceAdjustment{adjustmentAmount{${MONEY}}}... on SellingPlanFixedPriceAdjustment{price{${MONEY}}}}}`;
|
|
257
|
+
var ALLOCATION_FIELDS = ` sellingPlan{id}checkoutChargeAmount{${MONEY}}remainingBalanceChargeAmount{${MONEY}}priceAdjustments{price{${MONEY}}compareAtPrice{${MONEY}}perDeliveryPrice{${MONEY}}unitPrice{${MONEY}}}`;
|
|
258
|
+
function productFragment(o = cfg) {
|
|
259
|
+
return ` fragment TetherProduct on Product{id handle title description descriptionHtml productType vendor tags availableForSale publishedAt onlineStoreUrl requiresSellingPlan seo{title description}featuredImage{${IMAGE}}images(first:${o.imagesFirst}){nodes{${IMAGE}}}options{name optionValues{name swatch{color image{previewImage{${IMAGE}}}}}}priceRange{minVariantPrice{${MONEY}}maxVariantPrice{${MONEY}}}compareAtPriceRange{minVariantPrice{${MONEY}}maxVariantPrice{${MONEY}}}${metafieldsField(o)} sellingPlanGroups(first:${o.sellingPlanGroupsFirst}){nodes{name appName options{name values}sellingPlans(first:${o.sellingPlansFirst}){nodes{${SELLING_PLAN_FIELDS}}}}}variants(first:${o.variantsFirst}){nodes{id title availableForSale currentlyNotInStock quantityAvailable sku barcode selectedOptions{name value}image{${IMAGE}}price{${MONEY}}compareAtPrice{${MONEY}}unitPrice{${MONEY}}sellingPlanAllocations(first:${o.sellingPlansFirst}){nodes{${ALLOCATION_FIELDS}}}}}}`;
|
|
315
260
|
}
|
|
316
|
-
|
|
261
|
+
function cartFragment() {
|
|
262
|
+
return ` fragment TetherCart on Cart{id checkoutUrl totalQuantity note createdAt updatedAt attributes{key value}buyerIdentity{countryCode email}discountCodes{code applicable}cost{subtotalAmount{${MONEY}}totalAmount{${MONEY}}checkoutChargeAmount{${MONEY}}}lines(first:100){nodes{id quantity attributes{key value}cost{totalAmount{${MONEY}}subtotalAmount{${MONEY}}amountPerQuantity{${MONEY}}compareAtAmountPerQuantity{${MONEY}}}discountAllocations{discountedAmount{${MONEY}}}sellingPlanAllocation{sellingPlan{id name description recurringDeliveries options{name value}}checkoutChargeAmount{${MONEY}}priceAdjustments{price{${MONEY}}compareAtPrice{${MONEY}}perDeliveryPrice{${MONEY}}}}merchandise{... on ProductVariant{id title sku availableForSale requiresShipping image{${IMAGE}}price{${MONEY}}compareAtPrice{${MONEY}}selectedOptions{name value}product{id title handle vendor featuredImage{${IMAGE}}}}}... on ComponentizableCartLine{lineComponents{id quantity merchandise{... on ProductVariant{id title product{title handle}}}}}}}}`;
|
|
317
263
|
}
|
|
318
264
|
function collectionFields() {
|
|
319
265
|
return `
|
|
@@ -323,6 +269,18 @@ fragment TetherCart on Cart {
|
|
|
323
269
|
}
|
|
324
270
|
|
|
325
271
|
// src/loader.js
|
|
272
|
+
var loader_exports = {};
|
|
273
|
+
__export(loader_exports, {
|
|
274
|
+
clearProductCache: () => clearProductCache,
|
|
275
|
+
getProduct: () => getProduct,
|
|
276
|
+
getProducts: () => getProducts,
|
|
277
|
+
getRecommendations: () => getRecommendations,
|
|
278
|
+
hydrateProducts: () => hydrateProducts,
|
|
279
|
+
normalizeProductKey: () => normalizeProductKey,
|
|
280
|
+
primeProduct: () => primeProduct,
|
|
281
|
+
refreshProducts: () => refreshProducts,
|
|
282
|
+
takeStale: () => takeStale
|
|
283
|
+
});
|
|
326
284
|
var productCache = /* @__PURE__ */ new Map();
|
|
327
285
|
var pending = [];
|
|
328
286
|
var resolvers = /* @__PURE__ */ new Map();
|
|
@@ -330,7 +288,7 @@ fragment TetherCart on Cart {
|
|
|
330
288
|
var generation = 0;
|
|
331
289
|
var isGid = (v) => typeof v === "string" && v.startsWith("gid://");
|
|
332
290
|
function normalizeProductKey(raw) {
|
|
333
|
-
const v = String(raw
|
|
291
|
+
const v = String(raw ?? "").trim();
|
|
334
292
|
if (!v) return null;
|
|
335
293
|
if (isGid(v)) return v;
|
|
336
294
|
if (/^\d+$/.test(v)) return `gid://shopify/Product/${v}`;
|
|
@@ -366,22 +324,19 @@ fragment TetherCart on Cart {
|
|
|
366
324
|
const ids = keys.filter(isGid);
|
|
367
325
|
const handles = keys.filter((k) => !isGid(k));
|
|
368
326
|
const defs = [];
|
|
369
|
-
const
|
|
327
|
+
const parts2 = [];
|
|
370
328
|
const vars = {};
|
|
371
329
|
if (ids.length) {
|
|
372
330
|
defs.push("$ids: [ID!]!");
|
|
373
331
|
vars.ids = ids;
|
|
374
|
-
|
|
332
|
+
parts2.push("byId: nodes(ids: $ids) { ... on Product { ...TetherProduct } }");
|
|
375
333
|
}
|
|
376
334
|
handles.forEach((h, i) => {
|
|
377
335
|
defs.push(`$h${i}: String!`);
|
|
378
336
|
vars[`h${i}`] = h;
|
|
379
|
-
|
|
337
|
+
parts2.push(`h${i}: product(handle: $h${i}) { ...TetherProduct }`);
|
|
380
338
|
});
|
|
381
|
-
const query = `query TetherProducts(${defs.join(",
|
|
382
|
-
${parts.join("\n ")}
|
|
383
|
-
}
|
|
384
|
-
${productFragment()}`;
|
|
339
|
+
const query = `query TetherProducts(${defs.join(",")}){${parts2.join(" ")}}${productFragment()}`;
|
|
385
340
|
log("fetch products", keys);
|
|
386
341
|
const settle = (k, fn) => {
|
|
387
342
|
const r = resolvers.get(k);
|
|
@@ -399,8 +354,13 @@ ${productFragment()}`;
|
|
|
399
354
|
});
|
|
400
355
|
keys.forEach((k) => settle(k, (r) => {
|
|
401
356
|
if (got.has(k)) {
|
|
402
|
-
|
|
403
|
-
|
|
357
|
+
const p = got.get(k);
|
|
358
|
+
if (gen === generation) {
|
|
359
|
+
primeProduct(p);
|
|
360
|
+
freshAt.set(p.handle, Date.now());
|
|
361
|
+
freshAt.set(p.id, Date.now());
|
|
362
|
+
}
|
|
363
|
+
r.resolve(p);
|
|
404
364
|
} else {
|
|
405
365
|
if (gen === generation) productCache.delete(k);
|
|
406
366
|
r.reject(new TetherError(`Product not found: ${k}`, { code: "NOT_FOUND" }));
|
|
@@ -416,6 +376,35 @@ ${productFragment()}`;
|
|
|
416
376
|
function clearProductCache() {
|
|
417
377
|
productCache.clear();
|
|
418
378
|
generation++;
|
|
379
|
+
stale.clear();
|
|
380
|
+
freshAt.clear();
|
|
381
|
+
}
|
|
382
|
+
var stale = /* @__PURE__ */ new Set();
|
|
383
|
+
var freshAt = /* @__PURE__ */ new Map();
|
|
384
|
+
var FRESH_MS = 6e4;
|
|
385
|
+
function hydrateProducts(products) {
|
|
386
|
+
(products || []).forEach((p) => {
|
|
387
|
+
if (!p?.id || !p.handle) return;
|
|
388
|
+
if (Date.now() - (freshAt.get(p.handle) || 0) < FRESH_MS && productCache.has(p.handle)) return;
|
|
389
|
+
primeProduct(p);
|
|
390
|
+
stale.add(p.handle);
|
|
391
|
+
stale.add(p.id);
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
function takeStale(key) {
|
|
395
|
+
const k = normalizeProductKey(key);
|
|
396
|
+
if (!stale.has(k)) return false;
|
|
397
|
+
const p = productCache.get(k);
|
|
398
|
+
stale.delete(k);
|
|
399
|
+
p?.then?.((x) => {
|
|
400
|
+
stale.delete(x.handle);
|
|
401
|
+
stale.delete(x.id);
|
|
402
|
+
});
|
|
403
|
+
return true;
|
|
404
|
+
}
|
|
405
|
+
function refreshProducts(keys) {
|
|
406
|
+
keys.forEach((k) => productCache.delete(normalizeProductKey(k)));
|
|
407
|
+
return keys.map((k) => getProduct(k));
|
|
419
408
|
}
|
|
420
409
|
async function getProducts({
|
|
421
410
|
collection,
|
|
@@ -428,16 +417,7 @@ ${productFragment()}`;
|
|
|
428
417
|
} = {}) {
|
|
429
418
|
const fragment = productFragment();
|
|
430
419
|
if (collection) {
|
|
431
|
-
const data2 = await gql(`query TetherCollection($handle:
|
|
432
|
-
collection(handle: $handle) {
|
|
433
|
-
${collectionFields()}
|
|
434
|
-
products(first: $first, after: $after, sortKey: $sortKey, reverse: $reverse, filters: $filters) {
|
|
435
|
-
nodes { ...TetherProduct }
|
|
436
|
-
pageInfo { hasNextPage endCursor }
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
${fragment}`, {
|
|
420
|
+
const data2 = await gql(`query TetherCollection($handle:String!,$first:Int!,$after:String,$sortKey:ProductCollectionSortKeys,$reverse:Boolean,$filters:[ProductFilter!]){collection(handle:$handle){${collectionFields()} products(first:$first,after:$after,sortKey:$sortKey,reverse:$reverse,filters:$filters){nodes{...TetherProduct}pageInfo{hasNextPage endCursor}}}}${fragment}`, {
|
|
441
421
|
handle: collection,
|
|
442
422
|
first,
|
|
443
423
|
after,
|
|
@@ -450,21 +430,14 @@ ${fragment}`, {
|
|
|
450
430
|
products.nodes.forEach(primeProduct);
|
|
451
431
|
return { collection: rest, products: products.nodes, pageInfo: products.pageInfo };
|
|
452
432
|
}
|
|
453
|
-
const data = await gql(`query TetherSearch($first:
|
|
454
|
-
products(first: $first, after: $after, query: $query, sortKey: $sortKey, reverse: $reverse) {
|
|
455
|
-
nodes { ...TetherProduct }
|
|
456
|
-
pageInfo { hasNextPage endCursor }
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
${fragment}`, { first, after, query: query || void 0, sortKey: sortKey || void 0, reverse });
|
|
433
|
+
const data = await gql(`query TetherSearch($first:Int!,$after:String,$query:String,$sortKey:ProductSortKeys,$reverse:Boolean){products(first:$first,after:$after,query:$query,sortKey:$sortKey,reverse:$reverse){nodes{...TetherProduct}pageInfo{hasNextPage endCursor}}}${fragment}`, { first, after, query: query || void 0, sortKey: sortKey || void 0, reverse });
|
|
460
434
|
data.products.nodes.forEach(primeProduct);
|
|
461
435
|
return { collection: null, products: data.products.nodes, pageInfo: data.products.pageInfo };
|
|
462
436
|
}
|
|
463
|
-
async function getRecommendations(
|
|
464
|
-
const
|
|
465
|
-
|
|
466
|
-
}
|
|
467
|
-
${productFragment()}`, { id: productId, intent });
|
|
437
|
+
async function getRecommendations(product, intent = "RELATED") {
|
|
438
|
+
const key = normalizeProductKey(typeof product === "object" ? product?.id : product);
|
|
439
|
+
const byId = key?.startsWith("gid://");
|
|
440
|
+
const data = await gql(`query TetherRecs(${byId ? "$id: ID!" : "$handle: String!"},$intent:ProductRecommendationIntent){productRecommendations(${byId ? "productId: $id" : "productHandle: $handle"},intent:$intent){...TetherProduct}}${productFragment()}`, byId ? { id: key, intent } : { handle: key, intent });
|
|
468
441
|
const list = data.productRecommendations || [];
|
|
469
442
|
list.forEach(primeProduct);
|
|
470
443
|
return list;
|
|
@@ -491,49 +464,38 @@ ${productFragment()}`, { id: productId, intent });
|
|
|
491
464
|
sellingPlan: (v) => toGid(v, "SellingPlan")
|
|
492
465
|
};
|
|
493
466
|
function toGid(value, type) {
|
|
494
|
-
const v = String(value
|
|
467
|
+
const v = String(value ?? "").trim();
|
|
495
468
|
if (!v) return null;
|
|
496
469
|
if (v.startsWith("gid://")) return v;
|
|
497
470
|
if (/^\d+$/.test(v)) return `gid://shopify/${type}/${v}`;
|
|
498
471
|
return v;
|
|
499
472
|
}
|
|
500
473
|
function allPlans(product) {
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
return groups.flatMap((g) => {
|
|
504
|
-
var _a2;
|
|
505
|
-
return (((_a2 = g.sellingPlans) == null ? void 0 : _a2.nodes) || []).map((p) => ({ ...p, group: g }));
|
|
506
|
-
});
|
|
474
|
+
const groups = product?.sellingPlanGroups?.nodes || [];
|
|
475
|
+
return groups.flatMap((g) => (g.sellingPlans?.nodes || []).map((p) => ({ ...p, group: g })));
|
|
507
476
|
}
|
|
508
477
|
function hasPlans(product) {
|
|
509
478
|
return allPlans(product).length > 0;
|
|
510
479
|
}
|
|
511
480
|
function allocationFor(variant, planId) {
|
|
512
|
-
var _a;
|
|
513
481
|
if (!variant || !planId) return null;
|
|
514
|
-
return (
|
|
515
|
-
var _a2;
|
|
516
|
-
return ((_a2 = a.sellingPlan) == null ? void 0 : _a2.id) === planId;
|
|
517
|
-
}) || null;
|
|
482
|
+
return (variant.sellingPlanAllocations?.nodes || []).find((a) => a.sellingPlan?.id === planId) || null;
|
|
518
483
|
}
|
|
519
484
|
function plansForVariant(product, variant) {
|
|
520
485
|
return allPlans(product).filter((p) => allocationFor(variant, p.id));
|
|
521
486
|
}
|
|
522
487
|
function planPrice(variant, planId) {
|
|
523
|
-
var _a;
|
|
524
488
|
const a = allocationFor(variant, planId);
|
|
525
|
-
const adj =
|
|
526
|
-
return
|
|
489
|
+
const adj = a?.priceAdjustments?.[0];
|
|
490
|
+
return adj?.perDeliveryPrice || adj?.price || variant?.price || null;
|
|
527
491
|
}
|
|
528
492
|
function planCheckoutCharge(variant, planId) {
|
|
529
|
-
var _a, _b;
|
|
530
493
|
const a = allocationFor(variant, planId);
|
|
531
|
-
return
|
|
494
|
+
return a?.checkoutChargeAmount || a?.priceAdjustments?.[0]?.price || variant?.price || null;
|
|
532
495
|
}
|
|
533
496
|
function planSavings(variant, planId) {
|
|
534
|
-
|
|
535
|
-
const
|
|
536
|
-
const sub = parseFloat((_b = planPrice(variant, planId)) == null ? void 0 : _b.amount);
|
|
497
|
+
const base = parseFloat(variant?.price?.amount);
|
|
498
|
+
const sub = parseFloat(planPrice(variant, planId)?.amount);
|
|
537
499
|
if (!Number.isFinite(base) || !Number.isFinite(sub) || base <= 0 || sub >= base) {
|
|
538
500
|
return { amount: null, percentage: 0 };
|
|
539
501
|
}
|
|
@@ -543,8 +505,7 @@ ${productFragment()}`, { id: productId, intent });
|
|
|
543
505
|
};
|
|
544
506
|
}
|
|
545
507
|
function planAdjustment(plan) {
|
|
546
|
-
|
|
547
|
-
const v = (_b = (_a = plan == null ? void 0 : plan.priceAdjustments) == null ? void 0 : _a[0]) == null ? void 0 : _b.adjustmentValue;
|
|
508
|
+
const v = plan?.priceAdjustments?.[0]?.adjustmentValue;
|
|
548
509
|
if (!v) return null;
|
|
549
510
|
switch (v.__typename) {
|
|
550
511
|
case "SellingPlanPercentagePriceAdjustment":
|
|
@@ -566,6 +527,37 @@ ${productFragment()}`, { id: productId, intent });
|
|
|
566
527
|
}
|
|
567
528
|
|
|
568
529
|
// src/dom.js
|
|
530
|
+
var dom_exports = {};
|
|
531
|
+
__export(dom_exports, {
|
|
532
|
+
attr: () => attr,
|
|
533
|
+
busy: () => busy,
|
|
534
|
+
cdnUrl: () => cdnUrl,
|
|
535
|
+
dataKey: () => dataKey,
|
|
536
|
+
emit: () => emit,
|
|
537
|
+
formatDate: () => formatDate,
|
|
538
|
+
formatMoney: () => formatMoney,
|
|
539
|
+
get: () => get,
|
|
540
|
+
has: () => has,
|
|
541
|
+
hasTemplate: () => hasTemplate,
|
|
542
|
+
imageUrl: () => imageUrl,
|
|
543
|
+
notifyLayoutChanged: () => notifyLayoutChanged,
|
|
544
|
+
parseJSONAttr: () => parseJSONAttr,
|
|
545
|
+
productUrl: () => productUrl,
|
|
546
|
+
qsa: () => qsa,
|
|
547
|
+
readText: () => readText,
|
|
548
|
+
repeat: () => repeat,
|
|
549
|
+
repeatKeyed: () => repeatKeyed,
|
|
550
|
+
sameImage: () => sameImage,
|
|
551
|
+
scoped: () => scoped,
|
|
552
|
+
sel: () => sel,
|
|
553
|
+
setImage: () => setImage,
|
|
554
|
+
setSelected: () => setSelected,
|
|
555
|
+
setText: () => setText,
|
|
556
|
+
setTextTracked: () => setTextTracked,
|
|
557
|
+
show: () => show,
|
|
558
|
+
toAttributes: () => toAttributes,
|
|
559
|
+
watchImages: () => watchImages
|
|
560
|
+
});
|
|
569
561
|
var attr = (name) => `${cfg.prefix}-${name}`;
|
|
570
562
|
var sel = (name, value) => value === void 0 ? `[${attr(name)}]` : `[${attr(name)}="${value}"]`;
|
|
571
563
|
var get = (el, name) => el.getAttribute(attr(name));
|
|
@@ -604,25 +596,31 @@ ${productFragment()}`, { id: productId, intent });
|
|
|
604
596
|
return "";
|
|
605
597
|
}
|
|
606
598
|
}
|
|
607
|
-
function
|
|
608
|
-
if (!url
|
|
609
|
-
const w = el.getAttribute("max-width");
|
|
610
|
-
const h = el.getAttribute("max-height");
|
|
611
|
-
const crop = el.getAttribute("crop");
|
|
612
|
-
if (!w && !h) return url;
|
|
599
|
+
function imageUrl(url, { width, height, crop } = {}) {
|
|
600
|
+
if (!url) return url;
|
|
613
601
|
try {
|
|
614
602
|
const u = new URL(url);
|
|
615
|
-
if (
|
|
616
|
-
if (
|
|
603
|
+
if (width) u.searchParams.set("width", String(width));
|
|
604
|
+
if (height) u.searchParams.set("height", String(height));
|
|
617
605
|
if (crop) u.searchParams.set("crop", crop);
|
|
618
606
|
return u.toString();
|
|
619
607
|
} catch {
|
|
620
608
|
return url;
|
|
621
609
|
}
|
|
622
610
|
}
|
|
611
|
+
function cdnUrl(url, el) {
|
|
612
|
+
if (!url || !el) return url;
|
|
613
|
+
const g = (k) => el.getAttribute(k);
|
|
614
|
+
return imageUrl(url, { width: g("max-width"), height: g("max-height"), crop: g("max-width") || g("max-height") ? g("crop") : null });
|
|
615
|
+
}
|
|
616
|
+
var sameImage = (a, b) => Boolean(a && b) && a.split("?")[0] === b.split("?")[0];
|
|
623
617
|
function setImage(el, url, alt) {
|
|
624
618
|
if (!url) return;
|
|
625
619
|
const src = cdnUrl(url, el);
|
|
620
|
+
if (el.tagName === "IMG" && sameImage(el.getAttribute("src"), src)) {
|
|
621
|
+
if (alt != null && !el.getAttribute("alt")) el.setAttribute("alt", alt);
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
626
624
|
if (el.tagName === "IMG") {
|
|
627
625
|
el.removeAttribute("srcset");
|
|
628
626
|
el.removeAttribute("sizes");
|
|
@@ -654,31 +652,36 @@ ${productFragment()}`, { id: productId, intent });
|
|
|
654
652
|
function show(el, visible) {
|
|
655
653
|
const k = "tetherPrevDisplay";
|
|
656
654
|
if (visible) {
|
|
655
|
+
if (el.dataset.tetherShown !== void 0) return;
|
|
656
|
+
el.dataset.tetherShown = "";
|
|
657
|
+
el.removeAttribute("hidden");
|
|
657
658
|
if (el.dataset[k] !== void 0) {
|
|
658
659
|
el.style.display = el.dataset[k];
|
|
659
660
|
delete el.dataset[k];
|
|
660
661
|
} else if (el.style.display === "none") {
|
|
661
662
|
el.style.display = "";
|
|
662
663
|
}
|
|
664
|
+
if (typeof getComputedStyle === "function" && getComputedStyle(el).display === "none") {
|
|
665
|
+
el.style.display = el.getAttribute(`${cfg.prefix}-display`) || "block";
|
|
666
|
+
}
|
|
663
667
|
} else {
|
|
664
|
-
|
|
668
|
+
delete el.dataset.tetherShown;
|
|
669
|
+
if (el.dataset[k] === void 0 && el.style.display !== "none") el.dataset[k] = el.style.display || "";
|
|
665
670
|
el.style.display = "none";
|
|
666
671
|
}
|
|
667
672
|
}
|
|
668
673
|
function setSelected(el, selected) {
|
|
669
|
-
var _a;
|
|
670
674
|
el.classList.toggle("is-selected", selected);
|
|
671
675
|
el.setAttribute("aria-pressed", selected ? "true" : "false");
|
|
672
676
|
if (el.tagName === "INPUT" && (el.type === "radio" || el.type === "checkbox")) el.checked = selected;
|
|
673
|
-
const input =
|
|
677
|
+
const input = el.querySelector?.('input[type="radio"],input[type="checkbox"]');
|
|
674
678
|
if (input) input.checked = selected;
|
|
675
679
|
}
|
|
676
680
|
var templates = /* @__PURE__ */ new WeakMap();
|
|
677
681
|
var hasTemplate = (container) => Boolean(templates.get(container));
|
|
678
682
|
function template(container) {
|
|
679
|
-
var _a, _b;
|
|
680
683
|
if (!templates.has(container)) {
|
|
681
|
-
const tpl =
|
|
684
|
+
const tpl = container.firstElementChild?.cloneNode(true) ?? null;
|
|
682
685
|
templates.set(container, tpl);
|
|
683
686
|
container.innerHTML = "";
|
|
684
687
|
}
|
|
@@ -760,9 +763,8 @@ ${productFragment()}`, { id: productId, intent });
|
|
|
760
763
|
function notifyLayoutChanged(reason) {
|
|
761
764
|
clearTimeout(layoutTimer);
|
|
762
765
|
layoutTimer = setTimeout(() => {
|
|
763
|
-
var _a;
|
|
764
766
|
if (typeof cfg.onLayoutChange === "function") cfg.onLayoutChange(reason);
|
|
765
|
-
else if (
|
|
767
|
+
else if (window.ScrollTrigger?.refresh) window.ScrollTrigger.refresh();
|
|
766
768
|
emit("layout-changed", { reason });
|
|
767
769
|
}, 120);
|
|
768
770
|
}
|
|
@@ -795,20 +797,16 @@ ${productFragment()}`, { id: productId, intent });
|
|
|
795
797
|
}
|
|
796
798
|
function toAttributes(obj) {
|
|
797
799
|
if (!obj) return [];
|
|
798
|
-
if (Array.isArray(obj)) return obj.map((a) => {
|
|
799
|
-
|
|
800
|
-
return { key: String(a.key), value: String((_a = a.value) != null ? _a : "") };
|
|
801
|
-
});
|
|
802
|
-
return Object.entries(obj).map(([key, value]) => ({ key, value: String(value != null ? value : "") }));
|
|
800
|
+
if (Array.isArray(obj)) return obj.map((a) => ({ key: String(a.key), value: String(a.value ?? "") }));
|
|
801
|
+
return Object.entries(obj).map(([key, value]) => ({ key, value: String(value ?? "") }));
|
|
803
802
|
}
|
|
804
803
|
|
|
805
804
|
// src/cart.js
|
|
806
805
|
function toLineInput(input, quantity = 1, opts = {}) {
|
|
807
|
-
var _a;
|
|
808
806
|
const src = typeof input === "object" && input !== null ? input : { variantId: input, quantity, ...opts };
|
|
809
807
|
const merchandiseId = GID.variant(src.merchandiseId || src.variantId || src.id);
|
|
810
808
|
if (!merchandiseId) throw new TetherError("add(): variantId is required");
|
|
811
|
-
const line = { merchandiseId, quantity: Math.max(1, parseInt(
|
|
809
|
+
const line = { merchandiseId, quantity: Math.max(1, parseInt(src.quantity ?? 1, 10) || 1) };
|
|
812
810
|
const plan = src.sellingPlanId || src.sellingPlan;
|
|
813
811
|
if (plan) line.sellingPlanId = GID.sellingPlan(plan);
|
|
814
812
|
const attrs = toAttributes(src.attributes);
|
|
@@ -816,12 +814,21 @@ ${productFragment()}`, { id: productId, intent });
|
|
|
816
814
|
return line;
|
|
817
815
|
}
|
|
818
816
|
function mutation(name, args, inner) {
|
|
819
|
-
return `mutation Tether_${name}(${args}) {
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
817
|
+
return `mutation Tether_${name}(${args}){${name}(${inner}){cart{...TetherCart}userErrors{field message}warnings{code message}}}${cartFragment()}`;
|
|
818
|
+
}
|
|
819
|
+
var FOCUSABLE = 'a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])';
|
|
820
|
+
var returnFocus = null;
|
|
821
|
+
var focusables = (d) => qsa(d, FOCUSABLE).filter((el) => el.offsetParent !== null || el === document.activeElement);
|
|
822
|
+
function ariaState(open) {
|
|
823
|
+
qsa(document, sel("cart-open")).forEach((b) => b.setAttribute("aria-expanded", String(open)));
|
|
824
|
+
qsa(document, sel("cart-drawer")).forEach((d) => {
|
|
825
|
+
if (!d.hasAttribute("role")) d.setAttribute("role", "dialog");
|
|
826
|
+
if (!d.hasAttribute("aria-label") && !d.hasAttribute("aria-labelledby")) d.setAttribute("aria-label", get(d, "cart-label") || "Cart");
|
|
827
|
+
d.setAttribute("aria-modal", String(open));
|
|
828
|
+
if (!d.classList.contains(cfg.cartDrawerOpenClass)) d.setAttribute("aria-hidden", "true");
|
|
829
|
+
});
|
|
823
830
|
}
|
|
824
|
-
var isMissingCart = (err) => /does not exist|not found|invalid id/i.test(
|
|
831
|
+
var isMissingCart = (err) => /does not exist|not found|invalid id/i.test(err?.message || "");
|
|
825
832
|
var Cart = {
|
|
826
833
|
state: null,
|
|
827
834
|
_chain: Promise.resolve(),
|
|
@@ -840,14 +847,22 @@ ${cartFragment()}`;
|
|
|
840
847
|
}
|
|
841
848
|
},
|
|
842
849
|
get lines() {
|
|
843
|
-
|
|
844
|
-
return ((_b = (_a = this.state) == null ? void 0 : _a.lines) == null ? void 0 : _b.nodes) || [];
|
|
850
|
+
return this.state?.lines?.nodes || [];
|
|
845
851
|
},
|
|
846
852
|
get count() {
|
|
847
|
-
|
|
848
|
-
return ((_a = this.state) == null ? void 0 : _a.totalQuantity) || 0;
|
|
853
|
+
return this.state?.totalQuantity || 0;
|
|
849
854
|
},
|
|
850
855
|
lastError: null,
|
|
856
|
+
/** Shopify's notes on the last change, e.g. less stock than asked: [{ code, message }]. */
|
|
857
|
+
warnings: [],
|
|
858
|
+
/** true once the saved cart (or the absence of one) is known. */
|
|
859
|
+
loaded: false,
|
|
860
|
+
_take(payload, name) {
|
|
861
|
+
const p = unwrap(payload, name);
|
|
862
|
+
this.warnings = p.warnings || [];
|
|
863
|
+
if (this.warnings.length) emit("cart-warning", { warnings: this.warnings });
|
|
864
|
+
return p.cart;
|
|
865
|
+
},
|
|
851
866
|
_queue(fn) {
|
|
852
867
|
const run = this._chain.then(fn, fn).then((value) => {
|
|
853
868
|
if (this.lastError) {
|
|
@@ -867,7 +882,7 @@ ${cartFragment()}`;
|
|
|
867
882
|
},
|
|
868
883
|
_commit(cart, reason) {
|
|
869
884
|
this.state = cart;
|
|
870
|
-
if (cart
|
|
885
|
+
if (cart?.id) this._setId(cart.id);
|
|
871
886
|
this.render();
|
|
872
887
|
emit("cart-updated", { cart, reason });
|
|
873
888
|
return cart;
|
|
@@ -876,12 +891,13 @@ ${cartFragment()}`;
|
|
|
876
891
|
const id = this.id();
|
|
877
892
|
if (!id) {
|
|
878
893
|
this.state = null;
|
|
894
|
+
this.loaded = true;
|
|
879
895
|
this.render();
|
|
896
|
+
emit("cart-updated", { cart: null, reason: "load" });
|
|
880
897
|
return null;
|
|
881
898
|
}
|
|
882
899
|
try {
|
|
883
|
-
const data = await gql(`query TetherCart($id:
|
|
884
|
-
${cartFragment()}`, { id });
|
|
900
|
+
const data = await gql(`query TetherCart($id:ID!){cart(id:$id){...TetherCart}}${cartFragment()}`, { id });
|
|
885
901
|
if (!data.cart) {
|
|
886
902
|
this._setId(null);
|
|
887
903
|
this.state = null;
|
|
@@ -892,7 +908,9 @@ ${cartFragment()}`, { id });
|
|
|
892
908
|
if (isMissingCart(err)) this._setId(null);
|
|
893
909
|
this.state = null;
|
|
894
910
|
}
|
|
911
|
+
this.loaded = true;
|
|
895
912
|
this.render();
|
|
913
|
+
emit("cart-updated", { cart: this.state, reason: "load" });
|
|
896
914
|
return this.state;
|
|
897
915
|
},
|
|
898
916
|
async _create(input = {}) {
|
|
@@ -901,7 +919,7 @@ ${cartFragment()}`, { id });
|
|
|
901
919
|
mutation("cartCreate", "$input: CartInput!", "input: $input"),
|
|
902
920
|
{ input: { buyerIdentity, ...input } }
|
|
903
921
|
);
|
|
904
|
-
return
|
|
922
|
+
return this._take(data.cartCreate, "cartCreate");
|
|
905
923
|
},
|
|
906
924
|
/** Runs a cart mutation; creates a fresh cart if ours expired. */
|
|
907
925
|
async _mutate(name, args, inner, variables, createInput) {
|
|
@@ -909,7 +927,7 @@ ${cartFragment()}`, { id });
|
|
|
909
927
|
if (!cartId) return createInput ? this._create(createInput) : null;
|
|
910
928
|
try {
|
|
911
929
|
const data = await gql(mutation(name, `$cartId: ID!, ${args}`, `cartId: $cartId, ${inner}`), { ...variables, cartId });
|
|
912
|
-
return
|
|
930
|
+
return this._take(data[name], name);
|
|
913
931
|
} catch (err) {
|
|
914
932
|
if (createInput && isMissingCart(err)) {
|
|
915
933
|
log("cart expired, creating a new one");
|
|
@@ -966,14 +984,13 @@ ${cartFragment()}`, { id });
|
|
|
966
984
|
},
|
|
967
985
|
setNote(note) {
|
|
968
986
|
return this._queue(async () => this._commit(
|
|
969
|
-
await this._mutate("cartNoteUpdate", "$note: String!", "note: $note", { note: note
|
|
987
|
+
await this._mutate("cartNoteUpdate", "$note: String!", "note: $note", { note: note ?? "" }, { note: note ?? "" }),
|
|
970
988
|
"note"
|
|
971
989
|
));
|
|
972
990
|
},
|
|
973
991
|
/** Cart-level attributes, merged with existing ones. Value '' removes a key. */
|
|
974
992
|
setAttributes(obj) {
|
|
975
|
-
|
|
976
|
-
const merged = new Map((((_a = this.state) == null ? void 0 : _a.attributes) || []).map((a) => [a.key, a.value]));
|
|
993
|
+
const merged = new Map((this.state?.attributes || []).map((a) => [a.key, a.value]));
|
|
977
994
|
toAttributes(obj).forEach((a) => merged.set(a.key, a.value));
|
|
978
995
|
const attributes = [...merged].filter(([, v]) => v !== "").map(([key, value]) => ({ key, value }));
|
|
979
996
|
return this._queue(async () => this._commit(
|
|
@@ -990,13 +1007,12 @@ ${cartFragment()}`, { id });
|
|
|
990
1007
|
));
|
|
991
1008
|
},
|
|
992
1009
|
applyDiscount(code) {
|
|
993
|
-
|
|
994
|
-
const current = (((_a = this.state) == null ? void 0 : _a.discountCodes) || []).map((d) => d.code);
|
|
1010
|
+
const current = (this.state?.discountCodes || []).map((d) => d.code);
|
|
995
1011
|
return this.setDiscountCodes([.../* @__PURE__ */ new Set([...current, code])]);
|
|
996
1012
|
},
|
|
997
1013
|
removeDiscount(code) {
|
|
998
|
-
|
|
999
|
-
return this.setDiscountCodes((
|
|
1014
|
+
const k = String(code).toLowerCase();
|
|
1015
|
+
return this.setDiscountCodes((this.state?.discountCodes || []).map((d) => d.code).filter((c) => c.toLowerCase() !== k));
|
|
1000
1016
|
},
|
|
1001
1017
|
/** { countryCode, email, phone } — changes currency/prices for that market. */
|
|
1002
1018
|
setBuyerIdentity(buyerIdentity, { create = true } = {}) {
|
|
@@ -1015,9 +1031,8 @@ ${cartFragment()}`, { id });
|
|
|
1015
1031
|
* URL once we have it.
|
|
1016
1032
|
*/
|
|
1017
1033
|
async checkout(target = cfg.checkoutTarget) {
|
|
1018
|
-
var _a;
|
|
1019
1034
|
const id = this.id();
|
|
1020
|
-
if (!id) return null;
|
|
1035
|
+
if (!id || this.state && !this.count) return null;
|
|
1021
1036
|
const tab = target === "_blank" ? window.open("", "_blank") : null;
|
|
1022
1037
|
if (tab) {
|
|
1023
1038
|
try {
|
|
@@ -1027,7 +1042,7 @@ ${cartFragment()}`, { id });
|
|
|
1027
1042
|
}
|
|
1028
1043
|
try {
|
|
1029
1044
|
const data = await gql("query TetherCheckout($id: ID!) { cart(id: $id) { checkoutUrl } }", { id });
|
|
1030
|
-
const url =
|
|
1045
|
+
const url = data.cart?.checkoutUrl;
|
|
1031
1046
|
if (!url) throw new TetherError("No checkoutUrl (cart empty or expired)");
|
|
1032
1047
|
emit("checkout", { url, cart: this.state });
|
|
1033
1048
|
if (tab && !tab.closed) tab.location.href = url;
|
|
@@ -1053,16 +1068,29 @@ ${cartFragment()}`, { id });
|
|
|
1053
1068
|
d.classList.add(cfg.cartDrawerOpenClass);
|
|
1054
1069
|
d.setAttribute("aria-hidden", "false");
|
|
1055
1070
|
});
|
|
1071
|
+
ariaState(true);
|
|
1072
|
+
if (cfg.cartFocus !== false && ds[0]) {
|
|
1073
|
+
returnFocus = document.activeElement;
|
|
1074
|
+
setTimeout(() => {
|
|
1075
|
+
const target = focusables(ds[0])[0] || ds[0];
|
|
1076
|
+
if (target === ds[0] && !ds[0].hasAttribute("tabindex")) ds[0].tabIndex = -1;
|
|
1077
|
+
target.focus?.({ preventScroll: true });
|
|
1078
|
+
}, 60);
|
|
1079
|
+
}
|
|
1056
1080
|
if (typeof cfg.onCartOpen === "function") cfg.onCartOpen(ds);
|
|
1057
1081
|
emit("cart-toggled", { open: true });
|
|
1058
1082
|
},
|
|
1059
1083
|
close() {
|
|
1060
1084
|
if (!this.isOpen()) return;
|
|
1061
1085
|
const ds = this.drawers();
|
|
1086
|
+
const hadFocus = ds.some((d) => d.contains(document.activeElement));
|
|
1062
1087
|
ds.forEach((d) => {
|
|
1063
1088
|
d.classList.remove(cfg.cartDrawerOpenClass);
|
|
1064
1089
|
d.setAttribute("aria-hidden", "true");
|
|
1065
1090
|
});
|
|
1091
|
+
ariaState(false);
|
|
1092
|
+
if (hadFocus && returnFocus?.isConnected) returnFocus.focus({ preventScroll: true });
|
|
1093
|
+
returnFocus = null;
|
|
1066
1094
|
if (typeof cfg.onCartClose === "function") cfg.onCartClose(ds);
|
|
1067
1095
|
emit("cart-toggled", { open: false });
|
|
1068
1096
|
},
|
|
@@ -1070,14 +1098,23 @@ ${cartFragment()}`, { id });
|
|
|
1070
1098
|
if (this.isOpen()) this.close();
|
|
1071
1099
|
else this.open();
|
|
1072
1100
|
},
|
|
1101
|
+
/**
|
|
1102
|
+
* For framework islands (React, Svelte, Vue): fn(cart, { open }) now and on every change.
|
|
1103
|
+
* Returns a function that stops listening.
|
|
1104
|
+
*/
|
|
1105
|
+
subscribe(fn) {
|
|
1106
|
+
const call = () => fn(this.state, { open: this.isOpen(), count: this.count, loaded: this.loaded, warnings: this.warnings });
|
|
1107
|
+
const h = () => call();
|
|
1108
|
+
["cart-updated", "cart-toggled"].forEach((e) => document.addEventListener(`${cfg.prefix}:${e}`, h));
|
|
1109
|
+
call();
|
|
1110
|
+
return () => ["cart-updated", "cart-toggled"].forEach((e) => document.removeEventListener(`${cfg.prefix}:${e}`, h));
|
|
1111
|
+
},
|
|
1073
1112
|
// -------------------------------------------------------------- render
|
|
1074
1113
|
render() {
|
|
1075
1114
|
const cart = this.state;
|
|
1076
1115
|
const count = cart ? cart.totalQuantity : 0;
|
|
1077
|
-
const money = (k) =>
|
|
1078
|
-
|
|
1079
|
-
return cart ? formatMoney((_a = cart.cost) == null ? void 0 : _a[k]) : "";
|
|
1080
|
-
};
|
|
1116
|
+
const money = (k) => cart ? formatMoney(cart.cost?.[k]) : "";
|
|
1117
|
+
ariaState(this.isOpen());
|
|
1081
1118
|
qsa(document, sel("cart-count")).forEach((el) => setTextTracked(el, count));
|
|
1082
1119
|
qsa(document, sel("cart-subtotal")).forEach((el) => setTextTracked(el, money("subtotalAmount")));
|
|
1083
1120
|
qsa(document, sel("cart-total")).forEach((el) => setTextTracked(el, money("totalAmount")));
|
|
@@ -1088,18 +1125,24 @@ ${cartFragment()}`, { id });
|
|
|
1088
1125
|
"cart-filled": count > 0,
|
|
1089
1126
|
"cart-has-subscription": hasSub,
|
|
1090
1127
|
"cart-no-subscription": !hasSub,
|
|
1091
|
-
"cart-has-discount": (
|
|
1092
|
-
"cart-error": Boolean(this.lastError)
|
|
1128
|
+
"cart-has-discount": (cart?.discountCodes || []).some((d) => d.applicable),
|
|
1129
|
+
"cart-error": Boolean(this.lastError),
|
|
1130
|
+
"cart-warning": this.warnings.length > 0
|
|
1093
1131
|
};
|
|
1132
|
+
qsa(document, sel("cart-warning")).forEach((el) => setTextTracked(el, [...new Set(this.warnings.map((w) => w.message))].join(" ")));
|
|
1094
1133
|
qsa(document, sel("show")).forEach((el) => {
|
|
1095
1134
|
const k = get(el, "show");
|
|
1096
1135
|
if (k in conds && !el.closest(sel("product"))) show(el, conds[k]);
|
|
1097
1136
|
});
|
|
1137
|
+
qsa(document, sel("checkout")).forEach((el) => {
|
|
1138
|
+
el.classList.toggle("is-disabled", !count);
|
|
1139
|
+
el.setAttribute("aria-disabled", String(!count));
|
|
1140
|
+
});
|
|
1098
1141
|
qsa(document, `textarea${sel("cart-note")},input${sel("cart-note")}`).forEach((el) => {
|
|
1099
|
-
if (document.activeElement !== el) el.value =
|
|
1142
|
+
if (document.activeElement !== el) el.value = cart?.note || "";
|
|
1100
1143
|
});
|
|
1101
1144
|
qsa(document, sel("cart-discounts")).forEach((container) => {
|
|
1102
|
-
repeat(container,
|
|
1145
|
+
repeat(container, cart?.discountCodes || [], (node, d) => {
|
|
1103
1146
|
node.setAttribute(attr("discount-code"), d.code);
|
|
1104
1147
|
const t = node.querySelector(sel("discount", "code")) || (node.firstElementChild ? null : node);
|
|
1105
1148
|
if (t) setText(t, d.code);
|
|
@@ -1116,60 +1159,62 @@ ${cartFragment()}`, { id });
|
|
|
1116
1159
|
}
|
|
1117
1160
|
};
|
|
1118
1161
|
function lineValue(line, key) {
|
|
1119
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
1120
1162
|
const m = line.merchandise || {};
|
|
1121
|
-
const plan =
|
|
1163
|
+
const plan = line.sellingPlanAllocation?.sellingPlan;
|
|
1122
1164
|
switch (key) {
|
|
1123
1165
|
case "title":
|
|
1124
|
-
return
|
|
1166
|
+
return m.product?.title;
|
|
1125
1167
|
case "product-title":
|
|
1126
|
-
return
|
|
1168
|
+
return m.product?.title;
|
|
1127
1169
|
case "variant-title":
|
|
1128
1170
|
return m.title === "Default Title" ? "" : m.title;
|
|
1129
1171
|
case "options":
|
|
1130
1172
|
return (m.selectedOptions || []).filter((o) => o.value !== "Default Title").map((o) => `${o.name}: ${o.value}`).join(", ");
|
|
1131
1173
|
case "vendor":
|
|
1132
|
-
return
|
|
1174
|
+
return m.product?.vendor;
|
|
1133
1175
|
case "sku":
|
|
1134
1176
|
return m.sku;
|
|
1135
1177
|
case "price":
|
|
1136
|
-
return formatMoney(
|
|
1178
|
+
return formatMoney(line.cost?.amountPerQuantity || m.price);
|
|
1137
1179
|
case "compare-at-price":
|
|
1138
|
-
return formatMoney(
|
|
1180
|
+
return formatMoney(line.cost?.compareAtAmountPerQuantity || m.compareAtPrice);
|
|
1139
1181
|
case "line-total":
|
|
1140
|
-
return formatMoney(
|
|
1182
|
+
return formatMoney(line.cost?.totalAmount);
|
|
1141
1183
|
case "line-subtotal":
|
|
1142
|
-
return formatMoney(
|
|
1184
|
+
return formatMoney(line.cost?.subtotalAmount);
|
|
1143
1185
|
case "quantity":
|
|
1144
1186
|
return line.quantity;
|
|
1145
1187
|
case "plan":
|
|
1146
|
-
return
|
|
1188
|
+
return plan?.name || "";
|
|
1147
1189
|
case "plan-description":
|
|
1148
|
-
return
|
|
1190
|
+
return plan?.description || "";
|
|
1149
1191
|
case "components":
|
|
1150
|
-
return (line.lineComponents || []).map((c) => {
|
|
1151
|
-
var _a2, _b2;
|
|
1152
|
-
return `${c.quantity} \xD7 ${((_b2 = (_a2 = c.merchandise) == null ? void 0 : _a2.product) == null ? void 0 : _b2.title) || ""}`;
|
|
1153
|
-
}).join(", ");
|
|
1192
|
+
return (line.lineComponents || []).map((c) => `${c.quantity} \xD7 ${c.merchandise?.product?.title || ""}`).join(", ");
|
|
1154
1193
|
default:
|
|
1155
1194
|
if (key.startsWith("attribute:")) {
|
|
1156
1195
|
const k = key.slice("attribute:".length);
|
|
1157
|
-
return (
|
|
1196
|
+
return (line.attributes || []).find((a) => a.key === k)?.value || "";
|
|
1158
1197
|
}
|
|
1159
1198
|
return void 0;
|
|
1160
1199
|
}
|
|
1161
1200
|
}
|
|
1162
1201
|
function renderLine(node, line) {
|
|
1163
|
-
var _a, _b, _c, _d;
|
|
1164
1202
|
const m = line.merchandise || {};
|
|
1165
1203
|
const isSub = Boolean(line.sellingPlanAllocation);
|
|
1166
1204
|
node.setAttribute(attr("line-id"), line.id);
|
|
1167
1205
|
node.classList.toggle("is-subscription", isSub);
|
|
1168
1206
|
qsa(node, sel("line")).forEach((el) => {
|
|
1169
|
-
var _a2, _b2, _c2, _d2, _e, _f;
|
|
1170
1207
|
const key = get(el, "line");
|
|
1171
|
-
if (key === "image") return setImage(el,
|
|
1172
|
-
if (key === "url")
|
|
1208
|
+
if (key === "image") return setImage(el, m.image?.url || m.product?.featuredImage?.url, m.product?.title);
|
|
1209
|
+
if (key === "url") {
|
|
1210
|
+
const u = productUrl(m.product?.handle, m.product?.id);
|
|
1211
|
+
const q = new URLSearchParams();
|
|
1212
|
+
if (m.id) q.set("variant", String(m.id).split("/").pop());
|
|
1213
|
+
const plan = line.sellingPlanAllocation?.sellingPlan?.id;
|
|
1214
|
+
if (plan) q.set("selling_plan", plan.split("/").pop());
|
|
1215
|
+
const qs = q.toString();
|
|
1216
|
+
return el.setAttribute("href", qs ? `${u}${u.includes("?") ? "&" : "?"}${qs}` : u);
|
|
1217
|
+
}
|
|
1173
1218
|
const v = lineValue(line, key);
|
|
1174
1219
|
if (v !== void 0) setTextTracked(el, v);
|
|
1175
1220
|
return void 0;
|
|
@@ -1177,7 +1222,7 @@ ${cartFragment()}`, { id });
|
|
|
1177
1222
|
const lineConds = {
|
|
1178
1223
|
subscription: isSub,
|
|
1179
1224
|
"one-time": !isSub,
|
|
1180
|
-
"on-sale": parseFloat(
|
|
1225
|
+
"on-sale": parseFloat(line.cost?.compareAtAmountPerQuantity?.amount) > parseFloat(line.cost?.amountPerQuantity?.amount),
|
|
1181
1226
|
"has-components": (line.lineComponents || []).length > 0
|
|
1182
1227
|
};
|
|
1183
1228
|
qsa(node, sel("line-show")).forEach((el) => {
|
|
@@ -1185,11 +1230,9 @@ ${cartFragment()}`, { id });
|
|
|
1185
1230
|
if (k in lineConds) show(el, lineConds[k]);
|
|
1186
1231
|
});
|
|
1187
1232
|
qsa(node, `select${sel("line-plan")}`).forEach((select) => {
|
|
1188
|
-
|
|
1189
|
-
const handle = (_a2 = m.product) == null ? void 0 : _a2.handle;
|
|
1233
|
+
const handle = m.product?.handle;
|
|
1190
1234
|
if (!handle) return;
|
|
1191
1235
|
getProduct(handle).then((product) => {
|
|
1192
|
-
var _a3, _b2;
|
|
1193
1236
|
const plans = allPlans(product);
|
|
1194
1237
|
const allowOneTime = !product.requiresSellingPlan && get(select, "line-plan") !== "subscription-only";
|
|
1195
1238
|
const want = [
|
|
@@ -1207,21 +1250,38 @@ ${cartFragment()}`, { id });
|
|
|
1207
1250
|
});
|
|
1208
1251
|
select.dataset.tetherSig = sig;
|
|
1209
1252
|
}
|
|
1210
|
-
select.value =
|
|
1253
|
+
select.value = line.sellingPlanAllocation?.sellingPlan?.id || "";
|
|
1211
1254
|
show(select, plans.length > 0);
|
|
1212
1255
|
}).catch((err) => reportError(err, "line plan options"));
|
|
1213
1256
|
});
|
|
1214
1257
|
}
|
|
1215
1258
|
|
|
1259
|
+
// src/product.js
|
|
1260
|
+
var product_exports = {};
|
|
1261
|
+
__export(product_exports, {
|
|
1262
|
+
ProductScope: () => ProductScope,
|
|
1263
|
+
rememberOptionNames: () => rememberOptionNames,
|
|
1264
|
+
scopes: () => scopes
|
|
1265
|
+
});
|
|
1266
|
+
|
|
1216
1267
|
// src/status.js
|
|
1268
|
+
var status_exports = {};
|
|
1269
|
+
__export(status_exports, {
|
|
1270
|
+
STATUS_KEYS: () => STATUS_KEYS,
|
|
1271
|
+
boundary: () => boundary,
|
|
1272
|
+
isNotFound: () => isNotFound,
|
|
1273
|
+
ownedBy: () => ownedBy,
|
|
1274
|
+
resolveProductKey: () => resolveProductKey,
|
|
1275
|
+
setScopeStatus: () => setScopeStatus
|
|
1276
|
+
});
|
|
1217
1277
|
function resolveProductKey(el, location = window.location) {
|
|
1218
1278
|
const raw = (get(el, "product") || "").trim();
|
|
1219
1279
|
if (!raw.toLowerCase().startsWith("url:")) return { key: raw || null, fromUrl: false, raw };
|
|
1220
1280
|
const what = raw.slice(4).trim();
|
|
1221
1281
|
let key = null;
|
|
1222
1282
|
if (what.toLowerCase() === "path") {
|
|
1223
|
-
const
|
|
1224
|
-
key =
|
|
1283
|
+
const parts2 = location.pathname.split("/").filter(Boolean);
|
|
1284
|
+
key = parts2.length ? safeDecode(parts2[parts2.length - 1]) : null;
|
|
1225
1285
|
} else {
|
|
1226
1286
|
key = new URLSearchParams(location.search).get(what || "handle");
|
|
1227
1287
|
}
|
|
@@ -1236,29 +1296,23 @@ ${cartFragment()}`, { id });
|
|
|
1236
1296
|
}
|
|
1237
1297
|
}
|
|
1238
1298
|
var STATUS_KEYS = ["loading", "ready", "error", "not-found"];
|
|
1239
|
-
var isNotFound = (err) =>
|
|
1299
|
+
var isNotFound = (err) => err?.code === "NOT_FOUND";
|
|
1240
1300
|
var boundary = () => ["product", "collection", "products", "recommendations"].map((n) => sel(n)).join(",");
|
|
1241
|
-
var ownedBy = (el, root) =>
|
|
1242
|
-
var _a;
|
|
1243
|
-
return el === root || ((_a = el.parentElement) == null ? void 0 : _a.closest(boundary())) === root;
|
|
1244
|
-
};
|
|
1301
|
+
var ownedBy = (el, root) => el === root || el.parentElement?.closest(boundary()) === root;
|
|
1245
1302
|
function setScopeStatus(root, status, err) {
|
|
1246
1303
|
const notFound = status === "not-found";
|
|
1247
1304
|
root.setAttribute(attr("state"), notFound ? "error" : status);
|
|
1248
|
-
if (status === "error" || notFound) root.setAttribute(attr("error"), notFound ? "not-found" :
|
|
1305
|
+
if (status === "error" || notFound) root.setAttribute(attr("error"), notFound ? "not-found" : err?.code || "error");
|
|
1249
1306
|
else root.removeAttribute(attr("error"));
|
|
1250
1307
|
scoped(root, sel("show"), boundary()).forEach((el) => {
|
|
1251
1308
|
const k = get(el, "show");
|
|
1252
1309
|
if (!STATUS_KEYS.includes(k)) return;
|
|
1253
|
-
|
|
1254
|
-
show(el, visible);
|
|
1255
|
-
if (visible && typeof getComputedStyle === "function" && getComputedStyle(el).display === "none") {
|
|
1256
|
-
el.style.display = el.getAttribute(attr("display")) || "block";
|
|
1257
|
-
}
|
|
1310
|
+
show(el, k === status);
|
|
1258
1311
|
});
|
|
1259
1312
|
if (status === "error" || notFound) {
|
|
1260
1313
|
scoped(root, sel("add-to-cart"), boundary()).forEach((btn) => {
|
|
1261
1314
|
btn.classList.add("is-disabled");
|
|
1315
|
+
btn.setAttribute("aria-disabled", "true");
|
|
1262
1316
|
if ("disabled" in btn) btn.disabled = true;
|
|
1263
1317
|
});
|
|
1264
1318
|
}
|
|
@@ -1274,14 +1328,19 @@ ${cartFragment()}`, { id });
|
|
|
1274
1328
|
const mf = (product.metafields || []).find((m) => m && m.namespace === ns && m.key === key);
|
|
1275
1329
|
return mf || null;
|
|
1276
1330
|
}
|
|
1331
|
+
var knownOptionNames = /* @__PURE__ */ new Map();
|
|
1332
|
+
function rememberOptionNames(products) {
|
|
1333
|
+
(products || []).forEach((p) => {
|
|
1334
|
+
if (p?.handle && !knownOptionNames.has(p.handle)) knownOptionNames.set(p.handle, (p.options || []).map((o) => o.name));
|
|
1335
|
+
});
|
|
1336
|
+
}
|
|
1277
1337
|
var ProductScope = class {
|
|
1278
1338
|
constructor(root, product) {
|
|
1279
|
-
var _a;
|
|
1280
1339
|
this.root = root;
|
|
1281
1340
|
this.product = product;
|
|
1282
1341
|
this.selected = {};
|
|
1283
1342
|
this.variant = null;
|
|
1284
|
-
this.quantity = Math.max(0, parseInt(
|
|
1343
|
+
this.quantity = Math.max(0, parseInt(get(root, "quantity") ?? "1", 10) || (has(root, "quantity") ? 0 : 1));
|
|
1285
1344
|
this.plans = allPlans(product);
|
|
1286
1345
|
this.purchase = "one-time";
|
|
1287
1346
|
this.planId = null;
|
|
@@ -1299,11 +1358,10 @@ ${cartFragment()}`, { id });
|
|
|
1299
1358
|
}
|
|
1300
1359
|
/** New data for the same product (e.g. after a country switch). Keeps the selection. */
|
|
1301
1360
|
update(product) {
|
|
1302
|
-
|
|
1303
|
-
const variantId = (_a = this.variant) == null ? void 0 : _a.id;
|
|
1361
|
+
const variantId = this.variant?.id;
|
|
1304
1362
|
this.product = product;
|
|
1305
1363
|
this.plans = allPlans(product);
|
|
1306
|
-
if (this.planId && !this.plans.some((p) => p.id === this.planId)) this.planId =
|
|
1364
|
+
if (this.planId && !this.plans.some((p) => p.id === this.planId)) this.planId = this.plans[0]?.id || null;
|
|
1307
1365
|
if (!this.plans.length && this.purchase === "subscription") this.purchase = "one-time";
|
|
1308
1366
|
const v = product.variants.nodes.find((x) => x.id === variantId);
|
|
1309
1367
|
if (v) {
|
|
@@ -1325,6 +1383,13 @@ ${cartFragment()}`, { id });
|
|
|
1325
1383
|
return this.root.matches(s) ? [this.root, ...inner] : inner;
|
|
1326
1384
|
}
|
|
1327
1385
|
// ------------------------------------------------------------ state
|
|
1386
|
+
/** The product's option name for a name used in the markup (same name, or same position). */
|
|
1387
|
+
optName(authored) {
|
|
1388
|
+
const opts = this.product?.options || [];
|
|
1389
|
+
if (!authored || opts.some((o) => o.name === authored)) return authored;
|
|
1390
|
+
const i = (knownOptionNames.get(this.product.handle) || []).indexOf(authored);
|
|
1391
|
+
return i >= 0 && opts[i] ? opts[i].name : authored;
|
|
1392
|
+
}
|
|
1328
1393
|
initSelection() {
|
|
1329
1394
|
const vs = this.product.variants.nodes;
|
|
1330
1395
|
const first = vs.find((v) => v.availableForSale) || vs[0];
|
|
@@ -1425,8 +1490,7 @@ ${cartFragment()}`, { id });
|
|
|
1425
1490
|
this.renderVariant();
|
|
1426
1491
|
}
|
|
1427
1492
|
async addToCart() {
|
|
1428
|
-
|
|
1429
|
-
if (!((_a = this.variant) == null ? void 0 : _a.availableForSale)) return null;
|
|
1493
|
+
if (!this.variant?.availableForSale) return null;
|
|
1430
1494
|
const line = this.getLine();
|
|
1431
1495
|
if (!line || line.quantity < 1) return null;
|
|
1432
1496
|
const cart = await Cart.add(line);
|
|
@@ -1438,11 +1502,11 @@ ${cartFragment()}`, { id });
|
|
|
1438
1502
|
const own = (el) => el && el.closest(productSel()) === this.root;
|
|
1439
1503
|
this.root.addEventListener("click", (e) => {
|
|
1440
1504
|
const t = e.target;
|
|
1441
|
-
if (!
|
|
1505
|
+
if (!t?.closest) return;
|
|
1442
1506
|
const opt = t.closest(sel("option-value"));
|
|
1443
1507
|
if (own(opt) && opt.closest(sel("option-list"))) {
|
|
1444
1508
|
e.preventDefault();
|
|
1445
|
-
this.select(get(opt.closest(sel("option-list")), "option-list"), get(opt, "option-value"));
|
|
1509
|
+
this.select(this.optName(get(opt.closest(sel("option-list")), "option-list")), get(opt, "option-value"));
|
|
1446
1510
|
return;
|
|
1447
1511
|
}
|
|
1448
1512
|
const plan = t.closest(sel("plan-value"));
|
|
@@ -1478,7 +1542,7 @@ ${cartFragment()}`, { id });
|
|
|
1478
1542
|
this.root.addEventListener("change", (e) => {
|
|
1479
1543
|
const t = e.target;
|
|
1480
1544
|
if (!own(t)) return;
|
|
1481
|
-
if (t.matches(`select${sel("option")}`)) this.select(get(t, "option"), t.value);
|
|
1545
|
+
if (t.matches(`select${sel("option")}`)) this.select(this.optName(get(t, "option")), t.value);
|
|
1482
1546
|
else if (t.matches(`select${sel("variant-select")}`)) this.selectVariant(t.value);
|
|
1483
1547
|
else if (t.matches(`select${sel("plan")}`)) {
|
|
1484
1548
|
if (t.value) this.selectPlan(t.value);
|
|
@@ -1508,24 +1572,26 @@ ${cartFragment()}`, { id });
|
|
|
1508
1572
|
}
|
|
1509
1573
|
// ------------------------------------------------------------ render
|
|
1510
1574
|
renderProduct() {
|
|
1575
|
+
const p0 = this.product;
|
|
1576
|
+
if (!knownOptionNames.has(p0.handle)) {
|
|
1577
|
+
const authored = this.$(`${sel("option-list")},select${sel("option")}`).map((e) => get(e, "option-list") || get(e, "option"));
|
|
1578
|
+
if (authored.some((n) => p0.options.some((o) => o.name === n))) rememberOptionNames([p0]);
|
|
1579
|
+
}
|
|
1511
1580
|
const p = this.product;
|
|
1512
1581
|
this.$(sel("bind")).forEach((el) => {
|
|
1513
|
-
|
|
1514
|
-
const
|
|
1582
|
+
const key = get(el, "bind") || "";
|
|
1583
|
+
const txt = {
|
|
1584
|
+
title: p.title,
|
|
1585
|
+
"description-text": p.description,
|
|
1586
|
+
vendor: p.vendor,
|
|
1587
|
+
type: p.productType,
|
|
1588
|
+
handle: p.handle
|
|
1589
|
+
}[key];
|
|
1590
|
+
if (txt !== void 0) return setText(el, txt);
|
|
1515
1591
|
switch (key) {
|
|
1516
|
-
case "title":
|
|
1517
|
-
return setText(el, p.title);
|
|
1518
1592
|
case "description":
|
|
1519
1593
|
el.innerHTML = p.descriptionHtml || "";
|
|
1520
1594
|
return void 0;
|
|
1521
|
-
case "description-text":
|
|
1522
|
-
return setText(el, p.description);
|
|
1523
|
-
case "vendor":
|
|
1524
|
-
return setText(el, p.vendor);
|
|
1525
|
-
case "type":
|
|
1526
|
-
return setText(el, p.productType);
|
|
1527
|
-
case "handle":
|
|
1528
|
-
return setText(el, p.handle);
|
|
1529
1595
|
case "published-at":
|
|
1530
1596
|
return setText(el, formatDate(p.publishedAt));
|
|
1531
1597
|
case "min-price":
|
|
@@ -1533,13 +1599,17 @@ ${cartFragment()}`, { id });
|
|
|
1533
1599
|
case "max-price":
|
|
1534
1600
|
return setText(el, formatMoney(p.priceRange.maxVariantPrice));
|
|
1535
1601
|
case "featured-image":
|
|
1536
|
-
return setImage(el,
|
|
1602
|
+
return setImage(el, p.featuredImage?.url, p.featuredImage?.altText || p.title);
|
|
1537
1603
|
case "url":
|
|
1538
1604
|
case "link":
|
|
1539
1605
|
el.setAttribute("href", productUrl(p.handle, p.id));
|
|
1540
1606
|
if (key === "url" && !el.firstElementChild && !el.textContent.trim()) setText(el, p.title);
|
|
1541
1607
|
return void 0;
|
|
1542
1608
|
case "images":
|
|
1609
|
+
{
|
|
1610
|
+
const imgs = [...el.children].map((n) => n.tagName === "IMG" ? n : n.querySelector("img"));
|
|
1611
|
+
if (!hasTemplate(el) && imgs.length === p.images.nodes.length && imgs.every((im, i) => sameImage(im?.getAttribute("src"), p.images.nodes[i].url))) return void 0;
|
|
1612
|
+
}
|
|
1543
1613
|
return repeat(el, p.images.nodes, (node, img) => {
|
|
1544
1614
|
const target = node.matches(`img,${sel("bind", "image")}`) ? node : node.querySelector("img") || node;
|
|
1545
1615
|
setImage(target, img.url, img.altText || p.title);
|
|
@@ -1549,41 +1619,36 @@ ${cartFragment()}`, { id });
|
|
|
1549
1619
|
return repeat(el, p.tags.filter((t) => !exclude.includes(t)), (node, tag) => setText(node, tag));
|
|
1550
1620
|
}
|
|
1551
1621
|
default:
|
|
1552
|
-
if (
|
|
1553
|
-
const img = p.images.nodes[
|
|
1554
|
-
return setImage(el, img
|
|
1622
|
+
if (/^image:\d+$/.test(key)) {
|
|
1623
|
+
const img = p.images.nodes[key.slice(6) - 1] || p.featuredImage;
|
|
1624
|
+
return setImage(el, img?.url, img?.altText || p.title);
|
|
1555
1625
|
}
|
|
1556
|
-
if (key
|
|
1557
|
-
const mf = metafieldValue(p, key.slice(
|
|
1626
|
+
if (key.startsWith("metafield:")) {
|
|
1627
|
+
const mf = metafieldValue(p, key.slice(10));
|
|
1558
1628
|
if (!mf) return setText(el, "");
|
|
1559
|
-
if (
|
|
1560
|
-
if (mf.type
|
|
1561
|
-
el.textContent = mf.value;
|
|
1562
|
-
el.style.whiteSpace = "pre-line";
|
|
1563
|
-
return void 0;
|
|
1564
|
-
}
|
|
1629
|
+
if (mf.reference?.image) return setImage(el, mf.reference.image.url, mf.reference.image.altText);
|
|
1630
|
+
if (/^(rich_text|multi_line_text)_field$/.test(mf.type)) el.style.whiteSpace = "pre-line";
|
|
1565
1631
|
return setText(el, mf.value);
|
|
1566
1632
|
}
|
|
1567
1633
|
return void 0;
|
|
1568
1634
|
}
|
|
1569
1635
|
});
|
|
1570
1636
|
this.$(sel("option-list")).forEach((container) => {
|
|
1571
|
-
const name = get(container, "option-list");
|
|
1637
|
+
const name = this.optName(get(container, "option-list"));
|
|
1572
1638
|
const opt = p.options.find((o) => o.name === name);
|
|
1573
1639
|
if (!opt || opt.optionValues.length <= 1 && has(container, "hide-single")) return show(container, false);
|
|
1574
1640
|
return repeat(container, opt.optionValues, (node, v) => {
|
|
1575
|
-
var _a, _b, _c;
|
|
1576
1641
|
node.setAttribute(attr("option-value"), v.name);
|
|
1577
1642
|
const t = node.querySelector(sel("bind", "option-value"));
|
|
1578
1643
|
if (t) setText(t, v.name);
|
|
1579
1644
|
else if (!node.firstElementChild) setText(node, v.name);
|
|
1580
1645
|
const sw = node.querySelector(sel("bind", "swatch"));
|
|
1581
|
-
if (sw &&
|
|
1582
|
-
if (sw &&
|
|
1646
|
+
if (sw && v.swatch?.color) sw.style.backgroundColor = v.swatch.color;
|
|
1647
|
+
if (sw && v.swatch?.image?.previewImage) setImage(sw, v.swatch.image.previewImage.url, v.name);
|
|
1583
1648
|
});
|
|
1584
1649
|
});
|
|
1585
1650
|
this.$(`select${sel("option")}`).forEach((s) => {
|
|
1586
|
-
const opt = p.options.find((o) => o.name === get(s, "option"));
|
|
1651
|
+
const opt = p.options.find((o) => o.name === this.optName(get(s, "option")));
|
|
1587
1652
|
if (!opt) return show(s, false);
|
|
1588
1653
|
s.innerHTML = "";
|
|
1589
1654
|
opt.optionValues.forEach((v) => s.appendChild(new Option(v.name, v.name)));
|
|
@@ -1611,7 +1676,6 @@ ${cartFragment()}`, { id });
|
|
|
1611
1676
|
});
|
|
1612
1677
|
}
|
|
1613
1678
|
renderVariant() {
|
|
1614
|
-
var _a, _b, _c;
|
|
1615
1679
|
const p = this.product;
|
|
1616
1680
|
let v = this.findVariant();
|
|
1617
1681
|
if (!v) {
|
|
@@ -1633,67 +1697,44 @@ ${cartFragment()}`, { id });
|
|
|
1633
1697
|
const oneTimePrice = v.price;
|
|
1634
1698
|
const subPrice = this.planId ? planPrice(v, this.planId) : null;
|
|
1635
1699
|
const price = sub ? subPrice : oneTimePrice;
|
|
1636
|
-
const compareAt = sub ?
|
|
1700
|
+
const compareAt = sub ? allocationFor(v, this.planId)?.priceAdjustments?.[0]?.compareAtPrice || v.compareAtPrice : v.compareAtPrice;
|
|
1637
1701
|
const onSale = compareAt && parseFloat(compareAt.amount) > parseFloat(price.amount);
|
|
1638
1702
|
const discountPct = onSale ? Math.round((1 - parseFloat(price.amount) / parseFloat(compareAt.amount)) * 100) : 0;
|
|
1639
1703
|
const savings = this.planId ? planSavings(v, this.planId) : { amount: null, percentage: 0 };
|
|
1640
1704
|
const plan = this.plan;
|
|
1641
1705
|
const total = { amount: (parseFloat(price.amount) * Math.max(1, this.quantity)).toFixed(2), currencyCode: price.currencyCode };
|
|
1706
|
+
const fm = formatMoney;
|
|
1707
|
+
const pct = (n) => n ? `${n}%` : "";
|
|
1708
|
+
const text = {
|
|
1709
|
+
price: () => fm(price),
|
|
1710
|
+
"total-price": () => fm(total),
|
|
1711
|
+
"one-time-price": () => fm(oneTimePrice),
|
|
1712
|
+
"subscription-price": () => fm(subPrice),
|
|
1713
|
+
"subscription-savings": () => pct(savings.percentage),
|
|
1714
|
+
"subscription-savings-amount": () => fm(savings.amount),
|
|
1715
|
+
"checkout-charge": () => fm(sub ? planCheckoutCharge(v, this.planId) : price),
|
|
1716
|
+
"plan-name": () => plan?.name,
|
|
1717
|
+
"plan-description": () => plan?.description,
|
|
1718
|
+
"plan-group": () => plan?.group?.name,
|
|
1719
|
+
"compare-at-price": () => onSale ? fm(compareAt) : "",
|
|
1720
|
+
"unit-price": () => fm(v.unitPrice),
|
|
1721
|
+
"discount-percentage": () => `${discountPct}%`,
|
|
1722
|
+
"discount-amount": () => onSale ? fm({ amount: String(parseFloat(compareAt.amount) - parseFloat(price.amount)), currencyCode: price.currencyCode }) : "",
|
|
1723
|
+
"variant-title": () => v.title,
|
|
1724
|
+
"variant-id": () => v.id,
|
|
1725
|
+
sku: () => v.sku,
|
|
1726
|
+
barcode: () => v.barcode,
|
|
1727
|
+
stock: () => v.quantityAvailable,
|
|
1728
|
+
quantity: () => this.quantity
|
|
1729
|
+
};
|
|
1642
1730
|
this.$(sel("bind")).forEach((el) => {
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
return setText(el, formatMoney(price));
|
|
1647
|
-
case "total-price":
|
|
1648
|
-
return setText(el, formatMoney(total));
|
|
1649
|
-
case "one-time-price":
|
|
1650
|
-
return setText(el, formatMoney(oneTimePrice));
|
|
1651
|
-
case "subscription-price":
|
|
1652
|
-
return setText(el, formatMoney(subPrice));
|
|
1653
|
-
case "subscription-savings":
|
|
1654
|
-
return setText(el, savings.percentage ? `${savings.percentage}%` : "");
|
|
1655
|
-
case "subscription-savings-amount":
|
|
1656
|
-
return setText(el, formatMoney(savings.amount));
|
|
1657
|
-
case "checkout-charge":
|
|
1658
|
-
return setText(el, formatMoney(sub ? planCheckoutCharge(v, this.planId) : price));
|
|
1659
|
-
case "plan-name":
|
|
1660
|
-
return setText(el, (plan == null ? void 0 : plan.name) || "");
|
|
1661
|
-
case "plan-description":
|
|
1662
|
-
return setText(el, (plan == null ? void 0 : plan.description) || "");
|
|
1663
|
-
case "plan-group":
|
|
1664
|
-
return setText(el, ((_a2 = plan == null ? void 0 : plan.group) == null ? void 0 : _a2.name) || "");
|
|
1665
|
-
case "compare-at-price":
|
|
1666
|
-
return setText(el, onSale ? formatMoney(compareAt) : "");
|
|
1667
|
-
case "unit-price":
|
|
1668
|
-
return setText(el, formatMoney(v.unitPrice));
|
|
1669
|
-
case "discount-percentage":
|
|
1670
|
-
return setText(el, `${discountPct}%`);
|
|
1671
|
-
case "discount-amount":
|
|
1672
|
-
return setText(el, onSale ? formatMoney({
|
|
1673
|
-
amount: String(parseFloat(compareAt.amount) - parseFloat(price.amount)),
|
|
1674
|
-
currencyCode: price.currencyCode
|
|
1675
|
-
}) : "");
|
|
1676
|
-
case "variant-title":
|
|
1677
|
-
return setText(el, v.title);
|
|
1678
|
-
case "variant-id":
|
|
1679
|
-
return setText(el, v.id);
|
|
1680
|
-
case "sku":
|
|
1681
|
-
return setText(el, v.sku);
|
|
1682
|
-
case "barcode":
|
|
1683
|
-
return setText(el, v.barcode);
|
|
1684
|
-
case "stock":
|
|
1685
|
-
return setText(el, (_b2 = v.quantityAvailable) != null ? _b2 : "");
|
|
1686
|
-
case "quantity":
|
|
1687
|
-
return setText(el, this.quantity);
|
|
1688
|
-
case "variant-image":
|
|
1689
|
-
return setImage(el, ((_c2 = v.image) == null ? void 0 : _c2.url) || ((_d = p.featuredImage) == null ? void 0 : _d.url), ((_e = v.image) == null ? void 0 : _e.altText) || p.title);
|
|
1690
|
-
default:
|
|
1691
|
-
return void 0;
|
|
1692
|
-
}
|
|
1731
|
+
const k = get(el, "bind");
|
|
1732
|
+
if (k === "variant-image") setImage(el, v.image?.url || p.featuredImage?.url, v.image?.altText || p.title);
|
|
1733
|
+
else if (text[k]) setText(el, text[k]());
|
|
1693
1734
|
});
|
|
1694
|
-
this.$(sel("option-selected")).forEach((el) => setText(el, this.selected[get(el, "option-selected")] || ""));
|
|
1735
|
+
this.$(sel("option-selected")).forEach((el) => setText(el, this.selected[this.optName(get(el, "option-selected"))] || ""));
|
|
1695
1736
|
this.$(sel("option-list")).forEach((container) => {
|
|
1696
|
-
const name = get(container, "option-list");
|
|
1737
|
+
const name = this.optName(get(container, "option-list"));
|
|
1697
1738
|
qsa(container, sel("option-value")).forEach((node) => {
|
|
1698
1739
|
const value = get(node, "option-value");
|
|
1699
1740
|
setSelected(node, this.selected[name] === value);
|
|
@@ -1703,7 +1744,7 @@ ${cartFragment()}`, { id });
|
|
|
1703
1744
|
});
|
|
1704
1745
|
});
|
|
1705
1746
|
this.$(`select${sel("option")}`).forEach((s) => {
|
|
1706
|
-
s.value = this.selected[get(s, "option")] || "";
|
|
1747
|
+
s.value = this.selected[this.optName(get(s, "option"))] || "";
|
|
1707
1748
|
});
|
|
1708
1749
|
this.$(`select${sel("variant-select")}`).forEach((s) => {
|
|
1709
1750
|
s.value = v.id;
|
|
@@ -1727,23 +1768,16 @@ ${cartFragment()}`, { id });
|
|
|
1727
1768
|
qsa(node, sel("plan-bind")).forEach((el) => {
|
|
1728
1769
|
const adj = planAdjustment(planObj);
|
|
1729
1770
|
const s = planSavings(v, id);
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
return setText(el, formatMoney(s.amount));
|
|
1741
|
-
case "adjustment":
|
|
1742
|
-
if (!adj) return setText(el, "");
|
|
1743
|
-
return setText(el, adj.type === "percentage" ? `${adj.value}%` : formatMoney(adj.value));
|
|
1744
|
-
default:
|
|
1745
|
-
return void 0;
|
|
1746
|
-
}
|
|
1771
|
+
const k = get(el, "plan-bind");
|
|
1772
|
+
const val = {
|
|
1773
|
+
name: () => planObj?.name,
|
|
1774
|
+
description: () => planObj?.description,
|
|
1775
|
+
price: () => fm(planPrice(v, id)),
|
|
1776
|
+
savings: () => pct(s.percentage),
|
|
1777
|
+
"savings-amount": () => fm(s.amount),
|
|
1778
|
+
adjustment: () => !adj ? "" : adj.type === "percentage" ? `${adj.value}%` : fm(adj.value)
|
|
1779
|
+
}[k];
|
|
1780
|
+
if (val) setText(el, val());
|
|
1747
1781
|
});
|
|
1748
1782
|
});
|
|
1749
1783
|
this.$(`select${sel("plan")}`).forEach((s) => {
|
|
@@ -1782,16 +1816,17 @@ ${cartFragment()}`, { id });
|
|
|
1782
1816
|
this.$(sel("add-to-cart")).forEach((btn) => {
|
|
1783
1817
|
const disabled = !v.availableForSale;
|
|
1784
1818
|
btn.classList.toggle("is-disabled", disabled);
|
|
1819
|
+
btn.setAttribute("aria-disabled", String(disabled));
|
|
1785
1820
|
if ("disabled" in btn) btn.disabled = disabled;
|
|
1786
1821
|
const soldOut = get(btn, "soldout-text");
|
|
1787
1822
|
const subText = get(btn, "subscribe-text");
|
|
1788
1823
|
if (soldOut || subText) {
|
|
1789
1824
|
const label = btn.querySelector(sel("add-label")) || btn;
|
|
1790
1825
|
if (label.dataset.tetherLabel === void 0) label.dataset.tetherLabel = label.tagName === "INPUT" ? label.value : label.textContent;
|
|
1791
|
-
let
|
|
1792
|
-
if (sub && subText)
|
|
1793
|
-
if (disabled && soldOut)
|
|
1794
|
-
if (btn.getAttribute("aria-busy") !== "true") setText(label,
|
|
1826
|
+
let text2 = label.dataset.tetherLabel;
|
|
1827
|
+
if (sub && subText) text2 = subText;
|
|
1828
|
+
if (disabled && soldOut) text2 = soldOut;
|
|
1829
|
+
if (btn.getAttribute("aria-busy") !== "true") setText(label, text2);
|
|
1795
1830
|
}
|
|
1796
1831
|
});
|
|
1797
1832
|
emit("variant-changed", {
|
|
@@ -1805,6 +1840,11 @@ ${cartFragment()}`, { id });
|
|
|
1805
1840
|
};
|
|
1806
1841
|
|
|
1807
1842
|
// src/collection.js
|
|
1843
|
+
var collection_exports = {};
|
|
1844
|
+
__export(collection_exports, {
|
|
1845
|
+
ListScope: () => ListScope,
|
|
1846
|
+
listSelector: () => listSelector
|
|
1847
|
+
});
|
|
1808
1848
|
var listSelector = () => `${sel("collection")},${sel("products")},${sel("recommendations")}`;
|
|
1809
1849
|
var ListScope = class {
|
|
1810
1850
|
constructor(root) {
|
|
@@ -1814,8 +1854,7 @@ ${cartFragment()}`, { id });
|
|
|
1814
1854
|
this.collection = null;
|
|
1815
1855
|
setScopeStatus(root, "loading");
|
|
1816
1856
|
root.addEventListener("click", (e) => {
|
|
1817
|
-
|
|
1818
|
-
const more = (_b = (_a = e.target).closest) == null ? void 0 : _b.call(_a, sel("load-more"));
|
|
1857
|
+
const more = e.target.closest?.(sel("load-more"));
|
|
1819
1858
|
if (more && more.closest(listSelector()) === root) {
|
|
1820
1859
|
e.preventDefault();
|
|
1821
1860
|
busy(more, () => this.load(true));
|
|
@@ -1837,20 +1876,19 @@ ${cartFragment()}`, { id });
|
|
|
1837
1876
|
return opts;
|
|
1838
1877
|
}
|
|
1839
1878
|
async load(more = false) {
|
|
1840
|
-
var _a;
|
|
1841
1879
|
const token = this.loadToken = (this.loadToken || 0) + 1;
|
|
1842
|
-
const
|
|
1880
|
+
const stale2 = () => token !== this.loadToken;
|
|
1843
1881
|
try {
|
|
1844
1882
|
if (has(this.root, "recommendations")) {
|
|
1845
1883
|
const key = get(this.root, "recommendations");
|
|
1846
1884
|
const base = await getProduct(key);
|
|
1847
1885
|
const recs = await getRecommendations(base.id, get(this.root, "intent") || "RELATED");
|
|
1848
|
-
if (
|
|
1886
|
+
if (stale2()) return;
|
|
1849
1887
|
const limit = parseInt(get(this.root, "limit") || "", 10);
|
|
1850
1888
|
this.products = limit ? recs.slice(0, limit) : recs;
|
|
1851
1889
|
} else {
|
|
1852
|
-
const res = await getProducts({ ...this.options(), after: more ?
|
|
1853
|
-
if (
|
|
1890
|
+
const res = await getProducts({ ...this.options(), after: more ? this.pageInfo?.endCursor : null });
|
|
1891
|
+
if (stale2()) return;
|
|
1854
1892
|
this.collection = res.collection;
|
|
1855
1893
|
this.pageInfo = res.pageInfo;
|
|
1856
1894
|
this.products = more ? [...this.products, ...res.products] : res.products;
|
|
@@ -1859,7 +1897,7 @@ ${cartFragment()}`, { id });
|
|
|
1859
1897
|
setScopeStatus(this.root, "ready");
|
|
1860
1898
|
emit("list-ready", { products: this.products, collection: this.collection }, this.root);
|
|
1861
1899
|
} catch (err) {
|
|
1862
|
-
if (
|
|
1900
|
+
if (stale2()) return;
|
|
1863
1901
|
if (!more) setScopeStatus(this.root, isNotFound(err) ? "not-found" : "error", err);
|
|
1864
1902
|
reportError(err, "list load failed");
|
|
1865
1903
|
emit("error", { error: err, context: "list" }, this.root);
|
|
@@ -1868,15 +1906,14 @@ ${cartFragment()}`, { id });
|
|
|
1868
1906
|
render() {
|
|
1869
1907
|
const c = this.collection;
|
|
1870
1908
|
this.$(sel("collection-bind")).forEach((el) => {
|
|
1871
|
-
var _a, _b;
|
|
1872
1909
|
switch (get(el, "collection-bind")) {
|
|
1873
1910
|
case "title":
|
|
1874
|
-
return setText(el,
|
|
1911
|
+
return setText(el, c?.title || "");
|
|
1875
1912
|
case "description":
|
|
1876
|
-
el.innerHTML =
|
|
1913
|
+
el.innerHTML = c?.descriptionHtml || "";
|
|
1877
1914
|
return void 0;
|
|
1878
1915
|
case "image":
|
|
1879
|
-
return setImage(el,
|
|
1916
|
+
return setImage(el, c?.image?.url, c?.image?.altText || c?.title);
|
|
1880
1917
|
case "count":
|
|
1881
1918
|
return setText(el, this.products.length);
|
|
1882
1919
|
default:
|
|
@@ -1897,330 +1934,25 @@ ${cartFragment()}`, { id });
|
|
|
1897
1934
|
if (k === "list-filled") el.style.display = empty ? "none" : "";
|
|
1898
1935
|
});
|
|
1899
1936
|
this.$(sel("load-more")).forEach((el) => {
|
|
1900
|
-
|
|
1901
|
-
el.style.display = ((_a = this.pageInfo) == null ? void 0 : _a.hasNextPage) ? "" : "none";
|
|
1937
|
+
el.style.display = this.pageInfo?.hasNextPage ? "" : "none";
|
|
1902
1938
|
});
|
|
1903
1939
|
notifyLayoutChanged("list");
|
|
1904
1940
|
}
|
|
1905
1941
|
};
|
|
1906
1942
|
|
|
1907
|
-
// src/
|
|
1908
|
-
var
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
function apiVersionStatus(version, now = /* @__PURE__ */ new Date()) {
|
|
1920
|
-
if (version === "unstable") return { version, ok: false, note: '"unstable" changes without notice: pin a dated version such as 2026-07' };
|
|
1921
|
-
const p = parse(version);
|
|
1922
|
-
if (!p) return { version, ok: false, note: "not a quarterly version (YYYY-01, -04, -07 or -10)" };
|
|
1923
|
-
const released = new Date(Date.UTC(p.y, p.m - 1, 1));
|
|
1924
|
-
const supportedUntil = new Date(Date.UTC(p.y + 1, p.m - 1, 1));
|
|
1925
|
-
const monthsLeft = (supportedUntil - now) / (30.44 * 24 * 3600 * 1e3);
|
|
1926
|
-
let note = `supported until about ${supportedUntil.toISOString().slice(0, 7)}`;
|
|
1927
|
-
if (released > now) note = "not released yet (release candidate at best)";
|
|
1928
|
-
else if (monthsLeft <= 0) note = "no longer supported: Shopify falls back to the oldest supported version. Upgrade (see README)";
|
|
1929
|
-
else if (monthsLeft < 3) note = `${note}: plan the upgrade to ${nextApiVersion(version)} or later (see README)`;
|
|
1930
|
-
return { version, ok: released <= now && monthsLeft > 0, monthsLeft: Math.round(monthsLeft), next: nextApiVersion(version), note };
|
|
1931
|
-
}
|
|
1932
|
-
|
|
1933
|
-
// src/markets.js
|
|
1934
|
-
var COUNTRY = /^[A-Z]{2}$/;
|
|
1935
|
-
var REGIONAL_LANGUAGES = /* @__PURE__ */ new Set(["PT_BR", "PT_PT", "ZH_CN", "ZH_TW"]);
|
|
1936
|
-
var storageKey = () => `${cfg.prefix}:market:${cfg.domain}`;
|
|
1937
|
-
var defaultEnv = () => ({
|
|
1938
|
-
document: typeof document !== "undefined" ? document : null,
|
|
1939
|
-
location: typeof window !== "undefined" ? window.location : null,
|
|
1940
|
-
storage: (() => {
|
|
1941
|
-
try {
|
|
1942
|
-
return window.localStorage;
|
|
1943
|
-
} catch {
|
|
1944
|
-
return null;
|
|
1945
|
-
}
|
|
1946
|
-
})(),
|
|
1947
|
-
global: typeof window !== "undefined" ? window : {}
|
|
1943
|
+
// src/mounting.js
|
|
1944
|
+
var mounting_exports = {};
|
|
1945
|
+
__export(mounting_exports, {
|
|
1946
|
+
hydrateFromPage: () => hydrateFromPage,
|
|
1947
|
+
liveLists: () => liveLists,
|
|
1948
|
+
liveProducts: () => liveProducts,
|
|
1949
|
+
mountProduct: () => mountProduct,
|
|
1950
|
+
mountedLists: () => mountedLists,
|
|
1951
|
+
mountedProducts: () => mountedProducts,
|
|
1952
|
+
pruneLive: () => pruneLive,
|
|
1953
|
+
retryFailed: () => retryFailed,
|
|
1954
|
+
within: () => within
|
|
1948
1955
|
});
|
|
1949
|
-
function toCountry(v) {
|
|
1950
|
-
const c = String(v || "").trim().toUpperCase();
|
|
1951
|
-
return COUNTRY.test(c) && c !== "XX" && c !== "T1" ? c : null;
|
|
1952
|
-
}
|
|
1953
|
-
function toLanguage(v) {
|
|
1954
|
-
const raw = String(v || "").trim().replace("-", "_").toUpperCase();
|
|
1955
|
-
if (!raw) return null;
|
|
1956
|
-
if (REGIONAL_LANGUAGES.has(raw)) return raw;
|
|
1957
|
-
const primary = raw.split("_")[0];
|
|
1958
|
-
return /^[A-Z]{2,3}$/.test(primary) ? primary : null;
|
|
1959
|
-
}
|
|
1960
|
-
function readCookie(doc, name) {
|
|
1961
|
-
if (!doc || !name) return null;
|
|
1962
|
-
const hit = String(doc.cookie || "").split(";").map((c) => c.trim()).find((c) => c.startsWith(`${name}=`));
|
|
1963
|
-
return hit ? decodeURIComponent(hit.slice(name.length + 1)) : null;
|
|
1964
|
-
}
|
|
1965
|
-
var lookup = (locales, key) => {
|
|
1966
|
-
if (!locales || !key) return null;
|
|
1967
|
-
const k = String(key).toLowerCase();
|
|
1968
|
-
const found = Object.keys(locales).find((x) => x.toLowerCase() === k);
|
|
1969
|
-
return found ? locales[found] : null;
|
|
1970
|
-
};
|
|
1971
|
-
var readers = {
|
|
1972
|
-
saved(m, env) {
|
|
1973
|
-
if (m.remember === false || !env.storage) return null;
|
|
1974
|
-
try {
|
|
1975
|
-
return JSON.parse(env.storage.getItem(storageKey()) || "null");
|
|
1976
|
-
} catch {
|
|
1977
|
-
return null;
|
|
1978
|
-
}
|
|
1979
|
-
},
|
|
1980
|
-
server(m, env) {
|
|
1981
|
-
const meta = (name) => {
|
|
1982
|
-
var _a, _b, _c;
|
|
1983
|
-
return (_c = (_b = (_a = env.document) == null ? void 0 : _a.querySelector) == null ? void 0 : _b.call(_a, `meta[name="${name}"]`)) == null ? void 0 : _c.getAttribute("content");
|
|
1984
|
-
};
|
|
1985
|
-
const country = env.global.TETHER_COUNTRY || meta("tether-country") || readCookie(env.document, m.cookie || "tether_country");
|
|
1986
|
-
const language = env.global.TETHER_LANGUAGE || meta("tether-language");
|
|
1987
|
-
return { country, language };
|
|
1988
|
-
},
|
|
1989
|
-
webflow(m, env) {
|
|
1990
|
-
var _a, _b, _c;
|
|
1991
|
-
const segment = (((_a = env.location) == null ? void 0 : _a.pathname) || "/").split("/").filter(Boolean)[0];
|
|
1992
|
-
const fromPath = lookup(m.locales, segment);
|
|
1993
|
-
if (fromPath) return fromPath;
|
|
1994
|
-
const lang = (_c = (_b = env.document) == null ? void 0 : _b.documentElement) == null ? void 0 : _c.getAttribute("lang");
|
|
1995
|
-
if (!lang) return null;
|
|
1996
|
-
const mapped = lookup(m.locales, lang) || lookup(m.locales, lang.split("-")[0]);
|
|
1997
|
-
if (mapped) return { locale: lang, ...mapped };
|
|
1998
|
-
if (m.locales && m.strict) return null;
|
|
1999
|
-
const region = lang.split("-")[1];
|
|
2000
|
-
return { language: lang, country: region && region.length === 2 ? region : null, locale: lang };
|
|
2001
|
-
}
|
|
2002
|
-
};
|
|
2003
|
-
function detectMarket(markets = cfg.markets, env = defaultEnv()) {
|
|
2004
|
-
const m = markets || {};
|
|
2005
|
-
if (m.detect === false) return {};
|
|
2006
|
-
const list = !m.detect ? ["saved"] : m.detect === true ? ["saved", "server", "webflow"] : [].concat(m.detect);
|
|
2007
|
-
markets = m;
|
|
2008
|
-
const out = { sources: {} };
|
|
2009
|
-
list.forEach((name) => {
|
|
2010
|
-
var _a;
|
|
2011
|
-
const r = (_a = readers[name]) == null ? void 0 : _a.call(readers, markets, env);
|
|
2012
|
-
if (!r) return;
|
|
2013
|
-
const vals = { country: toCountry(r.country), language: toLanguage(r.language), locale: r.locale || null };
|
|
2014
|
-
Object.entries(vals).forEach(([k, v]) => {
|
|
2015
|
-
if (v && !out[k]) {
|
|
2016
|
-
out[k] = v;
|
|
2017
|
-
out.sources[k] = name;
|
|
2018
|
-
}
|
|
2019
|
-
});
|
|
2020
|
-
});
|
|
2021
|
-
return out;
|
|
2022
|
-
}
|
|
2023
|
-
function rememberMarket(market) {
|
|
2024
|
-
var _a;
|
|
2025
|
-
if (((_a = cfg.markets) == null ? void 0 : _a.remember) === false) return;
|
|
2026
|
-
try {
|
|
2027
|
-
localStorage.setItem(storageKey(), JSON.stringify({ country: market.country, language: market.language || void 0 }));
|
|
2028
|
-
} catch {
|
|
2029
|
-
}
|
|
2030
|
-
}
|
|
2031
|
-
var localizationPromise = null;
|
|
2032
|
-
function clearLocalization() {
|
|
2033
|
-
localizationPromise = null;
|
|
2034
|
-
}
|
|
2035
|
-
function getLocalization() {
|
|
2036
|
-
if (!localizationPromise) {
|
|
2037
|
-
localizationPromise = gql(`query TetherLocalization {
|
|
2038
|
-
localization {
|
|
2039
|
-
country { isoCode name currency { isoCode symbol } }
|
|
2040
|
-
language { isoCode name }
|
|
2041
|
-
availableCountries { isoCode name currency { isoCode symbol } availableLanguages { isoCode name } }
|
|
2042
|
-
}
|
|
2043
|
-
}`).then((d) => d.localization).catch((err) => {
|
|
2044
|
-
localizationPromise = null;
|
|
2045
|
-
throw err;
|
|
2046
|
-
});
|
|
2047
|
-
}
|
|
2048
|
-
return localizationPromise;
|
|
2049
|
-
}
|
|
2050
|
-
|
|
2051
|
-
// src/diagnose.js
|
|
2052
|
-
var mask = (t) => t ? `${String(t).slice(0, 4)}\u2026${String(t).slice(-4)} (${String(t).length} chars)` : "(missing)";
|
|
2053
|
-
function describe(el) {
|
|
2054
|
-
const id = el.id ? `#${el.id}` : "";
|
|
2055
|
-
const cls = el.className && typeof el.className === "string" ? `.${el.className.trim().split(/\s+/).slice(0, 2).join(".")}` : "";
|
|
2056
|
-
return `<${el.tagName.toLowerCase()}${id}${cls}>`;
|
|
2057
|
-
}
|
|
2058
|
-
function checkMarkup(root = document) {
|
|
2059
|
-
const problems = [];
|
|
2060
|
-
const add = (level, message, el) => problems.push({ level, message, element: el ? describe(el) : void 0, el });
|
|
2061
|
-
const inProduct = (el) => Boolean(el.closest(sel("product")));
|
|
2062
|
-
const listSel = `${sel("collection")},${sel("products")},${sel("recommendations")}`;
|
|
2063
|
-
qsa(root, sel("product")).forEach((el) => {
|
|
2064
|
-
if (el.closest(listSel) && el.closest(sel("list"))) return;
|
|
2065
|
-
const { key, fromUrl, raw } = resolveProductKey(el);
|
|
2066
|
-
if (!raw) add("error", `${attr("product")} has no value. Bind it to the Shopify handle (CMS field), or use "url:handle".`, el);
|
|
2067
|
-
else if (fromUrl && !key) add("warn", `${attr("product")}="${raw}" but this URL has no value for it (e.g. ?handle=my-product).`, el);
|
|
2068
|
-
});
|
|
2069
|
-
const productOnly = ["bind", "option-list", "option", "variant-select", "add-to-cart", "purchase", "plan-list", "plan", "qty", "line-attribute"];
|
|
2070
|
-
productOnly.forEach((name) => {
|
|
2071
|
-
qsa(root, sel(name)).forEach((el) => {
|
|
2072
|
-
if (inProduct(el)) return;
|
|
2073
|
-
if (name === "line-attribute" && el.closest(sel("group"))) return;
|
|
2074
|
-
add("warn", `${attr(name)} outside any ${attr("product")}: it does nothing here.`, el);
|
|
2075
|
-
});
|
|
2076
|
-
});
|
|
2077
|
-
qsa(root, sel("group-add")).forEach((el) => {
|
|
2078
|
-
if (!el.closest(sel("group"))) add("warn", `${attr("group-add")} outside a ${attr("group")}.`, el);
|
|
2079
|
-
});
|
|
2080
|
-
[...qsa(root, sel("option-list")), ...qsa(root, sel("plan-list")), ...qsa(root, sel("cart-lines")), ...qsa(root, sel("list"))].forEach((el) => {
|
|
2081
|
-
if (!el.firstElementChild && !hasTemplate(el)) {
|
|
2082
|
-
add("warn", "Repeater without a template child: put one example element inside it.", el);
|
|
2083
|
-
}
|
|
2084
|
-
});
|
|
2085
|
-
qsa(root, listSel).forEach((el) => {
|
|
2086
|
-
if (!qsa(el, sel("list")).length) add("error", `List without a ${attr("list")} child: products have nowhere to render.`, el);
|
|
2087
|
-
});
|
|
2088
|
-
const known = /* @__PURE__ */ new Set([
|
|
2089
|
-
"in-stock",
|
|
2090
|
-
"out-of-stock",
|
|
2091
|
-
"backorder",
|
|
2092
|
-
"low-stock",
|
|
2093
|
-
"on-sale",
|
|
2094
|
-
"not-on-sale",
|
|
2095
|
-
"has-variants",
|
|
2096
|
-
"single-variant",
|
|
2097
|
-
"subscription-available",
|
|
2098
|
-
"subscription-unavailable",
|
|
2099
|
-
"subscription-only",
|
|
2100
|
-
"one-time-available",
|
|
2101
|
-
"subscription-selected",
|
|
2102
|
-
"one-time-selected",
|
|
2103
|
-
"has-savings",
|
|
2104
|
-
"loading",
|
|
2105
|
-
"ready",
|
|
2106
|
-
"error",
|
|
2107
|
-
"not-found",
|
|
2108
|
-
"cart-empty",
|
|
2109
|
-
"cart-filled",
|
|
2110
|
-
"cart-has-subscription",
|
|
2111
|
-
"cart-no-subscription",
|
|
2112
|
-
"cart-has-discount",
|
|
2113
|
-
"cart-error",
|
|
2114
|
-
"list-empty",
|
|
2115
|
-
"list-filled"
|
|
2116
|
-
]);
|
|
2117
|
-
qsa(root, sel("show")).forEach((el) => {
|
|
2118
|
-
const k = get(el, "show");
|
|
2119
|
-
if (!known.has(k)) add("warn", `Unknown ${attr("show")}="${k}".`, el);
|
|
2120
|
-
});
|
|
2121
|
-
qsa(root, sel("bind")).forEach((el) => {
|
|
2122
|
-
const k = get(el, "bind") || "";
|
|
2123
|
-
if (!k.startsWith("metafield:")) return;
|
|
2124
|
-
const ref = k.slice(10);
|
|
2125
|
-
const listed = (cfg.metafields || []).some((m) => (typeof m === "string" ? m : `${m.namespace}.${m.key}`) === ref);
|
|
2126
|
-
if (!listed) add("error", `${attr("bind")}="${k}" but "${ref}" is not in config.metafields, so it stays empty.`, el);
|
|
2127
|
-
});
|
|
2128
|
-
if (qsa(root, sel("cart-open")).length && !qsa(root, sel("cart-drawer")).length) {
|
|
2129
|
-
add("warn", `${attr("cart-open")} exists but there is no ${attr("cart-drawer")} on this page.`);
|
|
2130
|
-
}
|
|
2131
|
-
if (qsa(root, sel("cart-drawer")).length > 1) add("warn", `More than one ${attr("cart-drawer")}.`);
|
|
2132
|
-
qsa(root, `[${attr("state")}="error"]`).forEach((el) => {
|
|
2133
|
-
if (el.matches(boundary())) add("error", `Scope failed to load (${get(el, "error") || "error"}).`, el);
|
|
2134
|
-
});
|
|
2135
|
-
return problems;
|
|
2136
|
-
}
|
|
2137
|
-
async function diagnose({ root = document, silent = false, version, now = /* @__PURE__ */ new Date() } = {}) {
|
|
2138
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
2139
|
-
const report = {
|
|
2140
|
-
version,
|
|
2141
|
-
config: {
|
|
2142
|
-
domain: cfg.domain || "(missing)",
|
|
2143
|
-
token: mask(cfg.token),
|
|
2144
|
-
apiVersion: cfg.apiVersion,
|
|
2145
|
-
endpoint: cfg.domain ? endpoint() : null,
|
|
2146
|
-
country: cfg.country,
|
|
2147
|
-
language: cfg.language,
|
|
2148
|
-
locale: cfg.locale,
|
|
2149
|
-
markets: cfg.markets || null,
|
|
2150
|
-
prefix: cfg.prefix,
|
|
2151
|
-
productPath: cfg.productPath,
|
|
2152
|
-
metafields: cfg.metafields,
|
|
2153
|
-
autoMount: cfg.autoMount,
|
|
2154
|
-
observe: cfg.observe,
|
|
2155
|
-
onError: typeof cfg.onError === "function"
|
|
2156
|
-
},
|
|
2157
|
-
apiVersion: apiVersionStatus(cfg.apiVersion, now),
|
|
2158
|
-
market: ((_a = cfg.markets) == null ? void 0 : _a.detect) ? detectMarket(cfg.markets) : null,
|
|
2159
|
-
reachable: false,
|
|
2160
|
-
shop: null,
|
|
2161
|
-
latencyMs: null,
|
|
2162
|
-
products: [],
|
|
2163
|
-
sellingPlans: [],
|
|
2164
|
-
problems: []
|
|
2165
|
-
};
|
|
2166
|
-
if (!isConfigured()) {
|
|
2167
|
-
report.problems.push({ level: "error", message: "TETHER_CONFIG needs domain and token." });
|
|
2168
|
-
} else {
|
|
2169
|
-
const t0 = Date.now();
|
|
2170
|
-
try {
|
|
2171
|
-
const data = await gql("query TetherDiagnose { shop { name primaryDomain { url } paymentSettings { currencyCode } } }", {}, { context: false });
|
|
2172
|
-
report.reachable = true;
|
|
2173
|
-
report.shop = { name: (_b = data.shop) == null ? void 0 : _b.name, url: (_d = (_c = data.shop) == null ? void 0 : _c.primaryDomain) == null ? void 0 : _d.url, currency: (_f = (_e = data.shop) == null ? void 0 : _e.paymentSettings) == null ? void 0 : _f.currencyCode };
|
|
2174
|
-
} catch (err) {
|
|
2175
|
-
const hint = err.status === 401 || err.status === 403 ? "Token rejected: use the public Storefront token from the Headless channel, not an Admin token." : err.status === 404 ? "Store or API version not found: check domain (x.myshopify.com) and apiVersion." : "";
|
|
2176
|
-
report.problems.push({ level: "error", message: `Storefront API not reachable: ${err.message}${hint ? `. ${hint}` : ""}` });
|
|
2177
|
-
}
|
|
2178
|
-
report.latencyMs = Date.now() - t0;
|
|
2179
|
-
if (report.reachable) {
|
|
2180
|
-
const keys = [...new Set(qsa(root, sel("product")).map((el) => resolveProductKey(el).key).filter(Boolean))].slice(0, 20);
|
|
2181
|
-
const results = await Promise.allSettled(keys.map((k) => getProduct(k)));
|
|
2182
|
-
results.forEach((r, i) => {
|
|
2183
|
-
var _a2, _b2;
|
|
2184
|
-
if (r.status === "fulfilled") {
|
|
2185
|
-
const p = r.value;
|
|
2186
|
-
const plans = allPlans(p);
|
|
2187
|
-
report.products.push({ key: keys[i], handle: p.handle, title: p.title, plans: plans.length, requiresSellingPlan: p.requiresSellingPlan });
|
|
2188
|
-
plans.forEach((pl) => {
|
|
2189
|
-
var _a3;
|
|
2190
|
-
return report.sellingPlans.push({ product: p.handle, group: (_a3 = pl.group) == null ? void 0 : _a3.name, plan: pl.name, id: pl.id });
|
|
2191
|
-
});
|
|
2192
|
-
} else {
|
|
2193
|
-
report.products.push({ key: keys[i], error: (_a2 = r.reason) == null ? void 0 : _a2.message });
|
|
2194
|
-
report.problems.push({ level: "error", message: `Product "${keys[i]}": ${(_b2 = r.reason) == null ? void 0 : _b2.message}. Is it published to the Headless channel?` });
|
|
2195
|
-
}
|
|
2196
|
-
});
|
|
2197
|
-
}
|
|
2198
|
-
}
|
|
2199
|
-
if (!report.apiVersion.ok || ((_g = report.apiVersion.monthsLeft) != null ? _g : 99) < 3) {
|
|
2200
|
-
report.problems.push({ level: report.apiVersion.ok ? "warn" : "error", message: `apiVersion ${cfg.apiVersion}: ${report.apiVersion.note}` });
|
|
2201
|
-
}
|
|
2202
|
-
if (typeof document !== "undefined") report.problems.push(...checkMarkup(root));
|
|
2203
|
-
if (!silent) print(report);
|
|
2204
|
-
return report;
|
|
2205
|
-
}
|
|
2206
|
-
function print(r) {
|
|
2207
|
-
var _a, _b, _c;
|
|
2208
|
-
const c = console;
|
|
2209
|
-
const errors = r.problems.filter((p) => p.level === "error").length;
|
|
2210
|
-
(_a = c.group) == null ? void 0 : _a.call(c, `[${cfg.prefix}] diagnose: ${errors ? `${errors} error(s)` : "no errors"}, ${r.problems.length - errors} warning(s)`);
|
|
2211
|
-
c.log("version", r.version, "| API", r.apiVersion.version, `(${r.apiVersion.note})`);
|
|
2212
|
-
c.log("config", r.config);
|
|
2213
|
-
if (r.market) c.log("market detected", r.market, "(in use:", r.config.country, r.config.language, ")");
|
|
2214
|
-
c.log(r.reachable ? `Storefront API reachable: ${(_b = r.shop) == null ? void 0 : _b.name} (${r.latencyMs} ms)` : "Storefront API NOT reachable");
|
|
2215
|
-
if (r.products.length) (c.table || c.log)(r.products);
|
|
2216
|
-
if (r.sellingPlans.length) (c.table || c.log)(r.sellingPlans);
|
|
2217
|
-
else if (r.reachable) c.log("No selling plans on the products of this page.");
|
|
2218
|
-
r.problems.forEach((p) => (p.level === "error" ? c.error : c.warn)(p.message, p.el || ""));
|
|
2219
|
-
(_c = c.groupEnd) == null ? void 0 : _c.call(c);
|
|
2220
|
-
}
|
|
2221
|
-
|
|
2222
|
-
// src/index.js
|
|
2223
|
-
var VERSION = "0.3.6";
|
|
2224
1956
|
var mountedProducts = /* @__PURE__ */ new WeakSet();
|
|
2225
1957
|
var mountedLists = /* @__PURE__ */ new WeakSet();
|
|
2226
1958
|
var liveProducts = /* @__PURE__ */ new Set();
|
|
@@ -2234,35 +1966,8 @@ ${cartFragment()}`, { id });
|
|
|
2234
1966
|
if (!l.root.isConnected) liveLists.delete(l);
|
|
2235
1967
|
});
|
|
2236
1968
|
}
|
|
2237
|
-
var initPromise = null;
|
|
2238
|
-
var observer = null;
|
|
2239
1969
|
function within(root, selector) {
|
|
2240
|
-
|
|
2241
|
-
return [...((_a = root.matches) == null ? void 0 : _a.call(root, selector)) ? [root] : [], ...qsa(root, selector)];
|
|
2242
|
-
}
|
|
2243
|
-
function mount(root = document) {
|
|
2244
|
-
if (!isConfigured()) return 0;
|
|
2245
|
-
let count = 0;
|
|
2246
|
-
pruneLive();
|
|
2247
|
-
within(root, listSelector()).forEach((el) => {
|
|
2248
|
-
if (mountedLists.has(el)) return;
|
|
2249
|
-
mountedLists.add(el);
|
|
2250
|
-
count++;
|
|
2251
|
-
const list = new ListScope(el);
|
|
2252
|
-
liveLists.add(list);
|
|
2253
|
-
el.addEventListener(`${cfg.prefix}:list-rendered`, (e) => mount(e.detail.container));
|
|
2254
|
-
list.load();
|
|
2255
|
-
});
|
|
2256
|
-
within(root, sel("product")).forEach((el) => {
|
|
2257
|
-
if (mountProduct(el)) count++;
|
|
2258
|
-
});
|
|
2259
|
-
renderCountrySelects(root);
|
|
2260
|
-
qsa(root, sel("account-link")).forEach((a) => {
|
|
2261
|
-
a.setAttribute("href", `https://${cfg.domain}/account`);
|
|
2262
|
-
});
|
|
2263
|
-
Cart.render();
|
|
2264
|
-
log(`mounted ${count} scope(s)`, root);
|
|
2265
|
-
return count;
|
|
1970
|
+
return [...root.matches?.(selector) ? [root] : [], ...qsa(root, selector)];
|
|
2266
1971
|
}
|
|
2267
1972
|
function mountProduct(el) {
|
|
2268
1973
|
if (mountedProducts.has(el)) return false;
|
|
@@ -2290,6 +1995,7 @@ ${cartFragment()}`, { id });
|
|
|
2290
1995
|
if (existing) existing.update(p);
|
|
2291
1996
|
else new ProductScope(el, p);
|
|
2292
1997
|
applyPageTitle(el, p);
|
|
1998
|
+
if (cfg.revalidate !== false && takeStale(key)) queueRefresh(key);
|
|
2293
1999
|
}).catch((err) => {
|
|
2294
2000
|
if (!current()) return;
|
|
2295
2001
|
const notFound = isNotFound(err);
|
|
@@ -2305,74 +2011,126 @@ ${cartFragment()}`, { id });
|
|
|
2305
2011
|
});
|
|
2306
2012
|
return true;
|
|
2307
2013
|
}
|
|
2308
|
-
function
|
|
2309
|
-
const
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
const current = (cfg.country || ((_a = loc.country) == null ? void 0 : _a.isoCode) || "").toUpperCase();
|
|
2315
|
-
const countries = [...loc.availableCountries || []].sort((a, b) => a.name.localeCompare(b.name, cfg.locale || void 0));
|
|
2316
|
-
const info = countries.find((c) => c.isoCode === current) || loc.country;
|
|
2317
|
-
const fill = (tpl, c) => {
|
|
2318
|
-
var _a2, _b;
|
|
2319
|
-
return tpl.replace(/\{name\}/g, c.name).replace(/\{code\}/g, c.isoCode).replace(/\{currency\}/g, ((_a2 = c.currency) == null ? void 0 : _a2.isoCode) || "").replace(/\{symbol\}/g, ((_b = c.currency) == null ? void 0 : _b.symbol) || "");
|
|
2320
|
-
};
|
|
2321
|
-
selects.forEach((s) => {
|
|
2322
|
-
const tpl = s.getAttribute(attr("country-label")) || "{name} ({currency})";
|
|
2323
|
-
s.innerHTML = "";
|
|
2324
|
-
countries.forEach((c) => s.appendChild(new Option(fill(tpl, c), c.isoCode)));
|
|
2325
|
-
s.value = current;
|
|
2326
|
-
if (s.value !== current) s.selectedIndex = -1;
|
|
2327
|
-
s.disabled = countries.length < 2 && !s.hasAttribute(attr("always-enabled"));
|
|
2328
|
-
});
|
|
2329
|
-
binds.forEach((el) => {
|
|
2330
|
-
var _a2, _b;
|
|
2331
|
-
const k = get(el, "country-bind") || "name";
|
|
2332
|
-
const v = { name: info == null ? void 0 : info.name, code: info == null ? void 0 : info.isoCode, currency: (_a2 = info == null ? void 0 : info.currency) == null ? void 0 : _a2.isoCode, symbol: (_b = info == null ? void 0 : info.currency) == null ? void 0 : _b.symbol }[k];
|
|
2333
|
-
el.textContent = v || "";
|
|
2334
|
-
});
|
|
2335
|
-
}).catch((err) => reportError(err, "country select"));
|
|
2336
|
-
}
|
|
2337
|
-
async function setCountry(code, { language, remember = true } = {}) {
|
|
2338
|
-
const country = toCountry(code);
|
|
2339
|
-
if (!country) throw new TetherError(`Not a country code: ${code}`, { code: "INVALID_COUNTRY" });
|
|
2340
|
-
const lang = language === void 0 ? cfg.language : toLanguage(language);
|
|
2341
|
-
const changed = country !== String(cfg.country || "").toUpperCase() || lang !== cfg.language;
|
|
2342
|
-
cfg.country = country;
|
|
2343
|
-
cfg.language = lang || void 0;
|
|
2344
|
-
if (remember) rememberMarket({ country, language: lang });
|
|
2345
|
-
if (!changed) {
|
|
2346
|
-
renderCountrySelects();
|
|
2347
|
-
return Cart.state;
|
|
2348
|
-
}
|
|
2349
|
-
clearProductCache();
|
|
2350
|
-
const jobs = [];
|
|
2351
|
-
if (language !== void 0) clearLocalization();
|
|
2352
|
-
if (Cart.id()) jobs.push(Cart.setBuyerIdentity({ countryCode: country }, { create: false }).catch((err) => reportError(err, "cart country")));
|
|
2014
|
+
function applyPageTitle(el, p) {
|
|
2015
|
+
const tpl = get(el, "page-title");
|
|
2016
|
+
if (!tpl || typeof document === "undefined") return;
|
|
2017
|
+
document.title = tpl.replace(/\{title\}/g, p.title).replace(/\{vendor\}/g, p.vendor || "").replace(/\{type\}/g, p.productType || "");
|
|
2018
|
+
}
|
|
2019
|
+
function retryFailed() {
|
|
2353
2020
|
pruneLive();
|
|
2354
|
-
liveLists.forEach((list) => jobs.push(list.load()));
|
|
2355
2021
|
liveProducts.forEach((el) => {
|
|
2356
|
-
if (el.
|
|
2022
|
+
if (el.getAttribute(attr("state")) !== "error" || el.getAttribute(attr("error")) === "not-found") return;
|
|
2357
2023
|
mountedProducts.delete(el);
|
|
2358
2024
|
mountProduct(el);
|
|
2359
2025
|
});
|
|
2360
|
-
renderCountrySelects();
|
|
2361
|
-
emit("country-changed", { country, language: cfg.language });
|
|
2362
|
-
await Promise.all(jobs);
|
|
2363
|
-
return Cart.state;
|
|
2364
2026
|
}
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
if (!
|
|
2368
|
-
|
|
2027
|
+
var refreshQueue = /* @__PURE__ */ new Set();
|
|
2028
|
+
function queueRefresh(key) {
|
|
2029
|
+
if (!refreshQueue.size) setTimeout(flushRefresh, 0);
|
|
2030
|
+
refreshQueue.add(key);
|
|
2031
|
+
}
|
|
2032
|
+
function flushRefresh() {
|
|
2033
|
+
const keys = [...refreshQueue];
|
|
2034
|
+
refreshQueue = /* @__PURE__ */ new Set();
|
|
2035
|
+
refreshProducts(keys).forEach((promise, i) => promise.then((fresh) => {
|
|
2036
|
+
liveProducts.forEach((el) => {
|
|
2037
|
+
const scope = scopes.get(el);
|
|
2038
|
+
if (!scope || !el.isConnected) return;
|
|
2039
|
+
const k = resolveProductKey(el).key;
|
|
2040
|
+
if (k !== keys[i] && k !== fresh.handle && k !== fresh.id) return;
|
|
2041
|
+
if (JSON.stringify(scope.product) !== JSON.stringify(fresh)) scope.update(fresh);
|
|
2042
|
+
});
|
|
2043
|
+
}).catch((err) => reportError(err, `refresh ${keys[i]}`)));
|
|
2044
|
+
}
|
|
2045
|
+
var readScripts = /* @__PURE__ */ new WeakSet();
|
|
2046
|
+
function hydrateFromPage(root = document) {
|
|
2047
|
+
qsa(root.ownerDocument || root, 'script[type="application/json"][data-tether-hydrate]').forEach((s) => {
|
|
2048
|
+
if (readScripts.has(s)) return;
|
|
2049
|
+
readScripts.add(s);
|
|
2050
|
+
try {
|
|
2051
|
+
const data = JSON.parse(s.textContent);
|
|
2052
|
+
rememberOptionNames(data.products);
|
|
2053
|
+
const same = (a, b) => String(a || "").toUpperCase() === String(b || "").toUpperCase();
|
|
2054
|
+
if (same(data.country, cfg.country) && same(data.language, cfg.language)) hydrateProducts(data.products);
|
|
2055
|
+
else log("hydration skipped: other market");
|
|
2056
|
+
} catch (err) {
|
|
2057
|
+
warn("bad data-tether-hydrate", err);
|
|
2058
|
+
}
|
|
2059
|
+
});
|
|
2060
|
+
}
|
|
2061
|
+
|
|
2062
|
+
// src/lazy-iife.js
|
|
2063
|
+
var { cfg: cfg2 } = config_exports;
|
|
2064
|
+
var coreModules = {
|
|
2065
|
+
"config.js": config_exports,
|
|
2066
|
+
"client.js": client_exports,
|
|
2067
|
+
"dom.js": dom_exports,
|
|
2068
|
+
"fragments.js": fragments_exports,
|
|
2069
|
+
"loader.js": loader_exports,
|
|
2070
|
+
"selling-plans.js": selling_plans_exports,
|
|
2071
|
+
"status.js": status_exports,
|
|
2072
|
+
"cart.js": cart_exports,
|
|
2073
|
+
"product.js": product_exports,
|
|
2074
|
+
"collection.js": collection_exports,
|
|
2075
|
+
"mounting.js": mounting_exports
|
|
2076
|
+
};
|
|
2077
|
+
var self = typeof document !== "undefined" ? document.currentScript?.src || "" : "";
|
|
2078
|
+
var parts = {};
|
|
2079
|
+
function loadPart(name) {
|
|
2080
|
+
if (parts[name]) return parts[name];
|
|
2081
|
+
const base = cfg2.partsUrl || self.replace(/[^/]*$/, "");
|
|
2082
|
+
const min = /\.min\.js(\?|$)/.test(self) || !self ? ".min" : "";
|
|
2083
|
+
const url = `${base.replace(/\/?$/, "/")}tether.${name}${min}.js`;
|
|
2084
|
+
parts[name] = new Promise((resolve, reject) => {
|
|
2085
|
+
const s = document.createElement("script");
|
|
2086
|
+
s.src = url;
|
|
2087
|
+
s.async = true;
|
|
2088
|
+
s.onload = () => window.Tether?.__parts?.[name] ? resolve(window.Tether.__parts[name]) : reject(new Error(`${url} did not register`));
|
|
2089
|
+
s.onerror = () => {
|
|
2090
|
+
delete parts[name];
|
|
2091
|
+
reject(new Error(`Could not load ${url}. Host it next to the Tether script, or set partsUrl.`));
|
|
2092
|
+
};
|
|
2093
|
+
document.head.appendChild(s);
|
|
2094
|
+
});
|
|
2095
|
+
return parts[name];
|
|
2096
|
+
}
|
|
2097
|
+
|
|
2098
|
+
// src/index.js
|
|
2099
|
+
var VERSION = "0.5.0";
|
|
2100
|
+
var initPromise = null;
|
|
2101
|
+
var observer = null;
|
|
2102
|
+
function mount(root = document) {
|
|
2103
|
+
if (!isConfigured()) return 0;
|
|
2104
|
+
let count = 0;
|
|
2105
|
+
pruneLive();
|
|
2106
|
+
hydrateFromPage(root);
|
|
2107
|
+
within(root, listSelector()).forEach((el) => {
|
|
2108
|
+
if (mountedLists.has(el)) return;
|
|
2109
|
+
mountedLists.add(el);
|
|
2110
|
+
count++;
|
|
2111
|
+
const list = new ListScope(el);
|
|
2112
|
+
liveLists.add(list);
|
|
2113
|
+
el.addEventListener(`${cfg.prefix}:list-rendered`, (e) => mount(e.detail.container));
|
|
2114
|
+
list.load();
|
|
2115
|
+
});
|
|
2116
|
+
within(root, sel("product")).forEach((el) => {
|
|
2117
|
+
if (mountProduct(el)) count++;
|
|
2118
|
+
});
|
|
2119
|
+
if (within(root, `${sel("country-select")},${sel("country-bind")}`).length) {
|
|
2120
|
+
loadPart("markets").then((m) => m.renderCountrySelects(root)).catch((err) => reportError(err, "markets"));
|
|
2121
|
+
}
|
|
2122
|
+
qsa(root, sel("account-link")).forEach((a) => {
|
|
2123
|
+
a.setAttribute("href", `https://${cfg.domain}/account`);
|
|
2124
|
+
});
|
|
2125
|
+
Cart.render();
|
|
2126
|
+
log(`mounted ${count} scope(s)`, root);
|
|
2127
|
+
return count;
|
|
2369
2128
|
}
|
|
2370
2129
|
function unmount() {
|
|
2371
2130
|
Cart.close();
|
|
2372
2131
|
}
|
|
2373
2132
|
function getScope(el) {
|
|
2374
|
-
|
|
2375
|
-
const root = (_a = el == null ? void 0 : el.closest) == null ? void 0 : _a.call(el, sel("product"));
|
|
2133
|
+
const root = el?.closest?.(sel("product"));
|
|
2376
2134
|
return root ? scopes.get(root) || null : null;
|
|
2377
2135
|
}
|
|
2378
2136
|
var controlsBound = false;
|
|
@@ -2380,9 +2138,8 @@ ${cartFragment()}`, { id });
|
|
|
2380
2138
|
if (controlsBound) return;
|
|
2381
2139
|
controlsBound = true;
|
|
2382
2140
|
document.addEventListener("click", (e) => {
|
|
2383
|
-
var _a;
|
|
2384
2141
|
const t = e.target;
|
|
2385
|
-
if (!
|
|
2142
|
+
if (!t?.closest) return;
|
|
2386
2143
|
if (t.closest(sel("cart-open"))) {
|
|
2387
2144
|
e.preventDefault();
|
|
2388
2145
|
Cart.toggle();
|
|
@@ -2420,7 +2177,7 @@ ${cartFragment()}`, { id });
|
|
|
2420
2177
|
const removeCode = t.closest(sel("discount-remove"));
|
|
2421
2178
|
if (removeCode) {
|
|
2422
2179
|
e.preventDefault();
|
|
2423
|
-
const code =
|
|
2180
|
+
const code = removeCode.closest(sel("discount-code"))?.getAttribute(attr("discount-code"));
|
|
2424
2181
|
if (code) Cart.removeDiscount(code).catch((err) => reportError(err, "discount"));
|
|
2425
2182
|
return;
|
|
2426
2183
|
}
|
|
@@ -2432,9 +2189,9 @@ ${cartFragment()}`, { id });
|
|
|
2432
2189
|
});
|
|
2433
2190
|
document.addEventListener("change", (e) => {
|
|
2434
2191
|
const t = e.target;
|
|
2435
|
-
if (!
|
|
2192
|
+
if (!t?.closest) return;
|
|
2436
2193
|
const lineEl = t.closest(dataKeySel());
|
|
2437
|
-
const id = lineEl
|
|
2194
|
+
const id = lineEl?.getAttribute(`data-${cfg.prefix}-key`);
|
|
2438
2195
|
if (id && t.matches(`input${sel("line", "quantity")}`)) {
|
|
2439
2196
|
Cart.updateLine(id, parseInt(t.value, 10) || 0).catch((err) => reportError(err, "cart line"));
|
|
2440
2197
|
} else if (id && t.matches(`select${sel("line-plan")}`)) {
|
|
@@ -2446,18 +2203,18 @@ ${cartFragment()}`, { id });
|
|
|
2446
2203
|
}
|
|
2447
2204
|
});
|
|
2448
2205
|
document.addEventListener("submit", (e) => {
|
|
2449
|
-
|
|
2450
|
-
const form = (_b = (_a = e.target).closest) == null ? void 0 : _b.call(_a, sel("discount-form"));
|
|
2206
|
+
const form = e.target.closest?.(sel("discount-form"));
|
|
2451
2207
|
if (!form) return;
|
|
2452
2208
|
e.preventDefault();
|
|
2453
2209
|
const input = form.querySelector(sel("discount-input")) || form.querySelector("input");
|
|
2454
|
-
const code = input
|
|
2210
|
+
const code = input?.value.trim();
|
|
2455
2211
|
if (!code) return;
|
|
2456
2212
|
busy(form, async () => {
|
|
2457
|
-
var _a2;
|
|
2458
2213
|
try {
|
|
2459
2214
|
const cart = await Cart.applyDiscount(code);
|
|
2460
|
-
const ok = (
|
|
2215
|
+
const ok = (cart?.discountCodes || []).find((d) => d.code.toLowerCase() === code.toLowerCase())?.applicable;
|
|
2216
|
+
if (!ok) await Cart.removeDiscount(code).catch(() => {
|
|
2217
|
+
});
|
|
2461
2218
|
form.classList.toggle("is-invalid", !ok);
|
|
2462
2219
|
emit(ok ? "discount-applied" : "discount-invalid", { code }, form);
|
|
2463
2220
|
if (ok && input) input.value = "";
|
|
@@ -2466,9 +2223,36 @@ ${cartFragment()}`, { id });
|
|
|
2466
2223
|
reportError(err, "discount");
|
|
2467
2224
|
}
|
|
2468
2225
|
});
|
|
2226
|
+
}, true);
|
|
2227
|
+
document.addEventListener("input", (e) => {
|
|
2228
|
+
e.target.closest?.(sel("discount-form"))?.classList.remove("is-invalid");
|
|
2229
|
+
});
|
|
2230
|
+
if (cfg.astro !== false) {
|
|
2231
|
+
document.addEventListener("astro:page-load", () => mount(document));
|
|
2232
|
+
document.addEventListener("astro:before-swap", () => unmount());
|
|
2233
|
+
}
|
|
2234
|
+
window.addEventListener("online", () => {
|
|
2235
|
+
retryFailed();
|
|
2236
|
+
liveLists.forEach((l) => {
|
|
2237
|
+
if (l.root.getAttribute(attr("state")) === "error") l.load();
|
|
2238
|
+
});
|
|
2469
2239
|
});
|
|
2470
2240
|
document.addEventListener("keydown", (e) => {
|
|
2471
|
-
if (
|
|
2241
|
+
if (!Cart.isOpen()) return;
|
|
2242
|
+
if (e.key === "Escape") {
|
|
2243
|
+
Cart.close();
|
|
2244
|
+
return;
|
|
2245
|
+
}
|
|
2246
|
+
if (e.key !== "Tab" || cfg.cartFocus === false) return;
|
|
2247
|
+
const d = Cart.drawers().find((x) => x.classList.contains(cfg.cartDrawerOpenClass));
|
|
2248
|
+
const f = d ? focusables(d) : [];
|
|
2249
|
+
if (!f.length) return;
|
|
2250
|
+
const first = f[0];
|
|
2251
|
+
const last = f[f.length - 1];
|
|
2252
|
+
if (!d.contains(document.activeElement) || e.shiftKey && document.activeElement === first || !e.shiftKey && document.activeElement === last) {
|
|
2253
|
+
e.preventDefault();
|
|
2254
|
+
(e.shiftKey ? last : first).focus();
|
|
2255
|
+
}
|
|
2472
2256
|
});
|
|
2473
2257
|
}
|
|
2474
2258
|
function dataKeySel() {
|
|
@@ -2480,10 +2264,7 @@ ${cartFragment()}`, { id });
|
|
|
2480
2264
|
const name = get(group, "group");
|
|
2481
2265
|
const extra = toAttributes(parseJSONAttr(group, "line-attributes"));
|
|
2482
2266
|
const groupAttrs = name ? [{ key: "_group", value: name }, ...extra] : extra;
|
|
2483
|
-
const lines = qsa(group, sel("product")).map((el) => scopes.get(el)).filter((s) => {
|
|
2484
|
-
var _a;
|
|
2485
|
-
return s && ((_a = s.variant) == null ? void 0 : _a.availableForSale) && s.quantity > 0;
|
|
2486
|
-
}).map((s) => {
|
|
2267
|
+
const lines = qsa(group, sel("product")).map((el) => scopes.get(el)).filter((s) => s && s.variant?.availableForSale && s.quantity > 0).map((s) => {
|
|
2487
2268
|
const l = s.getLine();
|
|
2488
2269
|
l.attributes = [...l.attributes || [], ...groupAttrs];
|
|
2489
2270
|
return l;
|
|
@@ -2502,7 +2283,6 @@ ${cartFragment()}`, { id });
|
|
|
2502
2283
|
}
|
|
2503
2284
|
}
|
|
2504
2285
|
function init(overrides = {}) {
|
|
2505
|
-
var _a;
|
|
2506
2286
|
if (initPromise && !Object.keys(overrides).length) return initPromise;
|
|
2507
2287
|
configure({ ...window.TETHER_CONFIG || {}, ...overrides });
|
|
2508
2288
|
if (!isConfigured()) {
|
|
@@ -2510,17 +2290,11 @@ ${cartFragment()}`, { id });
|
|
|
2510
2290
|
initPromise = Promise.resolve(null);
|
|
2511
2291
|
return initPromise;
|
|
2512
2292
|
}
|
|
2513
|
-
const market = detectMarket(cfg.markets);
|
|
2514
|
-
if (market.country) cfg.country = market.country;
|
|
2515
|
-
if (market.language) cfg.language = market.language;
|
|
2516
|
-
if (market.locale && !overrides.locale && !((_a = window.TETHER_CONFIG) == null ? void 0 : _a.locale)) cfg.locale = market.locale;
|
|
2517
|
-
if (market.sources && Object.keys(market.sources).length) log("market", market);
|
|
2518
2293
|
bindControls();
|
|
2519
2294
|
if (cfg.observe) startObserver();
|
|
2520
|
-
initPromise = Cart.init().then(async (cart) => {
|
|
2521
|
-
var _a2;
|
|
2295
|
+
initPromise = applyMarket(overrides).then(() => Cart.init()).then(async (cart) => {
|
|
2522
2296
|
const want = cfg.country ? String(cfg.country).toUpperCase() : null;
|
|
2523
|
-
if (cart && want &&
|
|
2297
|
+
if (cart && want && cart.buyerIdentity?.countryCode && cart.buyerIdentity.countryCode !== want) {
|
|
2524
2298
|
try {
|
|
2525
2299
|
cart = await Cart.setBuyerIdentity({ countryCode: want }, { create: false });
|
|
2526
2300
|
} catch (err) {
|
|
@@ -2532,6 +2306,33 @@ ${cartFragment()}`, { id });
|
|
|
2532
2306
|
});
|
|
2533
2307
|
return initPromise;
|
|
2534
2308
|
}
|
|
2309
|
+
async function applyMarket(overrides) {
|
|
2310
|
+
const m = cfg.markets || {};
|
|
2311
|
+
if (m.detect === false) return;
|
|
2312
|
+
let market = null;
|
|
2313
|
+
if (m.detect) {
|
|
2314
|
+
try {
|
|
2315
|
+
market = (await loadPart("markets")).detectMarket(m);
|
|
2316
|
+
} catch (err) {
|
|
2317
|
+
reportError(err, "markets");
|
|
2318
|
+
return;
|
|
2319
|
+
}
|
|
2320
|
+
} else if (m.remember !== false) {
|
|
2321
|
+
try {
|
|
2322
|
+
market = JSON.parse(localStorage.getItem(`${cfg.prefix}:market:${cfg.domain}`) || "null");
|
|
2323
|
+
} catch {
|
|
2324
|
+
}
|
|
2325
|
+
if (market && !/^[A-Z]{2}$/.test(market.country || "")) market = null;
|
|
2326
|
+
}
|
|
2327
|
+
if (!market) return;
|
|
2328
|
+
if (market.country) cfg.country = market.country;
|
|
2329
|
+
if (market.language) cfg.language = market.language;
|
|
2330
|
+
if (market.locale && !overrides.locale) cfg.locale = market.locale;
|
|
2331
|
+
log("market", market);
|
|
2332
|
+
}
|
|
2333
|
+
function setCountry(code, opts) {
|
|
2334
|
+
return loadPart("markets").then((m) => m.setCountry(code, opts));
|
|
2335
|
+
}
|
|
2535
2336
|
function startObserver() {
|
|
2536
2337
|
if (observer || typeof MutationObserver === "undefined") return;
|
|
2537
2338
|
let t = null;
|
|
@@ -2573,13 +2374,16 @@ ${cartFragment()}`, { id });
|
|
|
2573
2374
|
normalizeProductKey,
|
|
2574
2375
|
clearProductCache,
|
|
2575
2376
|
formatMoney,
|
|
2377
|
+
imageUrl,
|
|
2576
2378
|
plans: selling_plans_exports,
|
|
2577
2379
|
TetherError,
|
|
2578
2380
|
resolveProductKey,
|
|
2579
2381
|
setCountry,
|
|
2580
|
-
detectMarket: (markets) => detectMarket(markets || cfg.markets),
|
|
2581
|
-
getLocalization,
|
|
2582
|
-
diagnose: (opts) => diagnose({ ...opts, version: VERSION }),
|
|
2382
|
+
detectMarket: (markets) => loadPart("markets").then((m) => m.detectMarket(markets || cfg.markets)),
|
|
2383
|
+
getLocalization: () => loadPart("markets").then((m) => m.getLocalization()),
|
|
2384
|
+
diagnose: (opts) => loadPart("diagnose").then((m) => m.diagnose({ ...opts, version: VERSION })),
|
|
2385
|
+
__core: coreModules,
|
|
2386
|
+
__parts: {},
|
|
2583
2387
|
on(name, fn) {
|
|
2584
2388
|
const h = (e) => fn(e.detail, e);
|
|
2585
2389
|
document.addEventListener(`${cfg.prefix}:${name}`, h);
|
|
@@ -2589,7 +2393,7 @@ ${cartFragment()}`, { id });
|
|
|
2589
2393
|
var index_default = api;
|
|
2590
2394
|
if (typeof window !== "undefined" && typeof document !== "undefined") {
|
|
2591
2395
|
window.Tether = api;
|
|
2592
|
-
const auto = window.TETHER_CONFIG && window.TETHER_CONFIG.autoMount !== false;
|
|
2396
|
+
const auto = window.TETHER_CONFIG && window.TETHER_CONFIG.autoMount !== false && !window.TETHER_INTEGRATION;
|
|
2593
2397
|
if (auto) {
|
|
2594
2398
|
const start = () => init().then(() => mount(document));
|
|
2595
2399
|
if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", start, { once: true });
|