@occluze/tether 0.6.8 → 0.7.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 +11 -11
- package/dist/esm/astro.js +2 -2
- package/dist/esm/chunks/account-UIPWJQ3Y.js +1 -0
- package/dist/esm/chunks/account-pages-6ROA6Y4V.js +1 -0
- package/dist/esm/chunks/analytics-2V632IRV.js +1 -0
- package/dist/esm/chunks/chunk-4GCXM764.js +1 -0
- package/dist/esm/chunks/chunk-56FCLJJD.js +1 -0
- package/dist/esm/chunks/chunk-5Z5O3767.js +1 -0
- package/dist/esm/chunks/chunk-6OZJCAGZ.js +1 -0
- package/dist/esm/chunks/chunk-A5SYCVQM.js +1 -0
- package/dist/esm/chunks/chunk-BA4LQJJQ.js +1 -0
- package/dist/esm/chunks/chunk-BFTZR4OB.js +4 -0
- package/dist/esm/chunks/chunk-C5JYQCWH.js +1 -0
- package/dist/esm/chunks/chunk-E5YO6274.js +1 -0
- package/dist/esm/chunks/chunk-MPDWM2NN.js +1 -0
- package/dist/esm/chunks/chunk-T44MUL4Y.js +1 -0
- package/dist/esm/chunks/chunk-YLUULATY.js +1 -0
- package/dist/esm/chunks/diagnose-3FEKRIEK.js +14 -0
- package/dist/esm/chunks/extras-6POFBNOP.js +1 -0
- package/dist/esm/chunks/markets-ui-FD6NI2OG.js +1 -0
- package/dist/esm/chunks/search-UJ73YBQ5.js +1 -0
- package/dist/esm/index.js +1 -1
- package/dist/esm/server.js +2 -2
- package/dist/presets/barba.esm.js +19 -23
- package/dist/tether.account-pages.js +320 -365
- package/dist/tether.account-pages.min.js +1 -1
- package/dist/tether.account.js +242 -491
- package/dist/tether.account.min.js +1 -1
- package/dist/tether.analytics.js +66 -88
- package/dist/tether.analytics.min.js +1 -1
- package/dist/tether.diagnose.js +36 -33
- package/dist/tether.diagnose.min.js +2 -2
- package/dist/tether.extras.js +28 -36
- package/dist/tether.extras.min.js +1 -1
- package/dist/tether.js +1270 -894
- package/dist/tether.markets.js +73 -90
- package/dist/tether.markets.min.js +1 -1
- package/dist/tether.min.js +0 -0
- package/dist/tether.min.js.map +4 -4
- package/dist/tether.search.js +44 -68
- package/dist/tether.search.min.js +1 -1
- package/dist/types/server/src/shared/account-orders.d.ts +44 -0
- package/dist/types/server/src/shared/account-routes.d.ts +153 -0
- package/dist/types/server/src/shared/api-versions.d.ts +13 -0
- package/dist/types/server/src/shared/error-message.d.ts +2 -0
- package/dist/types/server/src/shared/json.d.ts +25 -0
- package/dist/types/server/src/shared/market-codes.d.ts +16 -0
- package/dist/types/src/account/addresses-view.d.ts +5 -0
- package/dist/types/src/account/answers.d.ts +40 -0
- package/dist/types/src/account/markup.d.ts +19 -0
- package/dist/types/src/account/operations.d.ts +41 -0
- package/dist/types/src/account/order-lines.d.ts +13 -0
- package/dist/types/src/account/order-list.d.ts +12 -0
- package/dist/types/src/account/order-page.d.ts +6 -0
- package/dist/types/src/account/page-answers.d.ts +182 -0
- package/dist/types/src/account/page-events.d.ts +2 -0
- package/dist/types/src/account/server-client.d.ts +60 -0
- package/dist/types/src/account/subscriptions-view.d.ts +2 -0
- package/dist/types/src/account-api.d.ts +18 -0
- package/dist/types/src/account-marks.d.ts +15 -0
- package/dist/types/src/account-pages.d.ts +8 -0
- package/dist/types/src/account.d.ts +64 -0
- package/dist/types/src/amounts.d.ts +12 -0
- package/dist/types/src/analytics-shopify.d.ts +41 -0
- package/dist/types/src/analytics.d.ts +2 -0
- package/dist/types/src/api-version.d.ts +28 -0
- package/dist/types/src/astro.d.ts +42 -0
- package/dist/types/src/cart/cart-shape.d.ts +43 -0
- package/dist/types/src/cart/checkout.d.ts +16 -0
- package/dist/types/src/cart/drawer.d.ts +18 -0
- package/dist/types/src/cart/mutations.d.ts +79 -0
- package/dist/types/src/cart/render.d.ts +17 -0
- package/dist/types/src/cart/store.d.ts +182 -0
- package/dist/types/src/cart.d.ts +106 -0
- package/dist/types/src/client.d.ts +100 -0
- package/dist/types/src/collection.d.ts +25 -0
- package/dist/types/src/config-sections.d.ts +40 -0
- package/dist/types/src/config.d.ts +185 -0
- package/dist/types/src/controls/cart-controls.d.ts +2 -0
- package/dist/types/src/controls/discount.d.ts +2 -0
- package/dist/types/src/controls/extras.d.ts +5 -0
- package/dist/types/src/controls/navigation.d.ts +12 -0
- package/dist/types/src/diagnose-markup.d.ts +19 -0
- package/dist/types/src/diagnose.d.ts +60 -0
- package/dist/types/src/dom.d.ts +75 -0
- package/dist/types/src/extras.d.ts +21 -0
- package/dist/types/src/fragments.d.ts +158 -0
- package/dist/types/src/globals.d.ts +15 -0
- package/dist/types/src/images.d.ts +20 -0
- package/dist/types/src/index.d.ts +167 -0
- package/dist/types/src/initial.d.ts +56 -0
- package/dist/types/src/lazy.d.ts +28 -0
- package/dist/types/src/loader.d.ts +84 -0
- package/dist/types/src/market-codes.d.ts +6 -0
- package/dist/types/src/markets-ui.d.ts +32 -0
- package/dist/types/src/markets.d.ts +72 -0
- package/dist/types/src/markup.d.ts +131 -0
- package/dist/types/src/mounting.d.ts +46 -0
- package/dist/types/src/page-market.d.ts +27 -0
- package/dist/types/src/presets/barba.d.ts +56 -0
- package/dist/types/src/product-events.d.ts +2 -0
- package/dist/types/src/product-render.d.ts +17 -0
- package/dist/types/src/product-shape.d.ts +88 -0
- package/dist/types/src/product-state.d.ts +110 -0
- package/dist/types/src/product.d.ts +66 -0
- package/dist/types/src/repeaters.d.ts +16 -0
- package/dist/types/src/search.d.ts +33 -0
- package/dist/types/src/selling-plans.d.ts +64 -0
- package/dist/types/src/server.d.ts +88 -0
- package/dist/types/src/shopify-values.d.ts +46 -0
- package/dist/types/src/status.d.ts +33 -0
- package/dist/types/src/storage.d.ts +12 -0
- package/dist/types/src/store-html.d.ts +39 -0
- package/dist/types/src/storefront/catalog.d.ts +38 -0
- package/dist/types/src/storefront/json-ld.d.ts +20 -0
- package/dist/types/src/variant-render.d.ts +14 -0
- package/package.json +24 -23
- package/src/account/addresses-view.ts +58 -0
- package/src/account/answers.ts +79 -0
- package/src/account/markup.ts +36 -0
- package/src/account/operations.ts +145 -0
- package/src/account/{order-lines.js → order-lines.ts} +20 -41
- package/src/account/order-list.ts +107 -0
- package/src/account/order-page.ts +137 -0
- package/src/account/page-answers.ts +317 -0
- package/src/account/{page-events.js → page-events.ts} +55 -90
- package/src/account/server-client.ts +183 -0
- package/src/account/{subscriptions-view.js → subscriptions-view.ts} +25 -68
- package/src/account-api.ts +98 -0
- package/src/account-marks.ts +35 -0
- package/src/{account-pages.js → account-pages.ts} +29 -37
- package/src/account.ts +284 -0
- package/src/amounts.ts +64 -0
- package/src/{analytics-shopify.js → analytics-shopify.ts} +112 -107
- package/src/analytics.ts +315 -0
- package/src/{api-version.js → api-version.ts} +25 -31
- package/src/{astro.js → astro.ts} +110 -104
- package/src/cart/cart-shape.ts +25 -0
- package/src/cart/checkout.ts +101 -0
- package/src/cart/{drawer.js → drawer.ts} +18 -31
- package/src/cart/mutations.ts +237 -0
- package/src/cart/{render.js → render.ts} +68 -96
- package/src/cart/store.ts +279 -0
- package/src/{cart.js → cart.ts} +82 -128
- package/src/client.ts +391 -0
- package/src/{collection.js → collection.ts} +30 -38
- package/src/config-sections.ts +284 -0
- package/src/config.ts +480 -0
- package/src/controls/{cart-controls.js → cart-controls.ts} +22 -54
- package/src/controls/{discount.js → discount.ts} +13 -34
- package/src/controls/{extras.js → extras.ts} +15 -24
- package/src/controls/{navigation.js → navigation.ts} +24 -30
- package/src/{diagnose-markup.js → diagnose-markup.ts} +37 -72
- package/src/{diagnose.js → diagnose.ts} +95 -96
- package/src/dom.ts +268 -0
- package/src/{extras.js → extras.ts} +43 -52
- package/src/fragments.ts +261 -0
- package/src/globals.d.ts +15 -0
- package/src/{images.js → images.ts} +30 -43
- package/src/index.ts +415 -0
- package/src/initial.ts +101 -0
- package/src/lazy-iife.ts +197 -0
- package/src/lazy.ts +42 -0
- package/src/{loader.js → loader.ts} +149 -123
- package/src/market-codes.ts +6 -0
- package/src/{markets-ui.js → markets-ui.ts} +91 -68
- package/src/markets.ts +246 -0
- package/src/markup.ts +127 -0
- package/src/{mounting.js → mounting.ts} +88 -101
- package/src/{page-market.js → page-market.ts} +32 -38
- package/src/part-integrity.ts +7 -0
- package/src/parts/account-pages.ts +7 -0
- package/src/parts/account.ts +7 -0
- package/src/parts/analytics.ts +7 -0
- package/src/parts/diagnose.ts +7 -0
- package/src/parts/extras.ts +7 -0
- package/src/parts/markets.ts +7 -0
- package/src/parts/search.ts +7 -0
- package/src/presets/barba.ts +194 -0
- package/src/{product-events.js → product-events.ts} +33 -48
- package/src/{product-render.js → product-render.ts} +38 -77
- package/src/product-shape.ts +49 -0
- package/src/product-state.ts +318 -0
- package/src/{product.js → product.ts} +90 -126
- package/src/{repeaters.js → repeaters.ts} +40 -52
- package/src/{search.js → search.ts} +85 -103
- package/src/selling-plans.ts +145 -0
- package/src/server.ts +211 -0
- package/src/{shopify-values.js → shopify-values.ts} +24 -22
- package/src/{status.js → status.ts} +34 -54
- package/src/storage.ts +34 -0
- package/src/store-html.ts +359 -0
- package/src/storefront/catalog.ts +196 -0
- package/src/storefront/{json-ld.js → json-ld.ts} +45 -68
- package/src/{variant-render.js → variant-render.ts} +24 -50
- package/dist/esm/chunks/account-C3NP6HFB.js +0 -1
- package/dist/esm/chunks/account-pages-WLK374FO.js +0 -1
- package/dist/esm/chunks/analytics-G3SAWMNL.js +0 -1
- package/dist/esm/chunks/chunk-27PJJZGC.js +0 -1
- package/dist/esm/chunks/chunk-2FSXS55U.js +0 -1
- package/dist/esm/chunks/chunk-FCSWQDVV.js +0 -1
- package/dist/esm/chunks/chunk-FOMSMIAM.js +0 -1
- package/dist/esm/chunks/chunk-GZJTCLLW.js +0 -1
- package/dist/esm/chunks/chunk-HEQILZVR.js +0 -1
- package/dist/esm/chunks/chunk-JOOEXYF3.js +0 -4
- package/dist/esm/chunks/chunk-MPTQYLSM.js +0 -1
- package/dist/esm/chunks/chunk-NEHZ37TU.js +0 -3
- package/dist/esm/chunks/chunk-V74KGEJF.js +0 -1
- package/dist/esm/chunks/chunk-VT4CDUJJ.js +0 -1
- package/dist/esm/chunks/chunk-YJLWX2HK.js +0 -1
- package/dist/esm/chunks/diagnose-6VVGZNTY.js +0 -14
- package/dist/esm/chunks/extras-GJVRDFM3.js +0 -1
- package/dist/esm/chunks/markets-ui-UNB4PTA7.js +0 -1
- package/dist/esm/chunks/search-OU75RNQL.js +0 -1
- package/src/account/addresses-view.js +0 -92
- package/src/account/browser-session.js +0 -359
- package/src/account/markup.js +0 -48
- package/src/account/operations.js +0 -180
- package/src/account/order-list.js +0 -91
- package/src/account/order-page.js +0 -192
- package/src/account/server-client.js +0 -172
- package/src/account/shared.js +0 -111
- package/src/account/shopify-auth.js +0 -150
- package/src/account-api.js +0 -118
- package/src/account-links.js +0 -35
- package/src/account.js +0 -342
- package/src/amounts.js +0 -36
- package/src/analytics.js +0 -352
- package/src/cart/checkout.js +0 -97
- package/src/cart/mutations.js +0 -234
- package/src/cart/store.js +0 -326
- package/src/client.js +0 -267
- package/src/config.js +0 -349
- package/src/dom.js +0 -287
- package/src/fragments.js +0 -237
- package/src/index.js +0 -353
- package/src/initial.js +0 -87
- package/src/lazy-iife.js +0 -116
- package/src/lazy.js +0 -39
- package/src/market-codes.js +0 -35
- package/src/markets.js +0 -334
- package/src/markup.js +0 -64
- package/src/parts/account-pages.js +0 -6
- package/src/parts/account.js +0 -6
- package/src/parts/analytics.js +0 -6
- package/src/parts/diagnose.js +0 -6
- package/src/parts/extras.js +0 -6
- package/src/parts/markets.js +0 -6
- package/src/parts/search.js +0 -6
- package/src/presets/barba.js +0 -190
- package/src/product-state.js +0 -318
- package/src/selling-plans.js +0 -154
- package/src/server.js +0 -215
- package/src/store-html.js +0 -0
- package/src/storefront/catalog.js +0 -199
- package/types/astro.d.ts +0 -28
- package/types/barba.d.ts +0 -16
- package/types/index.d.ts +0 -577
- package/types/server.d.ts +0 -182
package/dist/tether.js
CHANGED
|
@@ -1,28 +1,231 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
(() => {
|
|
3
2
|
var __defProp = Object.defineProperty;
|
|
3
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
4
4
|
var __export = (target, all) => {
|
|
5
5
|
for (var name in all)
|
|
6
6
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
7
7
|
};
|
|
8
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
8
9
|
|
|
9
|
-
// src/
|
|
10
|
-
var
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
10
|
+
// server/src/shared/api-versions.ts
|
|
11
|
+
var STOREFRONT_API_VERSION = "2026-07";
|
|
12
|
+
|
|
13
|
+
// src/api-version.ts
|
|
14
|
+
var QUARTERLY_VERSION = /^(\d{4})-(01|04|07|10)$/;
|
|
15
|
+
var AVERAGE_MONTH_MS = 30.44 * 24 * 3600 * 1e3;
|
|
16
|
+
function parseVersion(version) {
|
|
17
|
+
const match = QUARTERLY_VERSION.exec(typeof version === "string" ? version : "");
|
|
18
|
+
if (!match) return null;
|
|
19
|
+
return { year: Number(match[1]), month: Number(match[2]) };
|
|
20
|
+
}
|
|
21
|
+
var isQuarterlyVersion = (version) => parseVersion(version) !== null;
|
|
22
|
+
|
|
23
|
+
// server/src/shared/json.ts
|
|
24
|
+
function isRecord(value) {
|
|
25
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
26
|
+
}
|
|
27
|
+
function field(value, key) {
|
|
28
|
+
return isRecord(value) ? value[key] : void 0;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// src/config-sections.ts
|
|
32
|
+
var METAFIELD_PART = /^[A-Za-z0-9_.-]+$/;
|
|
33
|
+
var METAFIELD_NAMESPACE = /^(\$app(:[A-Za-z0-9_-]+)?|[A-Za-z0-9_.-]+)$/;
|
|
34
|
+
var HOSTNAME = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i;
|
|
35
|
+
var COOKIE_NAME = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
|
|
36
|
+
var GLOBAL_NAME = /^[A-Za-z_$][\w$]*$/;
|
|
37
|
+
var SITE_ORIGIN = "https://site.invalid";
|
|
38
|
+
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
|
|
39
|
+
var MARKET_SOURCES = ["saved", "server", "webflow"];
|
|
40
|
+
var CONSENT_MODES = [
|
|
41
|
+
"no-banner",
|
|
42
|
+
"default-banner",
|
|
43
|
+
"custom-banner"
|
|
44
|
+
];
|
|
45
|
+
var isText = (value) => typeof value === "string" && value !== "";
|
|
46
|
+
var isFunction = (value) => typeof value === "function";
|
|
47
|
+
function domainOption(value, invalid) {
|
|
48
|
+
if (typeof value !== "string" || !value) return "";
|
|
49
|
+
const host = value.replace(/^https?:\/\//, "").replace(/\/.*$/, "");
|
|
50
|
+
if (isHostname(host)) return host;
|
|
51
|
+
invalid("domain", value);
|
|
52
|
+
return "";
|
|
53
|
+
}
|
|
54
|
+
function isSiteOrHttpsUrl(value) {
|
|
55
|
+
if (!isText(value)) return false;
|
|
56
|
+
try {
|
|
57
|
+
const url2 = new URL(value, `${SITE_ORIGIN}/`);
|
|
58
|
+
if (url2.origin === SITE_ORIGIN) return true;
|
|
59
|
+
return url2.protocol === "https:" || url2.protocol === "http:" && LOOPBACK_HOSTS.has(url2.hostname);
|
|
60
|
+
} catch {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function partsUrlOption(value, invalid) {
|
|
65
|
+
if (value === void 0 || value === null || value === "") return null;
|
|
66
|
+
if (typeof value === "string" && (isSiteOrHttpsUrl(value) || isPageOrigin(value))) return value;
|
|
67
|
+
invalid("partsUrl", value);
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
function isPageOrigin(value) {
|
|
71
|
+
if (typeof location === "undefined") return false;
|
|
72
|
+
try {
|
|
73
|
+
return new URL(value).origin === location.origin;
|
|
74
|
+
} catch {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function toHttpsOrigin(value) {
|
|
79
|
+
if (typeof value !== "string") return null;
|
|
80
|
+
try {
|
|
81
|
+
const url2 = new URL(value);
|
|
82
|
+
return url2.protocol === "https:" && url2.href === new URL(url2.origin).href ? url2.origin : null;
|
|
83
|
+
} catch {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function embedOriginsOption(value, invalid) {
|
|
88
|
+
if (!Array.isArray(value)) {
|
|
89
|
+
invalid("embedOrigins", value);
|
|
90
|
+
return [];
|
|
91
|
+
}
|
|
92
|
+
const origins = [];
|
|
93
|
+
const entries = value;
|
|
94
|
+
for (const entry of entries) {
|
|
95
|
+
const origin = toHttpsOrigin(entry);
|
|
96
|
+
if (origin) origins.push(origin);
|
|
97
|
+
else invalid("embedOrigin", entry);
|
|
98
|
+
}
|
|
99
|
+
return origins;
|
|
100
|
+
}
|
|
101
|
+
function toMetafieldIdentifier(ref) {
|
|
102
|
+
let namespace;
|
|
103
|
+
let key;
|
|
104
|
+
if (typeof ref === "string") {
|
|
105
|
+
const dot = ref.indexOf(".");
|
|
106
|
+
namespace = dot > 0 ? ref.slice(0, dot) : "";
|
|
107
|
+
key = ref.slice(dot + 1);
|
|
108
|
+
} else if (typeof ref === "object" && ref !== null) {
|
|
109
|
+
namespace = Reflect.get(ref, "namespace");
|
|
110
|
+
key = Reflect.get(ref, "key");
|
|
111
|
+
}
|
|
112
|
+
if (typeof namespace !== "string" || typeof key !== "string") return null;
|
|
113
|
+
return METAFIELD_NAMESPACE.test(namespace) && METAFIELD_PART.test(key) ? { namespace, key } : null;
|
|
114
|
+
}
|
|
115
|
+
function metafieldsOption(value, invalid) {
|
|
116
|
+
if (!Array.isArray(value)) {
|
|
117
|
+
invalid("metafields", value);
|
|
118
|
+
return [];
|
|
119
|
+
}
|
|
120
|
+
const refs = [];
|
|
121
|
+
const entries = value;
|
|
122
|
+
for (const ref of entries) {
|
|
123
|
+
const identifier = toMetafieldIdentifier(ref);
|
|
124
|
+
if (identifier) refs.push(typeof ref === "string" ? ref : identifier);
|
|
125
|
+
else invalid("metafield", ref);
|
|
126
|
+
}
|
|
127
|
+
return refs;
|
|
128
|
+
}
|
|
129
|
+
var when = (check) => (value) => check(value) ? value : void 0;
|
|
130
|
+
var isHostname = (value) => isText(value) && HOSTNAME.test(value);
|
|
131
|
+
var isCookieName = (value) => isText(value) && COOKIE_NAME.test(value);
|
|
132
|
+
var isDataLayer = (value) => typeof value === "boolean" || isText(value) && GLOBAL_NAME.test(value);
|
|
133
|
+
var url = when(isSiteOrHttpsUrl);
|
|
134
|
+
var text = when(isText);
|
|
135
|
+
var flag = when((value) => typeof value === "boolean");
|
|
136
|
+
var hook = when(isFunction);
|
|
137
|
+
function sectionOption(name, value, schema, invalid) {
|
|
138
|
+
if (value === null || value === void 0) return null;
|
|
139
|
+
if (!isRecord(value)) {
|
|
140
|
+
invalid(name, value);
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
const section = {};
|
|
144
|
+
const readers = schema;
|
|
145
|
+
for (const [key, read] of Object.entries(readers)) {
|
|
146
|
+
const field2 = value[key];
|
|
147
|
+
if (field2 === void 0) continue;
|
|
148
|
+
const parsed = read(field2);
|
|
149
|
+
if (parsed === void 0) invalid(`${name}.${key}`, field2);
|
|
150
|
+
else section[key] = parsed;
|
|
151
|
+
}
|
|
152
|
+
return section;
|
|
153
|
+
}
|
|
154
|
+
var ACCOUNT = {
|
|
155
|
+
server: url,
|
|
156
|
+
orderUrl: url,
|
|
157
|
+
subscriptionsUrl: url,
|
|
158
|
+
paymentUrl: url,
|
|
159
|
+
logoutUrl: url,
|
|
160
|
+
locale: text,
|
|
161
|
+
navigate: hook
|
|
162
|
+
};
|
|
163
|
+
var ACCOUNT_NEEDS_SERVER = "customer accounts need the Worker since 0.7.0: set account.server";
|
|
164
|
+
var LOCALE_MARKET = { country: text, language: text, locale: text };
|
|
165
|
+
var MARKETS = {
|
|
166
|
+
// A single source name counts as a list of one.
|
|
167
|
+
detect: (value) => {
|
|
168
|
+
if (typeof value === "boolean") return value;
|
|
169
|
+
const names = Array.isArray(value) ? value : [value];
|
|
170
|
+
return typeof value === "string" || Array.isArray(value) ? MARKET_SOURCES.filter((source) => names.includes(source)) : void 0;
|
|
171
|
+
},
|
|
172
|
+
locales: (value) => {
|
|
173
|
+
if (!isRecord(value)) return void 0;
|
|
174
|
+
const locales = {};
|
|
175
|
+
for (const [locale, entry] of Object.entries(value)) {
|
|
176
|
+
const market = sectionOption(`markets.locales.${locale}`, entry, LOCALE_MARKET, () => void 0);
|
|
177
|
+
if (market) locales[locale] = market;
|
|
178
|
+
}
|
|
179
|
+
return locales;
|
|
180
|
+
},
|
|
181
|
+
cookie: when(isCookieName),
|
|
182
|
+
remember: flag,
|
|
183
|
+
strict: flag
|
|
184
|
+
};
|
|
185
|
+
var SHOPIFY_ANALYTICS = {
|
|
186
|
+
consent: (value) => CONSENT_MODES.find((mode) => mode === value),
|
|
187
|
+
proxy: (value) => value !== void 0 && Boolean(value)
|
|
188
|
+
};
|
|
189
|
+
var ANALYTICS = {
|
|
190
|
+
dataLayer: when(isDataLayer),
|
|
191
|
+
shopify: (value) => typeof value === "boolean" ? value : sectionOption("analytics.shopify", value, SHOPIFY_ANALYTICS, () => void 0) ?? void 0,
|
|
192
|
+
onEvent: hook
|
|
193
|
+
};
|
|
194
|
+
function accountOption(value, invalid, warn2) {
|
|
195
|
+
const account = sectionOption("account", value, ACCOUNT, invalid);
|
|
196
|
+
if (account === null) return null;
|
|
197
|
+
const { server } = account;
|
|
198
|
+
if (server === void 0) {
|
|
199
|
+
warn2(ACCOUNT_NEEDS_SERVER);
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
return { ...account, server };
|
|
203
|
+
}
|
|
204
|
+
var marketsOption = (value, invalid) => sectionOption("markets", value, MARKETS, invalid);
|
|
205
|
+
var analyticsOption = (value, invalid) => sectionOption("analytics", value, ANALYTICS, invalid);
|
|
206
|
+
|
|
207
|
+
// server/src/shared/market-codes.ts
|
|
208
|
+
var COUNTRY = /^[A-Z]{2}$/;
|
|
209
|
+
var NOT_A_COUNTRY = /* @__PURE__ */ new Set(["XX", "T1"]);
|
|
210
|
+
var REGIONAL_LANGUAGES = /* @__PURE__ */ new Set(["PT_BR", "PT_PT", "ZH_CN", "ZH_TW"]);
|
|
211
|
+
var LANGUAGE = /^[A-Z]{2,3}$/;
|
|
212
|
+
function toCountry(value) {
|
|
213
|
+
const code = (typeof value === "string" ? value : "").trim().toUpperCase();
|
|
214
|
+
if (!COUNTRY.test(code) || NOT_A_COUNTRY.has(code)) return null;
|
|
215
|
+
return code;
|
|
216
|
+
}
|
|
217
|
+
function toLanguage(value) {
|
|
218
|
+
const raw = (typeof value === "string" ? value : "").trim().replace("-", "_").toUpperCase();
|
|
219
|
+
if (REGIONAL_LANGUAGES.has(raw)) return raw;
|
|
220
|
+
const primary = raw.split("_")[0] ?? "";
|
|
221
|
+
return LANGUAGE.test(primary) ? primary : null;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// src/config.ts
|
|
22
225
|
var DEFAULTS = {
|
|
23
226
|
domain: "",
|
|
24
227
|
token: "",
|
|
25
|
-
apiVersion:
|
|
228
|
+
apiVersion: STOREFRONT_API_VERSION,
|
|
26
229
|
country: void 0,
|
|
27
230
|
language: void 0,
|
|
28
231
|
locale: void 0,
|
|
@@ -31,6 +234,7 @@
|
|
|
31
234
|
productPath: "/product",
|
|
32
235
|
collectionPath: "/collection",
|
|
33
236
|
metafields: [],
|
|
237
|
+
embedOrigins: [],
|
|
34
238
|
imagesFirst: 25,
|
|
35
239
|
variantsFirst: 100,
|
|
36
240
|
sellingPlanGroupsFirst: 10,
|
|
@@ -66,9 +270,34 @@
|
|
|
66
270
|
debug: false
|
|
67
271
|
};
|
|
68
272
|
var cfg = { ...DEFAULTS };
|
|
273
|
+
function readOnlyView(target) {
|
|
274
|
+
const refuse = (key) => {
|
|
275
|
+
warn(`Tether.config is read-only (${String(key)}): pass options to Tether.init() instead`);
|
|
276
|
+
return false;
|
|
277
|
+
};
|
|
278
|
+
return new Proxy(target, {
|
|
279
|
+
get(object, key) {
|
|
280
|
+
const value = Reflect.get(object, key);
|
|
281
|
+
return typeof value === "object" && value !== null ? readOnlyView(value) : value;
|
|
282
|
+
},
|
|
283
|
+
set: (_object, key) => refuse(key),
|
|
284
|
+
deleteProperty: (_object, key) => refuse(key),
|
|
285
|
+
defineProperty: (_object, key) => refuse(key)
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
var configView = readOnlyView(cfg);
|
|
69
289
|
var PREFIX = /^[a-z][a-z0-9-]*$/;
|
|
70
|
-
var
|
|
71
|
-
|
|
290
|
+
var SWITCH_OPTIONS = [
|
|
291
|
+
"revalidate",
|
|
292
|
+
"openCartOnAdd",
|
|
293
|
+
"cartFocus",
|
|
294
|
+
"variantUrl",
|
|
295
|
+
"autoMount",
|
|
296
|
+
"observe",
|
|
297
|
+
"astro",
|
|
298
|
+
"debug"
|
|
299
|
+
];
|
|
300
|
+
var TEXT_OPTIONS = ["token", "productPath", "collectionPath", "cartDrawerOpenClass"];
|
|
72
301
|
var COUNT_OPTIONS = [
|
|
73
302
|
"imagesFirst",
|
|
74
303
|
"variantsFirst",
|
|
@@ -77,7 +306,8 @@
|
|
|
77
306
|
"collectionFirst",
|
|
78
307
|
"retries",
|
|
79
308
|
"maxAge",
|
|
80
|
-
"lowStock"
|
|
309
|
+
"lowStock",
|
|
310
|
+
"searchDelay"
|
|
81
311
|
];
|
|
82
312
|
var HOOK_OPTIONS = [
|
|
83
313
|
"warningText",
|
|
@@ -94,65 +324,86 @@
|
|
|
94
324
|
if (value !== void 0) warn(`invalid ${name} ${JSON.stringify(value)}: using ${JSON.stringify(fallback)}`);
|
|
95
325
|
return fallback;
|
|
96
326
|
}
|
|
327
|
+
function dropped(name, value) {
|
|
328
|
+
warn(`invalid ${name} ${JSON.stringify(value)}: ignored`);
|
|
329
|
+
}
|
|
330
|
+
function warnUnknownKeys(options) {
|
|
331
|
+
for (const key of Object.keys(options)) {
|
|
332
|
+
if (!(key in DEFAULTS)) warn(`unknown option ${JSON.stringify(key)}: ignored`);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
function hookOption(value) {
|
|
336
|
+
return typeof value === "function" ? value : null;
|
|
337
|
+
}
|
|
338
|
+
function hookOptions(options) {
|
|
339
|
+
const hooks = Object.fromEntries(HOOK_OPTIONS.map((name) => [name, hookOption(options[name])]));
|
|
340
|
+
return hooks;
|
|
341
|
+
}
|
|
97
342
|
function countOption(name, value) {
|
|
98
343
|
if (typeof value === "number" && Number.isInteger(value) && value >= 0) return value;
|
|
99
344
|
return invalidOption(name, value, DEFAULTS[name]);
|
|
100
345
|
}
|
|
101
|
-
function
|
|
102
|
-
return
|
|
346
|
+
function switchOption(name, value) {
|
|
347
|
+
return typeof value === "boolean" ? value : invalidOption(name, value, DEFAULTS[name]);
|
|
103
348
|
}
|
|
104
|
-
function
|
|
105
|
-
|
|
106
|
-
return value.replace(/^https?:\/\//, "").replace(/\/.*$/, "");
|
|
349
|
+
function textOption(name, value) {
|
|
350
|
+
return typeof value === "string" ? value : invalidOption(name, value, DEFAULTS[name]);
|
|
107
351
|
}
|
|
108
|
-
function
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
namespace = dot > 0 ? ref.slice(0, dot) : "";
|
|
114
|
-
key = ref.slice(dot + 1);
|
|
115
|
-
} else if (typeof ref === "object" && ref !== null) {
|
|
116
|
-
namespace = Reflect.get(ref, "namespace");
|
|
117
|
-
key = Reflect.get(ref, "key");
|
|
352
|
+
function localeOption(value) {
|
|
353
|
+
if (value === void 0 || value === null || value === "") return void 0;
|
|
354
|
+
try {
|
|
355
|
+
if (typeof value === "string" && Intl.getCanonicalLocales(value).length) return value;
|
|
356
|
+
} catch {
|
|
118
357
|
}
|
|
119
|
-
|
|
120
|
-
return
|
|
358
|
+
dropped("locale", value);
|
|
359
|
+
return void 0;
|
|
121
360
|
}
|
|
122
|
-
function
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
value
|
|
129
|
-
) {
|
|
130
|
-
const identifier = toMetafieldIdentifier(ref);
|
|
131
|
-
if (identifier) refs.push(typeof ref === "string" ? ref : identifier);
|
|
132
|
-
else invalidOption("metafield", ref, null);
|
|
361
|
+
function choiceOption(name, value, choices) {
|
|
362
|
+
return choices.find((choice) => choice === value) ?? invalidOption(name, value, choices[0]);
|
|
363
|
+
}
|
|
364
|
+
function apiVersionOption(value) {
|
|
365
|
+
if (value === "unstable") {
|
|
366
|
+
warn('apiVersion "unstable" changes without notice. Pin a dated version (e.g. 2026-07).');
|
|
367
|
+
return value;
|
|
133
368
|
}
|
|
134
|
-
return
|
|
369
|
+
return isQuarterlyVersion(value) ? value : invalidOption("apiVersion", value, DEFAULTS.apiVersion);
|
|
370
|
+
}
|
|
371
|
+
function codeOption(name, value, parse) {
|
|
372
|
+
if (value === void 0 || value === null || value === "") return void 0;
|
|
373
|
+
const code = parse(value);
|
|
374
|
+
if (code !== null) return code;
|
|
375
|
+
dropped(name, value);
|
|
376
|
+
return void 0;
|
|
135
377
|
}
|
|
136
378
|
function resolveConfig(options) {
|
|
137
|
-
|
|
379
|
+
warnUnknownKeys(options);
|
|
380
|
+
const domain = domainOption(options.domain, dropped);
|
|
138
381
|
const prefix = typeof options.prefix === "string" && PREFIX.test(options.prefix) ? options.prefix : invalidOption("prefix", options.prefix, DEFAULTS.prefix);
|
|
139
382
|
const resolved = {
|
|
140
383
|
...DEFAULTS,
|
|
141
|
-
...options,
|
|
384
|
+
...hookOptions(options),
|
|
142
385
|
domain,
|
|
386
|
+
apiVersion: apiVersionOption(options.apiVersion),
|
|
387
|
+
country: codeOption("country", options.country, toCountry),
|
|
388
|
+
locale: localeOption(options.locale),
|
|
389
|
+
partsUrl: partsUrlOption(options.partsUrl, dropped),
|
|
390
|
+
language: codeOption("language", options.language, toLanguage),
|
|
391
|
+
markets: marketsOption(options.markets, dropped),
|
|
392
|
+
analytics: analyticsOption(options.analytics, dropped),
|
|
393
|
+
account: accountOption(options.account, dropped, warn),
|
|
143
394
|
prefix,
|
|
144
|
-
storageKey: options.storageKey
|
|
145
|
-
metafields: metafieldsOption(options.metafields ?? []),
|
|
395
|
+
storageKey: typeof options.storageKey === "string" && options.storageKey ? options.storageKey : `${prefix}:cart:${domain}`,
|
|
396
|
+
metafields: metafieldsOption(options.metafields ?? [], dropped),
|
|
397
|
+
embedOrigins: embedOriginsOption(options.embedOrigins ?? [], dropped),
|
|
146
398
|
checkoutTarget: choiceOption("checkoutTarget", options.checkoutTarget, ["_self", "_blank"]),
|
|
147
399
|
defaultPurchase: choiceOption("defaultPurchase", options.defaultPurchase, ["one-time", "subscription"])
|
|
148
400
|
};
|
|
149
401
|
for (const name of COUNT_OPTIONS) resolved[name] = countOption(name, options[name]);
|
|
150
|
-
for (const name of
|
|
151
|
-
|
|
152
|
-
warn('apiVersion "unstable" changes without notice. Pin a dated version (e.g. 2026-07).');
|
|
153
|
-
}
|
|
402
|
+
for (const name of SWITCH_OPTIONS) resolved[name] = switchOption(name, options[name]);
|
|
403
|
+
for (const name of TEXT_OPTIONS) resolved[name] = textOption(name, options[name]);
|
|
154
404
|
return resolved;
|
|
155
405
|
}
|
|
406
|
+
var parseMarkets = (value) => marketsOption(value, dropped);
|
|
156
407
|
function configure(options = {}) {
|
|
157
408
|
const resolved = resolveConfig(options);
|
|
158
409
|
for (const key of Object.keys(cfg)) {
|
|
@@ -177,83 +428,73 @@
|
|
|
177
428
|
}
|
|
178
429
|
}
|
|
179
430
|
|
|
180
|
-
// src/
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
TetherError: () => TetherError,
|
|
184
|
-
endpoint: () => endpoint,
|
|
185
|
-
gql: () => gql,
|
|
186
|
-
request: () => request,
|
|
187
|
-
unwrap: () => unwrap,
|
|
188
|
-
withContext: () => withContext
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
// src/market-codes.js
|
|
192
|
-
var market_codes_exports = {};
|
|
193
|
-
__export(market_codes_exports, {
|
|
194
|
-
toCountry: () => toCountry,
|
|
195
|
-
toLanguage: () => toLanguage
|
|
196
|
-
});
|
|
197
|
-
var COUNTRY = /^[A-Z]{2}$/;
|
|
198
|
-
var NOT_A_COUNTRY = /* @__PURE__ */ new Set(["XX", "T1"]);
|
|
199
|
-
var REGIONAL_LANGUAGES = /* @__PURE__ */ new Set(["PT_BR", "PT_PT", "ZH_CN", "ZH_TW"]);
|
|
200
|
-
var LANGUAGE = /^[A-Z]{2,3}$/;
|
|
201
|
-
function toCountry(value) {
|
|
202
|
-
const code = (typeof value === "string" ? value : "").trim().toUpperCase();
|
|
203
|
-
if (!COUNTRY.test(code) || NOT_A_COUNTRY.has(code)) return null;
|
|
204
|
-
return (
|
|
205
|
-
/** @type {CountryCode} */
|
|
206
|
-
code
|
|
207
|
-
);
|
|
208
|
-
}
|
|
209
|
-
function toLanguage(value) {
|
|
210
|
-
const raw = (typeof value === "string" ? value : "").trim().replace("-", "_").toUpperCase();
|
|
211
|
-
if (REGIONAL_LANGUAGES.has(raw)) return (
|
|
212
|
-
/** @type {LanguageCode} */
|
|
213
|
-
raw
|
|
214
|
-
);
|
|
215
|
-
const primary = raw.split("_")[0] ?? "";
|
|
216
|
-
return LANGUAGE.test(primary) ? (
|
|
217
|
-
/** @type {LanguageCode} */
|
|
218
|
-
primary
|
|
219
|
-
) : null;
|
|
431
|
+
// server/src/shared/error-message.ts
|
|
432
|
+
function messageOf(error) {
|
|
433
|
+
return error instanceof Error ? error.message : String(error);
|
|
220
434
|
}
|
|
221
435
|
|
|
222
|
-
// src/client.
|
|
436
|
+
// src/client.ts
|
|
223
437
|
var TetherError = class extends Error {
|
|
224
|
-
/**
|
|
225
|
-
* @param {string} message
|
|
226
|
-
* @param {TetherErrorDetails} [details]
|
|
227
|
-
*/
|
|
228
438
|
constructor(message, { status: status2, errors, userErrors, code } = {}) {
|
|
229
439
|
super(message);
|
|
230
|
-
this
|
|
440
|
+
__publicField(this, "name", "TetherError");
|
|
441
|
+
__publicField(this, "code");
|
|
442
|
+
__publicField(this, "status");
|
|
443
|
+
__publicField(this, "errors");
|
|
444
|
+
__publicField(this, "userErrors");
|
|
231
445
|
this.code = code ?? (status2 ? `HTTP_${status2}` : "ERROR");
|
|
232
446
|
this.status = status2;
|
|
233
447
|
this.errors = errors;
|
|
234
448
|
this.userErrors = userErrors;
|
|
235
449
|
}
|
|
236
450
|
};
|
|
451
|
+
function matches(value, shape) {
|
|
452
|
+
if (typeof shape === "function") return shape(value);
|
|
453
|
+
if (typeof shape === "string") return shape === "object" ? isRecord(value) : typeof value === shape;
|
|
454
|
+
if (isShapeList(shape)) return Array.isArray(value) && value.every((item) => matches(item, shape[0]));
|
|
455
|
+
return isRecord(value) && Object.entries(shape).every(([key, inner]) => matches(value[key], inner));
|
|
456
|
+
}
|
|
457
|
+
var isShapeList = (shape) => Array.isArray(shape);
|
|
458
|
+
var orNull = (shape) => (value) => value === null || matches(value, shape);
|
|
459
|
+
var shaped = (shape) => (data) => matches(data, shape);
|
|
237
460
|
function endpoint(conf = cfg) {
|
|
238
461
|
return `https://${conf.domain}/api/${conf.apiVersion}/graphql.json`;
|
|
239
462
|
}
|
|
240
|
-
|
|
463
|
+
var STRINGS_AND_COMMENTS = /"(?:\\.|[^"\\\n])*"|#[^\n]*/g;
|
|
464
|
+
var OPERATION_START = /^\s*(?:query|mutation)(?:\s+\w+)?\s*(?:\([^)]*\))?/;
|
|
465
|
+
function inContext(query2, { country, language }) {
|
|
241
466
|
const args = [];
|
|
242
|
-
const country = toCountry(ctx.country);
|
|
243
|
-
const language = toLanguage(ctx.language);
|
|
244
467
|
if (country) args.push(`country: ${country}`);
|
|
245
468
|
if (language) args.push(`language: ${language}`);
|
|
246
|
-
if (!args.length
|
|
469
|
+
if (!args.length) return query2;
|
|
247
470
|
const tag = `@inContext(${args.join(", ")})`;
|
|
248
|
-
|
|
249
|
-
|
|
471
|
+
const blanked = query2.replace(STRINGS_AND_COMMENTS, (text2) => " ".repeat(text2.length));
|
|
472
|
+
const selectionStart = blanked.indexOf("{");
|
|
473
|
+
if (blanked.slice(0, selectionStart).includes("@inContext")) return query2;
|
|
474
|
+
if (blanked.trimStart().startsWith("{")) return `query ${tag} ${query2.slice(selectionStart)}`;
|
|
475
|
+
const header = OPERATION_START.exec(blanked);
|
|
476
|
+
if (!header) return query2;
|
|
477
|
+
const end = header[0].length;
|
|
478
|
+
return `${query2.slice(0, end).trimEnd()} ${tag} ${query2.slice(end).trimStart()}`;
|
|
479
|
+
}
|
|
480
|
+
function withContext(query2, ctx = cfg) {
|
|
481
|
+
const country = toCountry(ctx.country) ?? void 0;
|
|
482
|
+
const language = toLanguage(ctx.language) ?? void 0;
|
|
483
|
+
return inContext(query2, { country, language });
|
|
250
484
|
}
|
|
251
485
|
var RETRY_BACKOFF_MS = 300;
|
|
252
486
|
var THROTTLE_BACKOFF_MS = 500;
|
|
253
487
|
var SECURITY_REJECTION = 430;
|
|
488
|
+
var MS_PER_SECOND = 1e3;
|
|
254
489
|
var INVENTORY_FIELD = "quantityAvailable";
|
|
255
490
|
var storesWithoutInventory = /* @__PURE__ */ new Set();
|
|
256
491
|
var storeOf = (conf) => `${conf.domain} ${conf.apiVersion} ${conf.token}`;
|
|
492
|
+
function queryText(conf, query2, access, context) {
|
|
493
|
+
const written = typeof query2 === "string" ? query2 : query2(access);
|
|
494
|
+
const text2 = context ? inContext(written, conf) : written;
|
|
495
|
+
if (conf.maxAge <= 0 || /^\s*mutation\b/.test(text2)) return text2;
|
|
496
|
+
return `${text2} #${Math.floor(Date.now() / (conf.maxAge * MS_PER_SECOND))}`;
|
|
497
|
+
}
|
|
257
498
|
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
258
499
|
function statusText(status2, conf) {
|
|
259
500
|
if (status2 === 401 || status2 === 403) {
|
|
@@ -265,23 +506,40 @@
|
|
|
265
506
|
}
|
|
266
507
|
return `HTTP ${status2} from ${conf.domain}`;
|
|
267
508
|
}
|
|
509
|
+
function toGraphQLError(value) {
|
|
510
|
+
const message = field(value, "message");
|
|
511
|
+
const code = field(field(value, "extensions"), "code");
|
|
512
|
+
const text2 = typeof message === "string" ? message : "";
|
|
513
|
+
return typeof code === "string" ? { message: text2, extensions: { code } } : { message: text2 };
|
|
514
|
+
}
|
|
515
|
+
function toGraphQLResponse(json) {
|
|
516
|
+
if (!isRecord(json)) return null;
|
|
517
|
+
const { data, errors } = json;
|
|
518
|
+
if (errors === void 0 || errors === null) return { data };
|
|
519
|
+
if (!Array.isArray(errors)) return null;
|
|
520
|
+
return { data, errors: errors.map(toGraphQLError) };
|
|
521
|
+
}
|
|
522
|
+
function invalidResponse(res, conf) {
|
|
523
|
+
const text2 = res.ok ? `Invalid response (${res.status})` : statusText(res.status, conf);
|
|
524
|
+
return new TetherError(text2, { status: res.status });
|
|
525
|
+
}
|
|
268
526
|
function graphQLFailure(res, errors, conf) {
|
|
269
527
|
const message = errors.map((error) => error.message).filter(Boolean).join(" | ");
|
|
270
|
-
let
|
|
271
|
-
if (!res.ok)
|
|
272
|
-
return new TetherError(
|
|
528
|
+
let text2 = message || "Storefront API error";
|
|
529
|
+
if (!res.ok) text2 = res.status === 401 || res.status === 403 || !message ? statusText(res.status, conf) : message;
|
|
530
|
+
return new TetherError(text2, { status: res.status, errors });
|
|
273
531
|
}
|
|
274
|
-
async function attempt(conf,
|
|
532
|
+
async function attempt(conf, query2, variables, isData, canRetry) {
|
|
275
533
|
let res;
|
|
276
534
|
try {
|
|
277
535
|
res = await (conf.fetch ?? fetch)(endpoint(conf), {
|
|
278
536
|
method: "POST",
|
|
279
537
|
headers: { "Content-Type": "application/json", "X-Shopify-Storefront-Access-Token": conf.token },
|
|
280
|
-
body: JSON.stringify({ query, variables })
|
|
538
|
+
body: JSON.stringify({ query: query2, variables })
|
|
281
539
|
});
|
|
282
540
|
} catch (error) {
|
|
283
541
|
if (canRetry) return { kind: "retry", backoffMs: RETRY_BACKOFF_MS };
|
|
284
|
-
throw new TetherError(`Network error: ${
|
|
542
|
+
throw new TetherError(`Network error: ${messageOf(error)}`, {
|
|
285
543
|
code: "NETWORK"
|
|
286
544
|
});
|
|
287
545
|
}
|
|
@@ -295,16 +553,14 @@
|
|
|
295
553
|
try {
|
|
296
554
|
json = await res.json();
|
|
297
555
|
} catch {
|
|
298
|
-
|
|
299
|
-
throw new TetherError(text, { status: res.status });
|
|
556
|
+
throw invalidResponse(res, conf);
|
|
300
557
|
}
|
|
301
|
-
const
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
);
|
|
558
|
+
const body = toGraphQLResponse(json);
|
|
559
|
+
if (!body) throw invalidResponse(res, conf);
|
|
560
|
+
const { data, errors = [] } = body;
|
|
305
561
|
if (errors.length) {
|
|
306
|
-
if (
|
|
307
|
-
return { kind: "inventory-refused" };
|
|
562
|
+
if (query2.includes(INVENTORY_FIELD) && errors.some((error) => error.message.includes(INVENTORY_FIELD))) {
|
|
563
|
+
return { kind: "inventory-refused", error: graphQLFailure(res, errors, conf) };
|
|
308
564
|
}
|
|
309
565
|
if (canRetry && errors.some((error) => error.extensions?.code === "THROTTLED")) {
|
|
310
566
|
return { kind: "retry", backoffMs: THROTTLE_BACKOFF_MS };
|
|
@@ -312,28 +568,23 @@
|
|
|
312
568
|
throw graphQLFailure(res, errors, conf);
|
|
313
569
|
}
|
|
314
570
|
if (!res.ok) throw new TetherError(statusText(res.status, conf), { status: res.status });
|
|
571
|
+
if (!isData(data)) throw invalidResponse(res, conf);
|
|
315
572
|
return { kind: "data", data };
|
|
316
573
|
}
|
|
317
|
-
async function request(conf,
|
|
574
|
+
async function request(conf, query2, variables, isData, { context = true } = {}) {
|
|
318
575
|
if (!conf.domain || !conf.token) {
|
|
319
576
|
throw new TetherError("Tether is not configured: domain and token are required", { code: "NOT_CONFIGURED" });
|
|
320
577
|
}
|
|
321
|
-
let text = context ? withContext(query, conf) : query;
|
|
322
|
-
if (conf.maxAge > 0 && !/^\s*mutation\b/.test(text)) {
|
|
323
|
-
text += ` #${Math.floor(Date.now() / (conf.maxAge * 1e3))}`;
|
|
324
|
-
}
|
|
325
578
|
const store = storeOf(conf);
|
|
326
579
|
let retries = 0;
|
|
327
580
|
for (; ; ) {
|
|
328
|
-
const sent = storesWithoutInventory.has(store)
|
|
329
|
-
const outcome = await attempt(conf, sent, variables, retries < conf.retries);
|
|
581
|
+
const sent = queryText(conf, query2, { inventory: !storesWithoutInventory.has(store) }, context);
|
|
582
|
+
const outcome = await attempt(conf, sent, variables, isData, retries < conf.retries);
|
|
330
583
|
switch (outcome.kind) {
|
|
331
584
|
case "data":
|
|
332
|
-
return
|
|
333
|
-
/** @type {T} */
|
|
334
|
-
outcome.data
|
|
335
|
-
);
|
|
585
|
+
return outcome.data;
|
|
336
586
|
case "inventory-refused":
|
|
587
|
+
if (typeof query2 === "string") throw outcome.error;
|
|
337
588
|
log(`the token can't read inventory: ${INVENTORY_FIELD} is left out from now on`);
|
|
338
589
|
storesWithoutInventory.add(store);
|
|
339
590
|
break;
|
|
@@ -345,7 +596,13 @@
|
|
|
345
596
|
}
|
|
346
597
|
}
|
|
347
598
|
}
|
|
348
|
-
|
|
599
|
+
function gql(query2, variables, isData, options) {
|
|
600
|
+
return request(cfg, query2, variables, isData, options);
|
|
601
|
+
}
|
|
602
|
+
var hasData = (data) => typeof data === "object" && data !== null;
|
|
603
|
+
function query(text2, variables = {}, options) {
|
|
604
|
+
return gql(text2, variables, hasData, options);
|
|
605
|
+
}
|
|
349
606
|
function unwrap(payload, name) {
|
|
350
607
|
if (!payload) throw new TetherError(`${name}: empty response`);
|
|
351
608
|
const userErrors = payload.userErrors ?? [];
|
|
@@ -355,133 +612,237 @@
|
|
|
355
612
|
return payload;
|
|
356
613
|
}
|
|
357
614
|
|
|
358
|
-
// src/
|
|
359
|
-
var cart_exports = {};
|
|
360
|
-
__export(cart_exports, {
|
|
361
|
-
Cart: () => Cart,
|
|
362
|
-
trapFocus: () => trapFocus
|
|
363
|
-
});
|
|
364
|
-
|
|
365
|
-
// src/dom.js
|
|
366
|
-
var dom_exports = {};
|
|
367
|
-
__export(dom_exports, {
|
|
368
|
-
attr: () => attr,
|
|
369
|
-
busy: () => busy,
|
|
370
|
-
cdnUrl: () => cdnUrl,
|
|
371
|
-
emit: () => emit,
|
|
372
|
-
formatDate: () => formatDate,
|
|
373
|
-
formatMoney: () => formatMoney2,
|
|
374
|
-
formatPercent: () => formatPercent,
|
|
375
|
-
get: () => get,
|
|
376
|
-
has: () => has,
|
|
377
|
-
hasTemplate: () => hasTemplate,
|
|
378
|
-
imageUrl: () => imageUrl,
|
|
379
|
-
notifyLayoutChanged: () => notifyLayoutChanged,
|
|
380
|
-
parseJSONAttr: () => parseJSONAttr,
|
|
381
|
-
productUrl: () => productUrl2,
|
|
382
|
-
qsa: () => qsa,
|
|
383
|
-
repeat: () => repeat,
|
|
384
|
-
repeatKeyed: () => repeatKeyed,
|
|
385
|
-
sameImage: () => sameImage,
|
|
386
|
-
scoped: () => scoped,
|
|
387
|
-
sel: () => sel,
|
|
388
|
-
setImage: () => setImage,
|
|
389
|
-
setSelected: () => setSelected,
|
|
390
|
-
setStoreHtml: () => setStoreHtml,
|
|
391
|
-
setText: () => setText,
|
|
392
|
-
setTextTracked: () => setTextTracked,
|
|
393
|
-
show: () => show,
|
|
394
|
-
toAttributes: () => toAttributes,
|
|
395
|
-
watchImages: () => watchImages
|
|
396
|
-
});
|
|
397
|
-
|
|
398
|
-
// src/markup.js
|
|
615
|
+
// src/markup.ts
|
|
399
616
|
var attr = (name) => `${cfg.prefix}-${name}`;
|
|
400
617
|
var sel = (name, value) => value === void 0 ? `[${attr(name)}]` : `[${attr(name)}="${value}"]`;
|
|
401
618
|
var get = (el, name) => el.getAttribute(attr(name));
|
|
402
619
|
var has = (el, name) => el.hasAttribute(attr(name));
|
|
403
620
|
function qsa(root, selector) {
|
|
404
621
|
if (!root) return [];
|
|
405
|
-
return [
|
|
406
|
-
root.querySelectorAll(selector)];
|
|
622
|
+
return [...root.querySelectorAll(selector)];
|
|
407
623
|
}
|
|
408
624
|
var scoped = (root, selector, scopeSelector) => qsa(root, selector).filter((el) => el.closest(scopeSelector) === root);
|
|
625
|
+
var ANALYTICS_EVENTS = [
|
|
626
|
+
"product-ready",
|
|
627
|
+
"list-ready",
|
|
628
|
+
"cart-updated",
|
|
629
|
+
"cart-toggled",
|
|
630
|
+
"checkout",
|
|
631
|
+
"search"
|
|
632
|
+
];
|
|
633
|
+
var ownEvents = /* @__PURE__ */ new WeakMap();
|
|
634
|
+
function ownEvent(name, detail) {
|
|
635
|
+
const event = new CustomEvent(`${cfg.prefix}:${name}`, { detail, bubbles: true });
|
|
636
|
+
ownEvents.set(event, name);
|
|
637
|
+
return event;
|
|
638
|
+
}
|
|
639
|
+
function isOwnEvent(event, name) {
|
|
640
|
+
return ownEvents.get(event) === name;
|
|
641
|
+
}
|
|
409
642
|
function emit(name, detail, target = document) {
|
|
410
|
-
target.dispatchEvent(
|
|
643
|
+
target.dispatchEvent(ownEvent(name, detail));
|
|
411
644
|
}
|
|
412
645
|
|
|
413
|
-
// src/store-html.
|
|
414
|
-
var
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
"
|
|
420
|
-
"
|
|
421
|
-
"EMBED",
|
|
422
|
-
"BASE",
|
|
423
|
-
"META",
|
|
424
|
-
"LINK",
|
|
425
|
-
"NOSCRIPT",
|
|
426
|
-
"TEMPLATE",
|
|
427
|
-
// SVG animation can set an attribute such as href to a script URL after the attributes were checked.
|
|
428
|
-
"ANIMATE",
|
|
429
|
-
"SET"
|
|
646
|
+
// src/store-html.ts
|
|
647
|
+
var HTML_NAMESPACE = "http://www.w3.org/1999/xhtml";
|
|
648
|
+
var ALLOWED_ELEMENTS = /* @__PURE__ */ new Set([
|
|
649
|
+
...["h1", "h2", "h3", "h4", "h5", "h6", "p", "span", "div", "br", "hr", "blockquote", "code", "pre"],
|
|
650
|
+
...["strong", "em", "b", "i", "u", "s", "small", "sub", "sup"],
|
|
651
|
+
...["ul", "ol", "li", "dl", "dt", "dd"],
|
|
652
|
+
...["table", "caption", "colgroup", "col", "thead", "tbody", "tfoot", "tr", "th", "td"],
|
|
653
|
+
...["a", "img", "picture", "source", "video", "figure", "figcaption", "iframe"]
|
|
430
654
|
]);
|
|
431
|
-
var
|
|
432
|
-
var
|
|
433
|
-
var
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
655
|
+
var REMOVED_WITH_CONTENT = /* @__PURE__ */ new Set(["script", "style", "template", "noscript", "textarea", "select", "title"]);
|
|
656
|
+
var GLOBAL_ATTRIBUTES = /* @__PURE__ */ new Set(["style", "title", "lang", "dir", "role", "align", "width", "height"]);
|
|
657
|
+
var ELEMENT_ATTRIBUTES = /* @__PURE__ */ new Map([
|
|
658
|
+
["a", /* @__PURE__ */ new Set(["href", "target", "rel", "hreflang"])],
|
|
659
|
+
["img", /* @__PURE__ */ new Set(["src", "srcset", "sizes", "alt", "loading", "decoding"])],
|
|
660
|
+
["source", /* @__PURE__ */ new Set(["src", "srcset", "sizes", "type", "media"])],
|
|
661
|
+
["video", /* @__PURE__ */ new Set(["src", "poster", "controls", "autoplay", "muted", "loop", "playsinline", "preload"])],
|
|
662
|
+
["iframe", /* @__PURE__ */ new Set(["src", "allow", "allowfullscreen", "frameborder", "loading"])],
|
|
663
|
+
["blockquote", /* @__PURE__ */ new Set(["cite"])],
|
|
664
|
+
["ol", /* @__PURE__ */ new Set(["start", "type", "reversed"])],
|
|
665
|
+
["li", /* @__PURE__ */ new Set(["value"])],
|
|
666
|
+
["table", /* @__PURE__ */ new Set(["border", "cellpadding", "cellspacing", "summary"])],
|
|
667
|
+
["col", /* @__PURE__ */ new Set(["span"])],
|
|
668
|
+
["colgroup", /* @__PURE__ */ new Set(["span"])],
|
|
669
|
+
["th", /* @__PURE__ */ new Set(["colspan", "rowspan", "headers", "scope", "valign"])],
|
|
670
|
+
["td", /* @__PURE__ */ new Set(["colspan", "rowspan", "headers", "valign"])]
|
|
671
|
+
]);
|
|
672
|
+
var URL_ATTRIBUTES = /* @__PURE__ */ new Set(["href", "src", "poster", "cite"]);
|
|
673
|
+
var SCRIPT_URL = /^(javascript|vbscript|data):/i;
|
|
674
|
+
var DATA_IMAGE = /^data:image\/(png|gif|jpeg|webp|avif);/i;
|
|
675
|
+
var OUT_OF_PLACE_PROPERTIES = /* @__PURE__ */ new Set([
|
|
676
|
+
"position",
|
|
677
|
+
"top",
|
|
678
|
+
"right",
|
|
679
|
+
"bottom",
|
|
680
|
+
"left",
|
|
681
|
+
"z-index",
|
|
682
|
+
"-webkit-box-reflect"
|
|
683
|
+
]);
|
|
684
|
+
var OUT_OF_PLACE_PREFIXES = ["inset", "transform", "translate", "rotate", "scale", "offset"];
|
|
685
|
+
var VIEWPORT_LENGTH = /\d(?:[sld]?v|cq)(?:w|h|i|b|min|max)\b/i;
|
|
686
|
+
var PAINTS_BEYOND = /^(?:outline|box-shadow|text-shadow|filter|text-decoration|text-underline-offset|text-stroke)|^(?:border-image|mask-border|mask-box-image)(?:-outset)?$/;
|
|
687
|
+
var MAX_PAINT_PX = 40;
|
|
688
|
+
var NEGATIVE_LIMITS = /* @__PURE__ */ new Map([
|
|
689
|
+
["letter-spacing", { px: 4, em: 0.25 }],
|
|
690
|
+
["word-spacing", { px: 4, em: 0.25 }],
|
|
691
|
+
["text-indent", { px: MAX_PAINT_PX }]
|
|
692
|
+
]);
|
|
693
|
+
var NUMBER = /(?<![\w#.-])[+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?(%|[a-z]+)?/gi;
|
|
694
|
+
var NON_LENGTH_FUNCTION = /(?<![\w-])(?!drop-shadow\(|blur\()[\w-]+\([^()]*\)/g;
|
|
695
|
+
var COMPUTED_VALUE = /(?:var|calc|min|max|clamp|env|attr)\(/i;
|
|
696
|
+
var FETCHING_VALUE = /url\(|expression\(|@import/i;
|
|
697
|
+
var IGNORED_IN_SCHEME = /[\u0000- ]/g;
|
|
698
|
+
var IFRAME_SANDBOX = "allow-scripts allow-same-origin allow-popups allow-presentation";
|
|
699
|
+
var IFRAME_FEATURES = /* @__PURE__ */ new Set([
|
|
700
|
+
...["autoplay", "encrypted-media", "picture-in-picture", "fullscreen", "clipboard-write"],
|
|
701
|
+
...["accelerometer", "gyroscope", "web-share"]
|
|
702
|
+
]);
|
|
703
|
+
var IFRAME_REFERRER_POLICY = "strict-origin-when-cross-origin";
|
|
704
|
+
var EMBED_ORIGINS = [
|
|
705
|
+
"https://www.youtube.com",
|
|
706
|
+
"https://www.youtube-nocookie.com",
|
|
707
|
+
"https://player.vimeo.com",
|
|
708
|
+
"https://www.loom.com",
|
|
709
|
+
"https://fast.wistia.net"
|
|
710
|
+
];
|
|
711
|
+
function isAllowedAttribute(tag, name) {
|
|
712
|
+
return GLOBAL_ATTRIBUTES.has(name) || name.startsWith("aria-") || (ELEMENT_ATTRIBUTES.get(tag)?.has(name) ?? false);
|
|
713
|
+
}
|
|
714
|
+
function isSafeUrl(tag, name, value) {
|
|
715
|
+
const url2 = value.replace(IGNORED_IN_SCHEME, "");
|
|
716
|
+
if (!SCRIPT_URL.test(url2)) return true;
|
|
717
|
+
return tag === "img" && name === "src" && DATA_IMAGE.test(url2);
|
|
718
|
+
}
|
|
719
|
+
function urlOrNull(value, base) {
|
|
720
|
+
try {
|
|
721
|
+
return new URL(value, base);
|
|
722
|
+
} catch {
|
|
723
|
+
return null;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
function pageOf(document2, styleParser, embedOrigins) {
|
|
727
|
+
const baseUrl = document2.baseURI;
|
|
728
|
+
const origin = urlOrNull(baseUrl)?.origin ?? "null";
|
|
729
|
+
return {
|
|
730
|
+
origin: origin === "null" ? null : origin,
|
|
731
|
+
baseUrl,
|
|
732
|
+
styleParser,
|
|
733
|
+
embedOrigins: /* @__PURE__ */ new Set([...EMBED_ORIGINS, ...embedOrigins])
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
function isAllowedEmbed(value, page) {
|
|
737
|
+
const url2 = urlOrNull(value.trim(), page.baseUrl);
|
|
738
|
+
return url2 !== null && url2.origin !== page.origin && page.embedOrigins.has(url2.origin);
|
|
739
|
+
}
|
|
740
|
+
function allowedFeatures(value) {
|
|
741
|
+
const features = value.split(";").map((directive) => directive.trim().split(/\s+/)[0]?.toLowerCase() ?? "");
|
|
742
|
+
return [...new Set(features.filter((feature) => IFRAME_FEATURES.has(feature)))].join("; ");
|
|
743
|
+
}
|
|
744
|
+
function isOutOfPlace(property, value) {
|
|
745
|
+
if (OUT_OF_PLACE_PROPERTIES.has(property) || OUT_OF_PLACE_PREFIXES.some((prefix) => property.startsWith(prefix))) {
|
|
746
|
+
return true;
|
|
451
747
|
}
|
|
748
|
+
if (VIEWPORT_LENGTH.test(value)) return true;
|
|
749
|
+
const limits = NEGATIVE_LIMITS.get(property);
|
|
750
|
+
if (limits) return COMPUTED_VALUE.test(value) || movesBackTooFar(value, limits);
|
|
751
|
+
return property.startsWith("margin") && value.includes("-");
|
|
752
|
+
}
|
|
753
|
+
function movesBackTooFar(value, limits) {
|
|
754
|
+
return [...value.matchAll(NUMBER)].some(([number = "", unit = ""]) => {
|
|
755
|
+
const amount = parseFloat(number);
|
|
756
|
+
if (amount >= 0) return false;
|
|
757
|
+
const limit = limits[unit.toLowerCase()];
|
|
758
|
+
return limit === void 0 || -amount > limit;
|
|
759
|
+
});
|
|
452
760
|
}
|
|
453
|
-
function
|
|
761
|
+
function paintsTooFar(property, value) {
|
|
762
|
+
if (!PAINTS_BEYOND.test(property.replace(/^-webkit-/, ""))) return false;
|
|
763
|
+
if (COMPUTED_VALUE.test(value)) return true;
|
|
764
|
+
return [...value.replace(NON_LENGTH_FUNCTION, "").matchAll(NUMBER)].some(([number = "", unit = ""]) => {
|
|
765
|
+
const amount = Math.abs(parseFloat(number));
|
|
766
|
+
return amount !== 0 && (unit.toLowerCase() !== "px" || amount > MAX_PAINT_PX);
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
function cleanStyle(value, styleParser) {
|
|
770
|
+
const style = styleParser.style;
|
|
771
|
+
style.cssText = value;
|
|
772
|
+
const properties = Array.from({ length: style.length }, (_, index) => style.item(index));
|
|
773
|
+
for (const property of properties) {
|
|
774
|
+
const value2 = style.getPropertyValue(property);
|
|
775
|
+
const remove = isOutOfPlace(property, value2) || paintsTooFar(property, value2) || FETCHING_VALUE.test(value2);
|
|
776
|
+
if (remove) style.removeProperty(property);
|
|
777
|
+
}
|
|
778
|
+
const cleaned = style.cssText;
|
|
779
|
+
styleParser.removeAttribute("style");
|
|
780
|
+
return cleaned;
|
|
781
|
+
}
|
|
782
|
+
function cleanAttributes(element, page) {
|
|
783
|
+
const tag = element.localName;
|
|
784
|
+
for (const attribute of [...element.attributes]) {
|
|
785
|
+
const name = attribute.name.toLowerCase();
|
|
786
|
+
const isUrl = URL_ATTRIBUTES.has(name);
|
|
787
|
+
const keep = isAllowedAttribute(tag, name) && (!isUrl || isSafeUrl(tag, name, attribute.value));
|
|
788
|
+
if (!keep) element.removeAttribute(attribute.name);
|
|
789
|
+
}
|
|
790
|
+
const style = element.getAttribute("style");
|
|
791
|
+
if (style === null) return;
|
|
792
|
+
const cleaned = cleanStyle(style, page.styleParser);
|
|
793
|
+
if (cleaned === "") element.removeAttribute("style");
|
|
794
|
+
else element.setAttribute("style", cleaned);
|
|
795
|
+
}
|
|
796
|
+
function hardenEmbedsAndLinks(element) {
|
|
797
|
+
if (element.localName === "iframe") {
|
|
798
|
+
const allow = allowedFeatures(element.getAttribute("allow") ?? "");
|
|
799
|
+
if (allow === "") element.removeAttribute("allow");
|
|
800
|
+
else element.setAttribute("allow", allow);
|
|
801
|
+
element.setAttribute("sandbox", IFRAME_SANDBOX);
|
|
802
|
+
element.setAttribute("referrerpolicy", IFRAME_REFERRER_POLICY);
|
|
803
|
+
return;
|
|
804
|
+
}
|
|
805
|
+
if (element.localName !== "a" || element.getAttribute("target")?.toLowerCase() !== "_blank") return;
|
|
806
|
+
const rel = new Set((element.getAttribute("rel") ?? "").split(/\s+/).filter(Boolean));
|
|
807
|
+
rel.add("noopener");
|
|
808
|
+
rel.add("noreferrer");
|
|
809
|
+
element.setAttribute("rel", [...rel].join(" "));
|
|
810
|
+
}
|
|
811
|
+
function cleanElement(element, page) {
|
|
812
|
+
const tag = element.localName;
|
|
813
|
+
const isHtml = element.namespaceURI === HTML_NAMESPACE;
|
|
814
|
+
if (isHtml && REMOVED_WITH_CONTENT.has(tag)) {
|
|
815
|
+
element.remove();
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
818
|
+
if (!isHtml || !ALLOWED_ELEMENTS.has(tag)) {
|
|
819
|
+
element.replaceWith(...element.childNodes);
|
|
820
|
+
return;
|
|
821
|
+
}
|
|
822
|
+
if (tag === "iframe" && !isAllowedEmbed(element.getAttribute("src") ?? "", page)) {
|
|
823
|
+
element.remove();
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
cleanAttributes(element, page);
|
|
827
|
+
hardenEmbedsAndLinks(element);
|
|
828
|
+
}
|
|
829
|
+
function storeHtmlFragment(html, document2 = globalThis.document, embedOrigins = []) {
|
|
454
830
|
const template = document2.createElement("template");
|
|
455
831
|
template.innerHTML = html ?? "";
|
|
456
|
-
|
|
832
|
+
const page = pageOf(document2, template, embedOrigins);
|
|
833
|
+
for (const element of [...template.content.querySelectorAll("*")]) cleanElement(element, page);
|
|
457
834
|
return template.content;
|
|
458
835
|
}
|
|
459
836
|
|
|
460
|
-
// src/shopify-values.
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
STORE_ENV_NAMES: () => STORE_ENV_NAMES,
|
|
464
|
-
formatMoney: () => formatMoney,
|
|
465
|
-
isHttpsUrl: () => isHttpsUrl,
|
|
466
|
-
isSecretShopifyToken: () => isSecretShopifyToken,
|
|
467
|
-
productKey: () => productKey,
|
|
468
|
-
productUrl: () => productUrl
|
|
469
|
-
});
|
|
470
|
-
var STORE_ENV_NAMES = {
|
|
471
|
-
domain: ["PUBLIC_SHOPIFY_DOMAIN", "PUBLIC_SHOPIFY_STORE_DOMAIN", "SHOPIFY_DOMAIN"],
|
|
472
|
-
token: ["PUBLIC_SHOPIFY_STOREFRONT_TOKEN", "PUBLIC_SHOPIFY_STOREFRONT_ACCESS_TOKEN", "SHOPIFY_STOREFRONT_TOKEN"]
|
|
473
|
-
};
|
|
474
|
-
function isHttpsUrl(url) {
|
|
475
|
-
if (typeof url !== "string") return false;
|
|
837
|
+
// src/shopify-values.ts
|
|
838
|
+
function isHttpsUrl(url2) {
|
|
839
|
+
if (typeof url2 !== "string") return false;
|
|
476
840
|
try {
|
|
477
|
-
return new URL(
|
|
841
|
+
return new URL(url2).protocol === "https:";
|
|
478
842
|
} catch {
|
|
479
843
|
return false;
|
|
480
844
|
}
|
|
481
845
|
}
|
|
482
|
-
function isSecretShopifyToken(token) {
|
|
483
|
-
return /^shp[a-z]{2}_/.test(token.trim());
|
|
484
|
-
}
|
|
485
846
|
function formatMoney(money, locale) {
|
|
486
847
|
if (!money) return "";
|
|
487
848
|
const amount = parseFloat(money.amount);
|
|
@@ -495,8 +856,12 @@
|
|
|
495
856
|
}
|
|
496
857
|
}
|
|
497
858
|
function productUrl(productPath, handle, id) {
|
|
498
|
-
if (!/\{(handle|id)\}/.test(productPath))
|
|
499
|
-
|
|
859
|
+
if (!/\{(handle|id)\}/.test(productPath)) {
|
|
860
|
+
return handle ? `${productPath.replace(/\/$/, "")}/${encodeURIComponent(handle)}` : null;
|
|
861
|
+
}
|
|
862
|
+
const numericId2 = id ? id.split("/").at(-1) ?? "" : "";
|
|
863
|
+
if (productPath.includes("{handle}") && !handle) return null;
|
|
864
|
+
if (productPath.includes("{id}") && !numericId2) return null;
|
|
500
865
|
return productPath.replace(/\{handle\}/g, encodeURIComponent(handle ?? "")).replace(/\{id\}/g, encodeURIComponent(numericId2));
|
|
501
866
|
}
|
|
502
867
|
function productKey(raw) {
|
|
@@ -507,43 +872,44 @@
|
|
|
507
872
|
return /\/products\/([^/?#]+)/.exec(value)?.[1] ?? value;
|
|
508
873
|
}
|
|
509
874
|
|
|
510
|
-
// src/images.
|
|
511
|
-
function imageUrl(
|
|
512
|
-
if (!
|
|
875
|
+
// src/images.ts
|
|
876
|
+
function imageUrl(url2, { width, height, crop } = {}) {
|
|
877
|
+
if (!url2) return url2;
|
|
513
878
|
let parsed;
|
|
514
879
|
try {
|
|
515
|
-
parsed = new URL(
|
|
880
|
+
parsed = new URL(url2);
|
|
516
881
|
} catch (error) {
|
|
517
|
-
if (error instanceof TypeError) return
|
|
882
|
+
if (error instanceof TypeError) return url2;
|
|
518
883
|
throw error;
|
|
519
884
|
}
|
|
520
885
|
if (width) parsed.searchParams.set("width", String(width));
|
|
521
886
|
if (height) parsed.searchParams.set("height", String(height));
|
|
522
887
|
if (crop) parsed.searchParams.set("crop", crop);
|
|
523
|
-
return (
|
|
524
|
-
/** @type {Url} */
|
|
525
|
-
parsed.toString()
|
|
526
|
-
);
|
|
888
|
+
return parsed.toString();
|
|
527
889
|
}
|
|
528
890
|
function sizeAttribute(el, name) {
|
|
529
891
|
return el.getAttribute(name) ?? el.getAttribute(attr(name)) ?? el.getAttribute(`data-${name}`);
|
|
530
892
|
}
|
|
531
|
-
function cdnUrl(
|
|
532
|
-
if (!
|
|
893
|
+
function cdnUrl(url2, el) {
|
|
894
|
+
if (!url2 || !el) return url2;
|
|
533
895
|
const width = sizeAttribute(el, "max-width");
|
|
534
896
|
const height = sizeAttribute(el, "max-height");
|
|
535
|
-
return imageUrl(
|
|
897
|
+
return imageUrl(url2, { width, height, crop: width || height ? sizeAttribute(el, "crop") : null });
|
|
536
898
|
}
|
|
537
|
-
var
|
|
538
|
-
function
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
899
|
+
var withoutQuery = (url2) => url2.replace(/\?.*$/, "");
|
|
900
|
+
function sameImage(a, b) {
|
|
901
|
+
if (!a || !b) return false;
|
|
902
|
+
return withoutQuery(a) === withoutQuery(b);
|
|
903
|
+
}
|
|
904
|
+
function cssUrl(url2) {
|
|
905
|
+
const hasScheme = /^\s*[a-z][a-z0-9+.-]*:/i.test(url2);
|
|
906
|
+
if (hasScheme && !/^\s*(https?:|data:image\/)/i.test(url2)) return "none";
|
|
907
|
+
const escaped = url2.replace(/["\\\n\r\f]/g, (char) => `\\${char.charCodeAt(0).toString(16)} `);
|
|
542
908
|
return `url("${escaped}")`;
|
|
543
909
|
}
|
|
544
|
-
function setImage(el,
|
|
545
|
-
if (!
|
|
546
|
-
const src = cdnUrl(
|
|
910
|
+
function setImage(el, url2, alt) {
|
|
911
|
+
if (!url2) return;
|
|
912
|
+
const src = cdnUrl(url2, el);
|
|
547
913
|
if (el.tagName !== "IMG") {
|
|
548
914
|
el.style.backgroundImage = cssUrl(src);
|
|
549
915
|
return;
|
|
@@ -559,21 +925,15 @@
|
|
|
559
925
|
if (!el.getAttribute("loading")) el.setAttribute("loading", "lazy");
|
|
560
926
|
}
|
|
561
927
|
|
|
562
|
-
// src/repeaters.
|
|
928
|
+
// src/repeaters.ts
|
|
563
929
|
var templates = /* @__PURE__ */ new WeakMap();
|
|
564
930
|
var hasTemplate = (container) => Boolean(templates.get(container));
|
|
565
|
-
var copyOf = (template) => (
|
|
566
|
-
/** @type {HTMLElement} */
|
|
567
|
-
template.cloneNode(true)
|
|
568
|
-
);
|
|
931
|
+
var copyOf = (template) => template.cloneNode(true);
|
|
569
932
|
function templateOf(container) {
|
|
570
933
|
const known = templates.get(container);
|
|
571
934
|
if (known !== void 0) return known;
|
|
572
935
|
const first = container.firstElementChild;
|
|
573
|
-
const template = first ? copyOf(
|
|
574
|
-
/** @type {HTMLElement} */
|
|
575
|
-
first
|
|
576
|
-
) : null;
|
|
936
|
+
const template = first ? copyOf(first) : null;
|
|
577
937
|
templates.set(container, template);
|
|
578
938
|
container.replaceChildren();
|
|
579
939
|
return template;
|
|
@@ -628,8 +988,7 @@
|
|
|
628
988
|
}
|
|
629
989
|
const keyAttribute = `data-${cfg.prefix}-key`;
|
|
630
990
|
const leavingAttribute = `data-${cfg.prefix}-leaving`;
|
|
631
|
-
const live = () => [
|
|
632
|
-
container.children].filter(
|
|
991
|
+
const live = () => [...container.children].filter(
|
|
633
992
|
(node) => node.hasAttribute(keyAttribute) && !node.hasAttribute(leavingAttribute)
|
|
634
993
|
);
|
|
635
994
|
const existing = new Map(live().map((node) => [node.getAttribute(keyAttribute), node]));
|
|
@@ -667,7 +1026,7 @@
|
|
|
667
1026
|
});
|
|
668
1027
|
}
|
|
669
1028
|
|
|
670
|
-
// src/dom.
|
|
1029
|
+
// src/dom.ts
|
|
671
1030
|
var SEEN = "data-tether-seen";
|
|
672
1031
|
var SHOWN = "data-tether-shown";
|
|
673
1032
|
var PREVIOUS_DISPLAY = "data-tether-prev-display";
|
|
@@ -698,14 +1057,14 @@
|
|
|
698
1057
|
return formatOr(() => new Date(iso).toLocaleDateString(cfg.locale || void 0), "");
|
|
699
1058
|
}
|
|
700
1059
|
function setStoreHtml(el, html) {
|
|
701
|
-
el.replaceChildren(storeHtmlFragment(html, el.ownerDocument));
|
|
1060
|
+
el.replaceChildren(storeHtmlFragment(html, el.ownerDocument, cfg.embedOrigins));
|
|
702
1061
|
}
|
|
703
1062
|
var isTextField = (el) => el.tagName === "INPUT" || el.tagName === "TEXTAREA";
|
|
704
1063
|
var isInput = (el) => el.tagName === "INPUT";
|
|
705
1064
|
function setText(el, value) {
|
|
706
|
-
const
|
|
707
|
-
if (isTextField(el)) el.value =
|
|
708
|
-
else el.textContent =
|
|
1065
|
+
const text2 = value == null ? "" : String(value);
|
|
1066
|
+
if (isTextField(el)) el.value = text2;
|
|
1067
|
+
else el.textContent = text2;
|
|
709
1068
|
}
|
|
710
1069
|
var readText = (el) => isTextField(el) ? el.value : el.textContent;
|
|
711
1070
|
function setTextTracked(el, value) {
|
|
@@ -754,15 +1113,22 @@
|
|
|
754
1113
|
if (input && isInput(input)) input.checked = selected;
|
|
755
1114
|
}
|
|
756
1115
|
var layoutTimer;
|
|
1116
|
+
var pageGlobal = (name) => Reflect.get(window, name);
|
|
1117
|
+
function refreshScrollTrigger() {
|
|
1118
|
+
const scrollTrigger = pageGlobal("ScrollTrigger");
|
|
1119
|
+
if (typeof scrollTrigger !== "function") return;
|
|
1120
|
+
const refresh = Reflect.get(scrollTrigger, "refresh");
|
|
1121
|
+
if (typeof refresh === "function") Reflect.apply(refresh, scrollTrigger, []);
|
|
1122
|
+
}
|
|
757
1123
|
function notifyLayoutChanged(reason) {
|
|
758
1124
|
clearTimeout(layoutTimer);
|
|
759
1125
|
layoutTimer = setTimeout(() => {
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
)
|
|
764
|
-
|
|
765
|
-
|
|
1126
|
+
try {
|
|
1127
|
+
if (cfg.onLayoutChange) cfg.onLayoutChange(reason);
|
|
1128
|
+
else refreshScrollTrigger();
|
|
1129
|
+
} catch (error) {
|
|
1130
|
+
reportError(error, "layout-changed");
|
|
1131
|
+
}
|
|
766
1132
|
emit("layout-changed", { reason });
|
|
767
1133
|
}, LAYOUT_DEBOUNCE_MS);
|
|
768
1134
|
}
|
|
@@ -789,15 +1155,27 @@
|
|
|
789
1155
|
const raw = el ? get(el, name) : null;
|
|
790
1156
|
if (!raw) return null;
|
|
791
1157
|
try {
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
JSON.parse(raw)
|
|
795
|
-
);
|
|
1158
|
+
const json = JSON.parse(raw);
|
|
1159
|
+
return json;
|
|
796
1160
|
} catch {
|
|
797
1161
|
warn(`invalid JSON in ${attr(name)}`, el);
|
|
798
1162
|
return null;
|
|
799
1163
|
}
|
|
800
1164
|
}
|
|
1165
|
+
function attributeText(value) {
|
|
1166
|
+
if (value === null || value === void 0) return "";
|
|
1167
|
+
if (typeof value === "string") return value;
|
|
1168
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
1169
|
+
return JSON.stringify(value);
|
|
1170
|
+
}
|
|
1171
|
+
function attributesFromJson(json) {
|
|
1172
|
+
if (Array.isArray(json)) {
|
|
1173
|
+
const entries = json;
|
|
1174
|
+
return entries.filter(isRecord).map((entry) => ({ key: attributeText(entry["key"]), value: attributeText(entry["value"]) }));
|
|
1175
|
+
}
|
|
1176
|
+
if (!isRecord(json)) return [];
|
|
1177
|
+
return Object.entries(json).map(([key, value]) => ({ key, value: attributeText(value) }));
|
|
1178
|
+
}
|
|
801
1179
|
function toAttributes(attributes) {
|
|
802
1180
|
if (!attributes) return [];
|
|
803
1181
|
if (Array.isArray(attributes)) {
|
|
@@ -806,14 +1184,10 @@
|
|
|
806
1184
|
return Object.entries(attributes).map(([key, value]) => ({ key, value: String(value ?? "") }));
|
|
807
1185
|
}
|
|
808
1186
|
|
|
809
|
-
// src/fragments.
|
|
810
|
-
var fragments_exports = {};
|
|
811
|
-
__export(fragments_exports, {
|
|
812
|
-
cartFragment: () => cartFragment,
|
|
813
|
-
collectionFields: () => collectionFields,
|
|
814
|
-
productFragment: () => productFragment
|
|
815
|
-
});
|
|
1187
|
+
// src/fragments.ts
|
|
816
1188
|
var MONEY = "amount currencyCode";
|
|
1189
|
+
var FULL_ACCESS = { inventory: true };
|
|
1190
|
+
var inventoryField = (access) => access.inventory ? "quantityAvailable" : "";
|
|
817
1191
|
var IMAGE = "url altText width height";
|
|
818
1192
|
function metafieldsField(metafields) {
|
|
819
1193
|
const identifiers = [];
|
|
@@ -827,11 +1201,11 @@
|
|
|
827
1201
|
}
|
|
828
1202
|
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}}}}}`;
|
|
829
1203
|
var ALLOCATION_FIELDS = ` sellingPlan{id}checkoutChargeAmount{${MONEY}}remainingBalanceChargeAmount{${MONEY}}priceAdjustments{price{${MONEY}}compareAtPrice{${MONEY}}perDeliveryPrice{${MONEY}}unitPrice{${MONEY}}}`;
|
|
830
|
-
function productFragment(o = cfg) {
|
|
831
|
-
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.metafields)} 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
|
|
1204
|
+
function productFragment(o = cfg, access = FULL_ACCESS) {
|
|
1205
|
+
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.metafields)} 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 ${inventoryField(access)} sku barcode selectedOptions{name value}image{${IMAGE}}price{${MONEY}}compareAtPrice{${MONEY}}unitPrice{${MONEY}}sellingPlanAllocations(first:${o.sellingPlansFirst}){nodes{${ALLOCATION_FIELDS}}}}}}`;
|
|
832
1206
|
}
|
|
833
|
-
function cartFragment() {
|
|
834
|
-
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
|
|
1207
|
+
function cartFragment(access = FULL_ACCESS) {
|
|
1208
|
+
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 ${inventoryField(access)} 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}}}}}}}}`;
|
|
835
1209
|
}
|
|
836
1210
|
function collectionFields() {
|
|
837
1211
|
return `
|
|
@@ -840,7 +1214,7 @@
|
|
|
840
1214
|
seo { title description }`;
|
|
841
1215
|
}
|
|
842
1216
|
|
|
843
|
-
// src/selling-plans.
|
|
1217
|
+
// src/selling-plans.ts
|
|
844
1218
|
var selling_plans_exports = {};
|
|
845
1219
|
__export(selling_plans_exports, {
|
|
846
1220
|
GID: () => GID,
|
|
@@ -855,19 +1229,59 @@
|
|
|
855
1229
|
plansForVariant: () => plansForVariant,
|
|
856
1230
|
toGid: () => toGid
|
|
857
1231
|
});
|
|
1232
|
+
|
|
1233
|
+
// src/amounts.ts
|
|
1234
|
+
var DECIMAL = /^(-?)(\d+)(?:\.(\d+))?$/;
|
|
1235
|
+
function toScaledInteger(amount, fractionDigits) {
|
|
1236
|
+
const match = DECIMAL.exec(amount.trim());
|
|
1237
|
+
if (!match) return null;
|
|
1238
|
+
const [, sign = "", whole = "0", fraction = ""] = match;
|
|
1239
|
+
const digits = BigInt(`${whole}${fraction.padEnd(fractionDigits, "0")}`);
|
|
1240
|
+
return sign === "-" ? -digits : digits;
|
|
1241
|
+
}
|
|
1242
|
+
var fractionLength = (amount) => amount.split(".")[1]?.length ?? 0;
|
|
1243
|
+
function isGreaterAmount(amount, other) {
|
|
1244
|
+
if (amount == null || other == null) return false;
|
|
1245
|
+
const fractionDigits = Math.max(fractionLength(amount.trim()), fractionLength(other.trim()));
|
|
1246
|
+
const scaled = toScaledInteger(amount, fractionDigits);
|
|
1247
|
+
const scaledOther = toScaledInteger(other, fractionDigits);
|
|
1248
|
+
if (scaled === null || scaledOther === null) return false;
|
|
1249
|
+
return scaled > scaledOther;
|
|
1250
|
+
}
|
|
1251
|
+
var DEFAULT_FRACTION_DIGITS = 2;
|
|
1252
|
+
var fractionDigitsByCurrency = /* @__PURE__ */ new Map();
|
|
1253
|
+
function currencyFractionDigits(currencyCode) {
|
|
1254
|
+
const known = fractionDigitsByCurrency.get(currencyCode);
|
|
1255
|
+
if (known !== void 0) return known;
|
|
1256
|
+
let digits = DEFAULT_FRACTION_DIGITS;
|
|
1257
|
+
try {
|
|
1258
|
+
const format = new Intl.NumberFormat("en", { style: "currency", currency: currencyCode });
|
|
1259
|
+
digits = format.resolvedOptions().maximumFractionDigits ?? DEFAULT_FRACTION_DIGITS;
|
|
1260
|
+
} catch {
|
|
1261
|
+
}
|
|
1262
|
+
fractionDigitsByCurrency.set(currencyCode, digits);
|
|
1263
|
+
return digits;
|
|
1264
|
+
}
|
|
1265
|
+
function toMinorUnits(money) {
|
|
1266
|
+
return Math.round(parseFloat(money.amount) * 10 ** currencyFractionDigits(money.currencyCode));
|
|
1267
|
+
}
|
|
1268
|
+
function fromMinorUnits(minorUnits, currencyCode) {
|
|
1269
|
+
const digits = currencyFractionDigits(currencyCode);
|
|
1270
|
+
return { amount: (minorUnits / 10 ** digits).toFixed(digits), currencyCode };
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
// src/selling-plans.ts
|
|
1274
|
+
var PERCENT = 100;
|
|
858
1275
|
function toGid(value, type) {
|
|
859
|
-
const
|
|
860
|
-
if (!
|
|
861
|
-
if (
|
|
862
|
-
if (/^\d+$/.test(
|
|
863
|
-
return
|
|
1276
|
+
const text2 = typeof value === "string" || typeof value === "number" ? String(value).trim() : "";
|
|
1277
|
+
if (!text2) return null;
|
|
1278
|
+
if (text2.startsWith("gid://")) return text2;
|
|
1279
|
+
if (/^\d+$/.test(text2)) return `gid://shopify/${type}/${text2}`;
|
|
1280
|
+
return text2;
|
|
864
1281
|
}
|
|
865
1282
|
var GID = {
|
|
866
|
-
/** @param {unknown} value */
|
|
867
1283
|
product: (value) => toGid(value, "Product"),
|
|
868
|
-
/** @param {unknown} value */
|
|
869
1284
|
variant: (value) => toGid(value, "ProductVariant"),
|
|
870
|
-
/** @param {unknown} value */
|
|
871
1285
|
sellingPlan: (value) => toGid(value, "SellingPlan")
|
|
872
1286
|
};
|
|
873
1287
|
function allPlans(product) {
|
|
@@ -894,14 +1308,17 @@
|
|
|
894
1308
|
return allocation?.checkoutChargeAmount ?? allocation?.priceAdjustments[0]?.price ?? variant?.price ?? null;
|
|
895
1309
|
}
|
|
896
1310
|
function planSavings(variant, planId) {
|
|
897
|
-
const
|
|
898
|
-
|
|
899
|
-
|
|
1311
|
+
const onPlanPrice = planPrice(variant, planId);
|
|
1312
|
+
if (!variant || !onPlanPrice) return { amount: null, percentage: 0 };
|
|
1313
|
+
const oneTime = parseFloat(variant.price.amount);
|
|
1314
|
+
const onPlan = parseFloat(onPlanPrice.amount);
|
|
1315
|
+
if (!Number.isFinite(oneTime) || !Number.isFinite(onPlan) || oneTime <= 0 || onPlan >= oneTime) {
|
|
900
1316
|
return { amount: null, percentage: 0 };
|
|
901
1317
|
}
|
|
1318
|
+
const saved = toMinorUnits(variant.price) - toMinorUnits(onPlanPrice);
|
|
902
1319
|
return {
|
|
903
|
-
amount:
|
|
904
|
-
percentage: Math.round((1 - onPlan / oneTime) *
|
|
1320
|
+
amount: fromMinorUnits(saved, variant.price.currencyCode),
|
|
1321
|
+
percentage: Math.round((1 - onPlan / oneTime) * PERCENT)
|
|
905
1322
|
};
|
|
906
1323
|
}
|
|
907
1324
|
function planAdjustment(plan) {
|
|
@@ -926,7 +1343,40 @@
|
|
|
926
1343
|
return plans.find((plan) => plan.id === gid) ?? plans.find((plan) => plan.name.toLowerCase() === needle) ?? plans.find((plan) => plan.options.some((option) => option.value.toLowerCase() === needle)) ?? null;
|
|
927
1344
|
}
|
|
928
1345
|
|
|
929
|
-
// src/
|
|
1346
|
+
// src/product-shape.ts
|
|
1347
|
+
var MONEY2 = { amount: "string", currencyCode: "string" };
|
|
1348
|
+
var PRODUCT = {
|
|
1349
|
+
id: "string",
|
|
1350
|
+
handle: "string",
|
|
1351
|
+
title: "string",
|
|
1352
|
+
options: ["object"],
|
|
1353
|
+
images: { nodes: ["object"] },
|
|
1354
|
+
priceRange: { minVariantPrice: MONEY2, maxVariantPrice: MONEY2 },
|
|
1355
|
+
variants: { nodes: [{ id: "string", price: MONEY2, selectedOptions: ["object"] }] }
|
|
1356
|
+
};
|
|
1357
|
+
var PRODUCT_CONNECTION = { nodes: [PRODUCT], pageInfo: { hasNextPage: "boolean" } };
|
|
1358
|
+
var isProductShape = (value) => field(value, "id") !== "" && field(value, "handle") !== "" && matches(value, PRODUCT);
|
|
1359
|
+
var PRODUCT_NODE = (node) => isRecord(node) && (node["id"] === void 0 || matches(node, PRODUCT));
|
|
1360
|
+
|
|
1361
|
+
// src/cart/cart-shape.ts
|
|
1362
|
+
var LINE = {
|
|
1363
|
+
id: "string",
|
|
1364
|
+
quantity: "number",
|
|
1365
|
+
attributes: ["object"],
|
|
1366
|
+
cost: { totalAmount: MONEY2, subtotalAmount: MONEY2, amountPerQuantity: MONEY2 },
|
|
1367
|
+
merchandise: { id: "string", selectedOptions: ["object"], product: "object" }
|
|
1368
|
+
};
|
|
1369
|
+
var CART = {
|
|
1370
|
+
id: "string",
|
|
1371
|
+
checkoutUrl: "string",
|
|
1372
|
+
totalQuantity: "number",
|
|
1373
|
+
attributes: ["object"],
|
|
1374
|
+
discountCodes: ["object"],
|
|
1375
|
+
cost: { totalAmount: MONEY2, subtotalAmount: MONEY2 },
|
|
1376
|
+
lines: { nodes: [LINE] }
|
|
1377
|
+
};
|
|
1378
|
+
|
|
1379
|
+
// src/cart/drawer.ts
|
|
930
1380
|
var FOCUSABLE = 'a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])';
|
|
931
1381
|
var FOCUS_AFTER_OPEN_MS = 60;
|
|
932
1382
|
var openWithoutDrawer = false;
|
|
@@ -1014,19 +1464,7 @@
|
|
|
1014
1464
|
(event.shiftKey ? last : first).focus();
|
|
1015
1465
|
}
|
|
1016
1466
|
|
|
1017
|
-
// src/loader.
|
|
1018
|
-
var loader_exports = {};
|
|
1019
|
-
__export(loader_exports, {
|
|
1020
|
-
clearProductCache: () => clearProductCache,
|
|
1021
|
-
getProduct: () => getProduct,
|
|
1022
|
-
getProducts: () => getProducts,
|
|
1023
|
-
getRecommendations: () => getRecommendations,
|
|
1024
|
-
hydrateProducts: () => hydrateProducts,
|
|
1025
|
-
normalizeProductKey: () => normalizeProductKey,
|
|
1026
|
-
primeProduct: () => primeProduct,
|
|
1027
|
-
refreshProducts: () => refreshProducts,
|
|
1028
|
-
takeStale: () => takeStale
|
|
1029
|
-
});
|
|
1467
|
+
// src/loader.ts
|
|
1030
1468
|
var MISS_CACHE_MS = 1e4;
|
|
1031
1469
|
var FRESH_MS = 6e4;
|
|
1032
1470
|
var productCache = /* @__PURE__ */ new Map();
|
|
@@ -1060,6 +1498,7 @@
|
|
|
1060
1498
|
productCache.set(key, loading);
|
|
1061
1499
|
return loading;
|
|
1062
1500
|
}
|
|
1501
|
+
var withProductFragment = (operation) => (access) => `${operation}${productFragment(cfg, access)}`;
|
|
1063
1502
|
function batchQuery(keys) {
|
|
1064
1503
|
const ids = keys.filter(isGid);
|
|
1065
1504
|
const handles = keys.filter((key) => !isGid(key));
|
|
@@ -1076,8 +1515,8 @@
|
|
|
1076
1515
|
variables[`h${index}`] = handle;
|
|
1077
1516
|
selections.push(`h${index}: product(handle: $h${index}) { ...TetherProduct }`);
|
|
1078
1517
|
});
|
|
1079
|
-
const
|
|
1080
|
-
return { query, variables, handles };
|
|
1518
|
+
const query2 = withProductFragment(`query TetherProducts(${definitions.join(",")}){${selections.join(" ")}}`);
|
|
1519
|
+
return { query: query2, variables, handles };
|
|
1081
1520
|
}
|
|
1082
1521
|
function settleWaiting(key, settle) {
|
|
1083
1522
|
const waiter = waiting.get(key);
|
|
@@ -1091,16 +1530,21 @@
|
|
|
1091
1530
|
if (productCache.get(key) === miss) productCache.delete(key);
|
|
1092
1531
|
}, MISS_CACHE_MS);
|
|
1093
1532
|
}
|
|
1533
|
+
function isBatchData(data) {
|
|
1534
|
+
if (!isRecord(data)) return false;
|
|
1535
|
+
const { byId = [], ...byHandle } = data;
|
|
1536
|
+
return matches(byId, [orNull(PRODUCT_NODE)]) && Object.values(byHandle).every(orNull(PRODUCT));
|
|
1537
|
+
}
|
|
1094
1538
|
async function flush() {
|
|
1095
1539
|
flushScheduled = false;
|
|
1096
1540
|
const batchGeneration = generation;
|
|
1097
1541
|
const keys = pending.splice(0);
|
|
1098
1542
|
if (!keys.length) return;
|
|
1099
|
-
const { query, variables, handles } = batchQuery(keys);
|
|
1543
|
+
const { query: query2, variables, handles } = batchQuery(keys);
|
|
1100
1544
|
log("fetch products", keys);
|
|
1101
1545
|
let data;
|
|
1102
1546
|
try {
|
|
1103
|
-
data = await gql(
|
|
1547
|
+
data = await gql(query2, variables, isBatchData);
|
|
1104
1548
|
} catch (error) {
|
|
1105
1549
|
keys.forEach((key) => {
|
|
1106
1550
|
settleWaiting(key, (waiter) => {
|
|
@@ -1111,7 +1555,7 @@
|
|
|
1111
1555
|
return;
|
|
1112
1556
|
}
|
|
1113
1557
|
const found = /* @__PURE__ */ new Map();
|
|
1114
|
-
for (const product of data.byId ?? []) if (product) found.set(product.id, product);
|
|
1558
|
+
for (const product of data.byId ?? []) if (product?.id) found.set(product.id, product);
|
|
1115
1559
|
handles.forEach((handle, index) => {
|
|
1116
1560
|
const product = data[`h${index}`];
|
|
1117
1561
|
if (product) found.set(handle, product);
|
|
@@ -1157,51 +1601,57 @@
|
|
|
1157
1601
|
void productCache.get(key)?.then((product) => {
|
|
1158
1602
|
stale.delete(product.handle);
|
|
1159
1603
|
stale.delete(product.id);
|
|
1160
|
-
});
|
|
1604
|
+
}, failureIsReportedByTheRender);
|
|
1161
1605
|
return true;
|
|
1162
1606
|
}
|
|
1163
|
-
function
|
|
1164
|
-
for (const key of keys) {
|
|
1165
|
-
const normalized = normalizeProductKey(key);
|
|
1166
|
-
if (normalized) productCache.delete(normalized);
|
|
1167
|
-
}
|
|
1168
|
-
return keys.map((key) => getProduct(key));
|
|
1607
|
+
function failureIsReportedByTheRender() {
|
|
1169
1608
|
}
|
|
1609
|
+
function refreshProduct(key) {
|
|
1610
|
+
const normalized = normalizeProductKey(key);
|
|
1611
|
+
if (normalized) productCache.delete(normalized);
|
|
1612
|
+
return getProduct(key);
|
|
1613
|
+
}
|
|
1614
|
+
var isCollectionAnswer = shaped({
|
|
1615
|
+
collection: orNull({ handle: "string", products: PRODUCT_CONNECTION })
|
|
1616
|
+
});
|
|
1617
|
+
var isSearchAnswer = shaped({ products: PRODUCT_CONNECTION });
|
|
1618
|
+
var isRecommendationsAnswer = shaped({
|
|
1619
|
+
productRecommendations: orNull([PRODUCT])
|
|
1620
|
+
});
|
|
1170
1621
|
var COLLECTION_QUERY = `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}}}}`;
|
|
1171
1622
|
var SEARCH_QUERY = `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}}}`;
|
|
1172
1623
|
async function getProducts({
|
|
1173
1624
|
collection,
|
|
1174
|
-
query,
|
|
1625
|
+
query: query2,
|
|
1175
1626
|
first = cfg.collectionFirst,
|
|
1176
1627
|
after = null,
|
|
1177
1628
|
sortKey,
|
|
1178
1629
|
reverse = false,
|
|
1179
1630
|
filters
|
|
1180
1631
|
} = {}) {
|
|
1181
|
-
const fragment = productFragment();
|
|
1182
1632
|
if (collection) {
|
|
1183
|
-
const data2 = await gql(
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1633
|
+
const data2 = await gql(
|
|
1634
|
+
withProductFragment(COLLECTION_QUERY),
|
|
1635
|
+
{
|
|
1636
|
+
handle: collection,
|
|
1637
|
+
first,
|
|
1638
|
+
after,
|
|
1639
|
+
sortKey: sortKey || void 0,
|
|
1640
|
+
reverse,
|
|
1641
|
+
filters: filters ?? void 0
|
|
1642
|
+
},
|
|
1643
|
+
isCollectionAnswer
|
|
1644
|
+
);
|
|
1192
1645
|
if (!data2.collection) throw new TetherError(`Collection not found: ${collection}`, { code: "NOT_FOUND" });
|
|
1193
1646
|
const { products, ...rest } = data2.collection;
|
|
1194
1647
|
products.nodes.forEach(primeProduct);
|
|
1195
1648
|
return { collection: rest, products: products.nodes, pageInfo: products.pageInfo };
|
|
1196
1649
|
}
|
|
1197
|
-
const data = await gql(
|
|
1198
|
-
|
|
1199
|
-
first,
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
sortKey: sortKey || void 0,
|
|
1203
|
-
reverse
|
|
1204
|
-
});
|
|
1650
|
+
const data = await gql(
|
|
1651
|
+
withProductFragment(SEARCH_QUERY),
|
|
1652
|
+
{ first, after, query: query2 || void 0, sortKey: sortKey || void 0, reverse },
|
|
1653
|
+
isSearchAnswer
|
|
1654
|
+
);
|
|
1205
1655
|
data.products.nodes.forEach(primeProduct);
|
|
1206
1656
|
return { collection: null, products: data.products.nodes, pageInfo: data.products.pageInfo };
|
|
1207
1657
|
}
|
|
@@ -1209,45 +1659,30 @@ ${fragment}`, {
|
|
|
1209
1659
|
const key = normalizeProductKey(typeof product === "object" ? product?.id : product);
|
|
1210
1660
|
const byId = key?.startsWith("gid://") ?? false;
|
|
1211
1661
|
const data = await gql(
|
|
1212
|
-
|
|
1213
|
-
|
|
1662
|
+
withProductFragment(
|
|
1663
|
+
`query TetherRecs(${byId ? "$id: ID!" : "$handle: String!"},$intent:ProductRecommendationIntent){productRecommendations(${byId ? "productId: $id" : "productHandle: $handle"},intent:$intent){...TetherProduct}}`
|
|
1664
|
+
),
|
|
1665
|
+
byId ? { id: key, intent } : { handle: key, intent },
|
|
1666
|
+
isRecommendationsAnswer
|
|
1214
1667
|
);
|
|
1215
1668
|
const list = data.productRecommendations ?? [];
|
|
1216
1669
|
list.forEach(primeProduct);
|
|
1217
1670
|
return list;
|
|
1218
1671
|
}
|
|
1219
1672
|
|
|
1220
|
-
// src/
|
|
1221
|
-
var DECIMAL = /^(-?)(\d+)(?:\.(\d+))?$/;
|
|
1222
|
-
function toScaledInteger(amount, fractionDigits) {
|
|
1223
|
-
const match = DECIMAL.exec(amount.trim());
|
|
1224
|
-
if (!match) return null;
|
|
1225
|
-
const [, sign = "", whole = "0", fraction = ""] = match;
|
|
1226
|
-
const digits = BigInt(`${whole}${fraction.padEnd(fractionDigits, "0")}`);
|
|
1227
|
-
return sign === "-" ? -digits : digits;
|
|
1228
|
-
}
|
|
1229
|
-
var fractionLength = (amount) => amount.split(".")[1]?.length ?? 0;
|
|
1230
|
-
function isGreaterAmount(amount, other) {
|
|
1231
|
-
if (amount == null || other == null) return false;
|
|
1232
|
-
const fractionDigits = Math.max(fractionLength(amount.trim()), fractionLength(other.trim()));
|
|
1233
|
-
const scaled = toScaledInteger(amount, fractionDigits);
|
|
1234
|
-
const scaledOther = toScaledInteger(other, fractionDigits);
|
|
1235
|
-
if (scaled === null || scaledOther === null) return false;
|
|
1236
|
-
return scaled > scaledOther;
|
|
1237
|
-
}
|
|
1238
|
-
|
|
1239
|
-
// src/cart/render.js
|
|
1673
|
+
// src/cart/render.ts
|
|
1240
1674
|
var DEFAULT_TITLE = "Default Title";
|
|
1241
1675
|
var quantityOf = (line, pending2) => pending2.get(line.id)?.quantity ?? line.quantity;
|
|
1242
1676
|
var numericId = (gid) => gid.split("/").pop() ?? gid;
|
|
1243
1677
|
function lineUrl(line) {
|
|
1244
1678
|
const variant = line.merchandise;
|
|
1245
1679
|
const base = productUrl2(variant.product.handle, variant.product.id);
|
|
1246
|
-
|
|
1247
|
-
|
|
1680
|
+
if (!base) return null;
|
|
1681
|
+
const query2 = new URLSearchParams();
|
|
1682
|
+
query2.set("variant", numericId(variant.id));
|
|
1248
1683
|
const planId = line.sellingPlanAllocation?.sellingPlan.id;
|
|
1249
|
-
if (planId)
|
|
1250
|
-
return `${base}${base.includes("?") ? "&" : "?"}${
|
|
1684
|
+
if (planId) query2.set("selling_plan", numericId(planId));
|
|
1685
|
+
return `${base}${base.includes("?") ? "&" : "?"}${query2.toString()}`;
|
|
1251
1686
|
}
|
|
1252
1687
|
var productTitle = (line) => line.merchandise.product.title;
|
|
1253
1688
|
var LINE_TEXT = {
|
|
@@ -1305,19 +1740,17 @@ ${fragment}`, {
|
|
|
1305
1740
|
qsa(node, sel("line", "warning")).forEach((el) => {
|
|
1306
1741
|
setTextTracked(el, warning?.message ?? "");
|
|
1307
1742
|
});
|
|
1308
|
-
const conditions =
|
|
1309
|
-
subscription
|
|
1310
|
-
"one-time"
|
|
1311
|
-
"on-sale"
|
|
1312
|
-
"has-components"
|
|
1313
|
-
warning
|
|
1314
|
-
|
|
1743
|
+
const conditions = /* @__PURE__ */ new Map([
|
|
1744
|
+
["subscription", isSubscription],
|
|
1745
|
+
["one-time", !isSubscription],
|
|
1746
|
+
["on-sale", isGreaterAmount(line.cost.compareAtAmountPerQuantity?.amount, line.cost.amountPerQuantity.amount)],
|
|
1747
|
+
["has-components", (line.lineComponents ?? []).length > 0],
|
|
1748
|
+
["warning", Boolean(warning)]
|
|
1749
|
+
]);
|
|
1315
1750
|
qsa(node, sel("line-show")).forEach((el) => {
|
|
1316
1751
|
const key = get(el, "line-show");
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
key
|
|
1320
|
-
]);
|
|
1752
|
+
const visible = key === null ? void 0 : conditions.get(key);
|
|
1753
|
+
if (visible !== void 0) show(el, visible);
|
|
1321
1754
|
});
|
|
1322
1755
|
}
|
|
1323
1756
|
function renderLine(node, line, view) {
|
|
@@ -1330,7 +1763,8 @@ ${fragment}`, {
|
|
|
1330
1763
|
return;
|
|
1331
1764
|
}
|
|
1332
1765
|
if (key === "url") {
|
|
1333
|
-
|
|
1766
|
+
const url2 = lineUrl(line);
|
|
1767
|
+
if (url2) el.setAttribute("href", url2);
|
|
1334
1768
|
return;
|
|
1335
1769
|
}
|
|
1336
1770
|
const value = lineValue(line, key, view);
|
|
@@ -1339,12 +1773,7 @@ ${fragment}`, {
|
|
|
1339
1773
|
renderLineState(node, line, view);
|
|
1340
1774
|
qsa(node, `select${sel("line-plan")}`).forEach((select) => {
|
|
1341
1775
|
getProduct(variant.product.handle).then((product) => {
|
|
1342
|
-
fillPlanSelect(
|
|
1343
|
-
/** @type {HTMLSelectElement} */
|
|
1344
|
-
select,
|
|
1345
|
-
line,
|
|
1346
|
-
product
|
|
1347
|
-
);
|
|
1776
|
+
fillPlanSelect(select, line, product);
|
|
1348
1777
|
}).catch((error) => {
|
|
1349
1778
|
reportError(error, "line plan options");
|
|
1350
1779
|
});
|
|
@@ -1353,22 +1782,20 @@ ${fragment}`, {
|
|
|
1353
1782
|
function renderConditions(cart, view) {
|
|
1354
1783
|
const count = cart?.totalQuantity ?? 0;
|
|
1355
1784
|
const hasSubscription = (cart?.lines.nodes ?? []).some((line) => line.sellingPlanAllocation);
|
|
1356
|
-
const conditions =
|
|
1357
|
-
"cart-empty"
|
|
1358
|
-
"cart-filled"
|
|
1359
|
-
"cart-has-subscription"
|
|
1360
|
-
"cart-no-subscription"
|
|
1361
|
-
"cart-has-discount"
|
|
1362
|
-
"cart-error"
|
|
1363
|
-
"cart-warning"
|
|
1364
|
-
|
|
1785
|
+
const conditions = /* @__PURE__ */ new Map([
|
|
1786
|
+
["cart-empty", count === 0],
|
|
1787
|
+
["cart-filled", count > 0],
|
|
1788
|
+
["cart-has-subscription", hasSubscription],
|
|
1789
|
+
["cart-no-subscription", !hasSubscription],
|
|
1790
|
+
["cart-has-discount", (cart?.discountCodes ?? []).some((code) => code.applicable)],
|
|
1791
|
+
["cart-error", view.failed],
|
|
1792
|
+
["cart-warning", view.warnings.length > 0]
|
|
1793
|
+
]);
|
|
1365
1794
|
qsa(document, sel("show")).forEach((el) => {
|
|
1366
1795
|
const key = get(el, "show");
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
key
|
|
1371
|
-
]);
|
|
1796
|
+
const visible = key === null ? void 0 : conditions.get(key);
|
|
1797
|
+
if (visible === void 0 || el.closest(sel("product"))) return;
|
|
1798
|
+
show(el, visible);
|
|
1372
1799
|
});
|
|
1373
1800
|
qsa(document, sel("checkout")).forEach((el) => {
|
|
1374
1801
|
el.classList.toggle("is-disabled", !count);
|
|
@@ -1424,15 +1851,35 @@ ${fragment}`, {
|
|
|
1424
1851
|
}
|
|
1425
1852
|
function showSavedCount(count) {
|
|
1426
1853
|
qsa(document, sel("cart-count")).forEach((el) => {
|
|
1427
|
-
if (/^\s*0?\s*$/.test(el.textContent)) el.textContent = count;
|
|
1854
|
+
if (/^\s*0?\s*$/.test(el.textContent)) el.textContent = String(count);
|
|
1428
1855
|
});
|
|
1429
1856
|
}
|
|
1430
1857
|
|
|
1431
|
-
// src/
|
|
1858
|
+
// src/storage.ts
|
|
1859
|
+
function getStored(key, area = "localStorage") {
|
|
1860
|
+
try {
|
|
1861
|
+
return window[area].getItem(key);
|
|
1862
|
+
} catch {
|
|
1863
|
+
return null;
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
function setStored(key, value, area = "localStorage") {
|
|
1867
|
+
try {
|
|
1868
|
+
window[area].setItem(key, value);
|
|
1869
|
+
} catch {
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
function removeStored(key, area = "localStorage") {
|
|
1873
|
+
try {
|
|
1874
|
+
window[area].removeItem(key);
|
|
1875
|
+
} catch {
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
// src/cart/store.ts
|
|
1432
1880
|
var status = { kind: "unknown" };
|
|
1433
1881
|
var warnings = [];
|
|
1434
1882
|
var pendingQuantities = /* @__PURE__ */ new Map();
|
|
1435
|
-
var customer = { accessToken: null, beforeCheckout: null };
|
|
1436
1883
|
var cartStatus = () => status;
|
|
1437
1884
|
function currentCart() {
|
|
1438
1885
|
switch (status.kind) {
|
|
@@ -1447,27 +1894,13 @@ ${fragment}`, {
|
|
|
1447
1894
|
var currentWarnings = () => warnings;
|
|
1448
1895
|
var statusOf = (cart) => cart ? { kind: "ready", cart } : { kind: "empty" };
|
|
1449
1896
|
var seenKey = () => `${cfg.storageKey}:seen`;
|
|
1450
|
-
|
|
1451
|
-
try {
|
|
1452
|
-
return localStorage.getItem(key);
|
|
1453
|
-
} catch {
|
|
1454
|
-
return null;
|
|
1455
|
-
}
|
|
1456
|
-
}
|
|
1457
|
-
function writeStorageIfAllowed(key, value) {
|
|
1458
|
-
try {
|
|
1459
|
-
if (value === null) localStorage.removeItem(key);
|
|
1460
|
-
else localStorage.setItem(key, value);
|
|
1461
|
-
} catch {
|
|
1462
|
-
}
|
|
1463
|
-
}
|
|
1464
|
-
var savedCartId = () => readStorageIfAllowed(cfg.storageKey) || null;
|
|
1897
|
+
var savedCartId = () => getStored(cfg.storageKey) || null;
|
|
1465
1898
|
function forgetSavedCart() {
|
|
1466
|
-
|
|
1899
|
+
removeStored(cfg.storageKey);
|
|
1467
1900
|
}
|
|
1468
1901
|
function keepQuietly(cart) {
|
|
1469
1902
|
status = statusOf(cart);
|
|
1470
|
-
if (cart)
|
|
1903
|
+
if (cart) setStored(cfg.storageKey, cart.id);
|
|
1471
1904
|
}
|
|
1472
1905
|
function render() {
|
|
1473
1906
|
renderCart({
|
|
@@ -1483,7 +1916,7 @@ ${fragment}`, {
|
|
|
1483
1916
|
}
|
|
1484
1917
|
function commit(cart, reason) {
|
|
1485
1918
|
keepQuietly(cart);
|
|
1486
|
-
|
|
1919
|
+
setStored(seenKey(), `${Date.now()}|${cart?.totalQuantity ?? 0}`);
|
|
1487
1920
|
announce(reason);
|
|
1488
1921
|
return cart;
|
|
1489
1922
|
}
|
|
@@ -1491,14 +1924,15 @@ ${fragment}`, {
|
|
|
1491
1924
|
status = outcome.kind === "loaded" ? statusOf(outcome.cart) : { kind: "error", error: outcome.error, cart: null };
|
|
1492
1925
|
announce("load");
|
|
1493
1926
|
}
|
|
1494
|
-
function
|
|
1495
|
-
const
|
|
1496
|
-
|
|
1927
|
+
function lastKnownCount() {
|
|
1928
|
+
const text2 = getStored(seenKey())?.split("|")[1] ?? "";
|
|
1929
|
+
const count = Number(text2);
|
|
1930
|
+
return /^\d+$/.test(text2) && Number.isSafeInteger(count) ? count : null;
|
|
1497
1931
|
}
|
|
1498
1932
|
function showLastKnownCount() {
|
|
1499
1933
|
if (status.kind !== "unknown") return;
|
|
1500
|
-
const count =
|
|
1501
|
-
if (count) showSavedCount(count);
|
|
1934
|
+
const count = lastKnownCount();
|
|
1935
|
+
if (count !== null) showSavedCount(count);
|
|
1502
1936
|
}
|
|
1503
1937
|
function setWarnings(notes) {
|
|
1504
1938
|
warnings = notes.filter((note, index) => notes.findIndex((other) => other.message === note.message) === index).map((note) => ({ ...note, message: cfg.warningText?.(note) || note.message }));
|
|
@@ -1536,7 +1970,7 @@ ${fragment}`, {
|
|
|
1536
1970
|
open: isOpen(),
|
|
1537
1971
|
count: currentCart()?.totalQuantity ?? 0,
|
|
1538
1972
|
loaded: status.kind !== "unknown",
|
|
1539
|
-
warnings
|
|
1973
|
+
warnings: [...warnings]
|
|
1540
1974
|
});
|
|
1541
1975
|
function subscribe(listener) {
|
|
1542
1976
|
const events = ["cart-updated", "cart-toggled"].map((name) => `${cfg.prefix}:${name}`);
|
|
@@ -1554,15 +1988,16 @@ ${fragment}`, {
|
|
|
1554
1988
|
};
|
|
1555
1989
|
}
|
|
1556
1990
|
|
|
1557
|
-
// src/cart/mutations.
|
|
1991
|
+
// src/cart/mutations.ts
|
|
1558
1992
|
var UNKNOWN_CART_MESSAGE = /does not exist|not found|invalid id/i;
|
|
1993
|
+
var isPayloadOf = (name) => shaped({ [name]: orNull({ cart: orNull(CART) }) });
|
|
1559
1994
|
function isCartGone(error) {
|
|
1560
1995
|
if (!(error instanceof TetherError)) return false;
|
|
1561
1996
|
if (error.userErrors?.some((userError) => userError.field?.[0] === "cartId")) return true;
|
|
1562
1997
|
return (error.errors ?? []).some((graphQLError) => UNKNOWN_CART_MESSAGE.test(graphQLError.message));
|
|
1563
1998
|
}
|
|
1564
1999
|
function toLineInput(input, quantity = 1, options = {}) {
|
|
1565
|
-
const spec = typeof input === "object" && input !== null ? input : { ...options, variantId: input
|
|
2000
|
+
const spec = typeof input === "object" && input !== null ? input : { ...options, quantity, ...input === null || input === void 0 ? {} : { variantId: input } };
|
|
1566
2001
|
const merchandiseId = GID.variant(spec.merchandiseId || spec.variantId || spec.id);
|
|
1567
2002
|
if (!merchandiseId) throw new TetherError("add(): variantId is required");
|
|
1568
2003
|
const asked = parseInt(String(spec.quantity ?? 1), 10);
|
|
@@ -1574,23 +2009,25 @@ ${fragment}`, {
|
|
|
1574
2009
|
return line;
|
|
1575
2010
|
}
|
|
1576
2011
|
function mutationText(name, args, inner) {
|
|
1577
|
-
return `mutation Tether_${name}(${args}){${name}(${inner}){cart{...TetherCart}userErrors{field message}warnings{code message target}}}${cartFragment()}`;
|
|
2012
|
+
return (access) => `mutation Tether_${name}(${args}){${name}(${inner}){cart{...TetherCart}userErrors{field message}warnings{code message target}}}${cartFragment(access)}`;
|
|
1578
2013
|
}
|
|
1579
2014
|
async function send(name, args, inner, variables) {
|
|
1580
|
-
const data = await gql(mutationText(name, args, inner), variables);
|
|
2015
|
+
const data = await gql(mutationText(name, args, inner), variables, isPayloadOf(name));
|
|
1581
2016
|
const payload = unwrap(data[name], name);
|
|
1582
2017
|
setWarnings(payload.warnings ?? []);
|
|
1583
2018
|
return payload.cart;
|
|
1584
2019
|
}
|
|
1585
2020
|
function createCart(input) {
|
|
1586
|
-
const
|
|
1587
|
-
...cfg.country ? { countryCode: cfg.country.toUpperCase() } : {},
|
|
1588
|
-
...customer.accessToken ? { customerAccessToken: customer.accessToken } : {}
|
|
1589
|
-
};
|
|
1590
|
-
const withIdentity = Object.keys(buyerIdentity).length ? { buyerIdentity, ...input } : input;
|
|
2021
|
+
const withIdentity = cfg.country ? { buyerIdentity: { countryCode: cfg.country }, ...input } : input;
|
|
1591
2022
|
return send("cartCreate", "$input: CartInput!", "input: $input", { input: withIdentity });
|
|
1592
2023
|
}
|
|
1593
|
-
async function mutateCart({
|
|
2024
|
+
async function mutateCart({
|
|
2025
|
+
name,
|
|
2026
|
+
args,
|
|
2027
|
+
inner,
|
|
2028
|
+
variables,
|
|
2029
|
+
createInput
|
|
2030
|
+
}) {
|
|
1594
2031
|
const cartId = savedCartId();
|
|
1595
2032
|
if (!cartId) return createInput ? createCart(createInput) : null;
|
|
1596
2033
|
try {
|
|
@@ -1604,11 +2041,19 @@ ${fragment}`, {
|
|
|
1604
2041
|
}
|
|
1605
2042
|
}
|
|
1606
2043
|
async function fetchCart(id) {
|
|
1607
|
-
const data = await gql(
|
|
2044
|
+
const data = await gql(
|
|
2045
|
+
(access) => `query TetherCart($id:ID!){cart(id:$id){...TetherCart}}${cartFragment(access)}`,
|
|
2046
|
+
{ id },
|
|
2047
|
+
shaped({ cart: orNull(CART) })
|
|
2048
|
+
);
|
|
1608
2049
|
return data.cart;
|
|
1609
2050
|
}
|
|
1610
2051
|
async function fetchCheckoutUrl(id) {
|
|
1611
|
-
const data = await gql(
|
|
2052
|
+
const data = await gql(
|
|
2053
|
+
"query TetherCheckout($id: ID!) { cart(id: $id) { checkoutUrl } }",
|
|
2054
|
+
{ id },
|
|
2055
|
+
shaped({ cart: orNull({ checkoutUrl: "string" }) })
|
|
2056
|
+
);
|
|
1612
2057
|
return data.cart?.checkoutUrl ?? null;
|
|
1613
2058
|
}
|
|
1614
2059
|
function toLineUpdate(lineId, patch) {
|
|
@@ -1638,21 +2083,20 @@ ${fragment}`, {
|
|
|
1638
2083
|
});
|
|
1639
2084
|
var setLineQuantity = (lineId, target) => target > 0 ? updateLines([{ id: lineId, quantity: target }]) : removeLines([lineId]);
|
|
1640
2085
|
|
|
1641
|
-
// src/cart/checkout.
|
|
2086
|
+
// src/cart/checkout.ts
|
|
2087
|
+
var resolveAccountCheckout = null;
|
|
2088
|
+
function setCheckoutResolver(resolver) {
|
|
2089
|
+
resolveAccountCheckout = resolver;
|
|
2090
|
+
}
|
|
1642
2091
|
var askCheckoutUrl = (cartId) => fetchCheckoutUrl(cartId).then(
|
|
1643
|
-
(
|
|
1644
|
-
|
|
1645
|
-
"url"
|
|
1646
|
-
), url }),
|
|
1647
|
-
(error) => ({ kind: (
|
|
1648
|
-
/** @type {const} */
|
|
1649
|
-
"failed"
|
|
1650
|
-
), error })
|
|
2092
|
+
(url2) => ({ kind: "url", url: url2 }),
|
|
2093
|
+
(error) => ({ kind: "failed", error })
|
|
1651
2094
|
);
|
|
1652
2095
|
async function accountCheckoutUrl() {
|
|
1653
|
-
if (!
|
|
2096
|
+
if (!resolveAccountCheckout) return null;
|
|
1654
2097
|
try {
|
|
1655
|
-
|
|
2098
|
+
const url2 = await resolveAccountCheckout();
|
|
2099
|
+
return url2 && `${url2}${url2.includes("?") ? "&" : "?"}logged_in=true`;
|
|
1656
2100
|
} catch (error) {
|
|
1657
2101
|
reportError(error, "account checkout");
|
|
1658
2102
|
return null;
|
|
@@ -1660,15 +2104,12 @@ ${fragment}`, {
|
|
|
1660
2104
|
}
|
|
1661
2105
|
async function checkoutUrl(cartId) {
|
|
1662
2106
|
const cartAnswer = askCheckoutUrl(cartId);
|
|
1663
|
-
|
|
1664
|
-
if (
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
if (!url) throw new TetherError("No checkoutUrl (cart empty or expired)");
|
|
1670
|
-
if (!customer.accessToken) return url;
|
|
1671
|
-
return `${url}${url.includes("?") ? "&" : "?"}logged_in=true`;
|
|
2107
|
+
const accountUrl = await accountCheckoutUrl();
|
|
2108
|
+
if (accountUrl) return accountUrl;
|
|
2109
|
+
const answer = await cartAnswer;
|
|
2110
|
+
if (answer.kind === "failed") throw answer.error;
|
|
2111
|
+
if (!answer.url) throw new TetherError("No checkoutUrl (cart empty or expired)");
|
|
2112
|
+
return answer.url;
|
|
1672
2113
|
}
|
|
1673
2114
|
function detachOpenerIfAllowed(tab) {
|
|
1674
2115
|
try {
|
|
@@ -1683,12 +2124,12 @@ ${fragment}`, {
|
|
|
1683
2124
|
const tab = target === "_blank" ? window.open("", "_blank") : null;
|
|
1684
2125
|
if (tab) detachOpenerIfAllowed(tab);
|
|
1685
2126
|
try {
|
|
1686
|
-
const
|
|
1687
|
-
if (!isHttpsUrl(
|
|
1688
|
-
emit("checkout", { url, cart: currentCart() });
|
|
1689
|
-
if (tab && !tab.closed) tab.location.href =
|
|
1690
|
-
else window.location.href =
|
|
1691
|
-
return
|
|
2127
|
+
const url2 = await checkoutUrl(cartId);
|
|
2128
|
+
if (!isHttpsUrl(url2)) throw new TetherError("The checkout URL is not https: not opened");
|
|
2129
|
+
emit("checkout", { url: url2, cart: currentCart() });
|
|
2130
|
+
if (tab && !tab.closed) tab.location.href = url2;
|
|
2131
|
+
else window.location.href = url2;
|
|
2132
|
+
return url2;
|
|
1692
2133
|
} catch (error) {
|
|
1693
2134
|
tab?.close();
|
|
1694
2135
|
reportError(error, "checkout failed");
|
|
@@ -1696,7 +2137,7 @@ ${fragment}`, {
|
|
|
1696
2137
|
}
|
|
1697
2138
|
}
|
|
1698
2139
|
|
|
1699
|
-
// src/cart.
|
|
2140
|
+
// src/cart.ts
|
|
1700
2141
|
var currentLines = () => currentCart()?.lines.nodes ?? [];
|
|
1701
2142
|
function changeQuantity(lineId, delta) {
|
|
1702
2143
|
const line = currentLines().find((candidate) => candidate.id === lineId);
|
|
@@ -1753,83 +2194,67 @@ ${fragment}`, {
|
|
|
1753
2194
|
}
|
|
1754
2195
|
var Cart = {
|
|
1755
2196
|
init,
|
|
1756
|
-
/**
|
|
2197
|
+
/** The saved cart's id. */
|
|
1757
2198
|
id: savedCartId,
|
|
1758
|
-
/** @returns {CartStatus} */
|
|
1759
2199
|
get status() {
|
|
1760
2200
|
return cartStatus();
|
|
1761
2201
|
},
|
|
1762
|
-
/** @returns {ShopifyCart | null} */
|
|
1763
2202
|
get state() {
|
|
1764
2203
|
return currentCart();
|
|
1765
2204
|
},
|
|
1766
|
-
/**
|
|
2205
|
+
/** The cart's lines, a copy: changing it changes nothing in the cart. */
|
|
1767
2206
|
get lines() {
|
|
1768
|
-
return currentLines();
|
|
2207
|
+
return [...currentLines()];
|
|
1769
2208
|
},
|
|
1770
|
-
/** @returns {number} */
|
|
1771
2209
|
get count() {
|
|
1772
2210
|
return currentCart()?.totalQuantity ?? 0;
|
|
1773
2211
|
},
|
|
1774
|
-
/**
|
|
2212
|
+
/** True once the saved cart, or that there is none, is known. */
|
|
1775
2213
|
get loaded() {
|
|
1776
2214
|
return cartStatus().kind !== "unknown";
|
|
1777
2215
|
},
|
|
1778
|
-
/**
|
|
2216
|
+
/**
|
|
2217
|
+
* The failure of the last load or change, until a change succeeds. Requests fail with an `Error`; anything else
|
|
2218
|
+
* thrown is wrapped, so the public type holds.
|
|
2219
|
+
*/
|
|
1779
2220
|
get lastError() {
|
|
1780
2221
|
const current = cartStatus();
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
set lastError(error) {
|
|
1784
|
-
setFailure(error);
|
|
2222
|
+
if (current.kind !== "error") return null;
|
|
2223
|
+
return current.error instanceof Error ? current.error : new Error(String(current.error));
|
|
1785
2224
|
},
|
|
1786
|
-
/**
|
|
2225
|
+
/** Shopify's notes on the last change, a copy. */
|
|
1787
2226
|
get warnings() {
|
|
1788
|
-
return currentWarnings();
|
|
2227
|
+
return [...currentWarnings()];
|
|
1789
2228
|
},
|
|
1790
|
-
/**
|
|
2229
|
+
/** Line id → the quantity quick clicks asked for. */
|
|
1791
2230
|
get pendingQty() {
|
|
1792
2231
|
return pendingQuantities;
|
|
1793
2232
|
},
|
|
1794
|
-
/** @returns {string | null} The logged-in customer's Customer Account API token (set by the account part). */
|
|
1795
|
-
get customerAccessToken() {
|
|
1796
|
-
return customer.accessToken;
|
|
1797
|
-
},
|
|
1798
|
-
set customerAccessToken(token) {
|
|
1799
|
-
customer.accessToken = token || null;
|
|
1800
|
-
},
|
|
1801
|
-
/** @returns {(() => Promise<string | null>) | null} The account's own checkout URL (set by the account part). */
|
|
1802
|
-
get beforeCheckout() {
|
|
1803
|
-
return customer.beforeCheckout;
|
|
1804
|
-
},
|
|
1805
|
-
set beforeCheckout(fetchUrl) {
|
|
1806
|
-
customer.beforeCheckout = typeof fetchUrl === "function" ? fetchUrl : null;
|
|
1807
|
-
},
|
|
1808
2233
|
/**
|
|
1809
|
-
*
|
|
1810
|
-
* customer
|
|
1811
|
-
|
|
1812
|
-
|
|
2234
|
+
* Sets where a logged-in customer checks out, or clears it with `null`: the account part's resolver answers the
|
|
2235
|
+
* customer's checkout URL for the cart (`null` uses the cart's own).
|
|
2236
|
+
*/
|
|
2237
|
+
setCheckoutResolver,
|
|
2238
|
+
/**
|
|
2239
|
+
* Forgets the saved cart on this device, as if there were none: the page shows an empty cart and
|
|
2240
|
+
* `tether:cart-updated` fires with reason `load`. The next add starts a new cart.
|
|
1813
2241
|
*/
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
2242
|
+
forgetSaved() {
|
|
2243
|
+
forgetSavedCart();
|
|
2244
|
+
settleLoad({ kind: "loaded", cart: null });
|
|
1817
2245
|
},
|
|
1818
|
-
/** Keeps a cart and shows it, as a change does; tests seed the cart with it. */
|
|
1819
|
-
_commit: commit,
|
|
1820
2246
|
render,
|
|
1821
2247
|
subscribe,
|
|
1822
2248
|
/**
|
|
1823
2249
|
* `add(variantId, 2)`, `add({ variantId, quantity, sellingPlanId, attributes })`, or `add([line, line])` for
|
|
1824
2250
|
* bundles in one request. Creates the cart when there is none.
|
|
1825
2251
|
*
|
|
1826
|
-
* @
|
|
1827
|
-
* @param {number} [quantity]
|
|
1828
|
-
* @param {Omit<LineSpec, 'variantId' | 'quantity'>} [options]
|
|
1829
|
-
* @returns {Promise<ShopifyCart | null>}
|
|
2252
|
+
* @throws {TetherError} (rejected) When the add fails, or Shopify answers it without a cart.
|
|
1830
2253
|
*/
|
|
1831
2254
|
add(input, quantity = 1, options = {}) {
|
|
1832
|
-
const lines = (Array.isArray(input) ? input : [input]).map(
|
|
2255
|
+
const lines = (Array.isArray(input) ? input : [input]).map(
|
|
2256
|
+
(line) => toLineInput(line, quantity, options)
|
|
2257
|
+
);
|
|
1833
2258
|
return enqueue(async () => {
|
|
1834
2259
|
const cart = await mutateCart({
|
|
1835
2260
|
name: "cartLinesAdd",
|
|
@@ -1838,6 +2263,7 @@ ${fragment}`, {
|
|
|
1838
2263
|
variables: { lines },
|
|
1839
2264
|
createInput: { lines }
|
|
1840
2265
|
});
|
|
2266
|
+
if (!cart) throw new TetherError("Shopify answered the add without a cart");
|
|
1841
2267
|
commit(cart, "add");
|
|
1842
2268
|
emit("added", { cart, lines });
|
|
1843
2269
|
if (cfg.openCartOnAdd) openCart();
|
|
@@ -1847,9 +2273,7 @@ ${fragment}`, {
|
|
|
1847
2273
|
/**
|
|
1848
2274
|
* `updateLine(lineId, 3)` or `updateLine(lineId, { quantity, sellingPlanId, attributes })`.
|
|
1849
2275
|
*
|
|
1850
|
-
* @param
|
|
1851
|
-
* @param {number | LinePatch} patch A quantity of 0 or less removes the line.
|
|
1852
|
-
* @returns {Promise<ShopifyCart | null>}
|
|
2276
|
+
* @param patch A quantity of 0 or less removes the line.
|
|
1853
2277
|
*/
|
|
1854
2278
|
updateLine(lineId, patch) {
|
|
1855
2279
|
const quantity = typeof patch === "number" ? patch : patch.quantity;
|
|
@@ -1858,29 +2282,17 @@ ${fragment}`, {
|
|
|
1858
2282
|
return enqueue(async () => commit(await updateLines([line]), "update"));
|
|
1859
2283
|
},
|
|
1860
2284
|
changeQuantity,
|
|
1861
|
-
/**
|
|
1862
|
-
* @param {ShopifyCartLine} line
|
|
1863
|
-
* @returns {number} The quantity to show: the one asked for while an update is on its way.
|
|
1864
|
-
*/
|
|
2285
|
+
/** The quantity to show: the one asked for while an update is on its way. */
|
|
1865
2286
|
quantityOf: pendingQuantityOf,
|
|
1866
|
-
/**
|
|
1867
|
-
* @param {string | string[]} lineIds
|
|
1868
|
-
* @returns {Promise<ShopifyCart | null>}
|
|
1869
|
-
*/
|
|
1870
2287
|
removeLine(lineIds) {
|
|
1871
2288
|
const ids = Array.isArray(lineIds) ? lineIds : [lineIds];
|
|
1872
2289
|
return enqueue(async () => commit(await removeLines(ids), "remove"));
|
|
1873
2290
|
},
|
|
1874
|
-
/** @returns {Promise<ShopifyCart | null>} */
|
|
1875
2291
|
clear() {
|
|
1876
2292
|
const ids = currentLines().map((line) => line.id);
|
|
1877
2293
|
if (!ids.length) return Promise.resolve(currentCart());
|
|
1878
2294
|
return Cart.removeLine(ids);
|
|
1879
2295
|
},
|
|
1880
|
-
/**
|
|
1881
|
-
* @param {string | null | undefined} note
|
|
1882
|
-
* @returns {Promise<ShopifyCart | null>}
|
|
1883
|
-
*/
|
|
1884
2296
|
setNote(note) {
|
|
1885
2297
|
const variables = { note: note ?? "" };
|
|
1886
2298
|
return enqueue(
|
|
@@ -1899,8 +2311,7 @@ ${fragment}`, {
|
|
|
1899
2311
|
/**
|
|
1900
2312
|
* Cart attributes, merged with the cart's (as it is when the change runs, so quick calls don't drop each other).
|
|
1901
2313
|
*
|
|
1902
|
-
* @param
|
|
1903
|
-
* @returns {Promise<ShopifyCart | null>}
|
|
2314
|
+
* @param attributes The value `''` removes a key.
|
|
1904
2315
|
*/
|
|
1905
2316
|
setAttributes(attributes) {
|
|
1906
2317
|
return enqueue(async () => {
|
|
@@ -1915,12 +2326,7 @@ ${fragment}`, {
|
|
|
1915
2326
|
return commit(cart, "attributes");
|
|
1916
2327
|
});
|
|
1917
2328
|
},
|
|
1918
|
-
/**
|
|
1919
|
-
* Replaces the discount codes; `[]` removes them all.
|
|
1920
|
-
*
|
|
1921
|
-
* @param {string[] | string | ((current: string[]) => string[])} codes
|
|
1922
|
-
* @returns {Promise<ShopifyCart | null>}
|
|
1923
|
-
*/
|
|
2329
|
+
/** Replaces the discount codes; `[]` removes them all. */
|
|
1924
2330
|
setDiscountCodes(codes) {
|
|
1925
2331
|
return enqueue(async () => {
|
|
1926
2332
|
const variables = { discountCodes: discountCodesFrom(codes) };
|
|
@@ -1937,8 +2343,6 @@ ${fragment}`, {
|
|
|
1937
2343
|
/**
|
|
1938
2344
|
* Adds a code. A code Shopify doesn't accept is removed again, so it doesn't warn on every later change.
|
|
1939
2345
|
*
|
|
1940
|
-
* @param {string} code
|
|
1941
|
-
* @returns {Promise<ShopifyCart | null>}
|
|
1942
2346
|
* @throws {TetherError} `INVALID_DISCOUNT` (rejected) when Shopify doesn't accept the code.
|
|
1943
2347
|
*/
|
|
1944
2348
|
async applyDiscount(code) {
|
|
@@ -1953,10 +2357,6 @@ ${fragment}`, {
|
|
|
1953
2357
|
}
|
|
1954
2358
|
throw new TetherError(`Discount code not valid: ${code}`, { code: "INVALID_DISCOUNT" });
|
|
1955
2359
|
},
|
|
1956
|
-
/**
|
|
1957
|
-
* @param {string} code
|
|
1958
|
-
* @returns {Promise<ShopifyCart | null>}
|
|
1959
|
-
*/
|
|
1960
2360
|
removeDiscount(code) {
|
|
1961
2361
|
const unwanted = code.toLowerCase();
|
|
1962
2362
|
return Cart.setDiscountCodes((current) => current.filter((existing) => existing.toLowerCase() !== unwanted));
|
|
@@ -1964,9 +2364,7 @@ ${fragment}`, {
|
|
|
1964
2364
|
/**
|
|
1965
2365
|
* Sets who is buying; a country changes the cart's currency and prices to that market's.
|
|
1966
2366
|
*
|
|
1967
|
-
* @param
|
|
1968
|
-
* @param {{ create?: boolean }} [options] `create: false` forgets an expired cart instead of creating an empty one.
|
|
1969
|
-
* @returns {Promise<ShopifyCart | null>}
|
|
2367
|
+
* @param options `create: false` forgets an expired cart instead of creating an empty one.
|
|
1970
2368
|
*/
|
|
1971
2369
|
setBuyerIdentity(buyerIdentity, { create = true } = {}) {
|
|
1972
2370
|
return enqueue(async () => {
|
|
@@ -1991,22 +2389,8 @@ ${fragment}`, {
|
|
|
1991
2389
|
toggle: toggleCart
|
|
1992
2390
|
};
|
|
1993
2391
|
|
|
1994
|
-
// src/product.
|
|
1995
|
-
var
|
|
1996
|
-
__export(product_exports, {
|
|
1997
|
-
ProductScope: () => ProductScope,
|
|
1998
|
-
rememberOptionNames: () => rememberOptionNames,
|
|
1999
|
-
scopes: () => scopes
|
|
2000
|
-
});
|
|
2001
|
-
|
|
2002
|
-
// src/product-state.js
|
|
2003
|
-
var MINOR_UNITS_PER_UNIT = 100;
|
|
2004
|
-
var PERCENT = 100;
|
|
2005
|
-
var toMinorUnits = (money) => Math.round(parseFloat(money.amount) * MINOR_UNITS_PER_UNIT);
|
|
2006
|
-
var fromMinorUnits = (minorUnits, currencyCode) => ({
|
|
2007
|
-
amount: (minorUnits / MINOR_UNITS_PER_UNIT).toFixed(2),
|
|
2008
|
-
currencyCode
|
|
2009
|
-
});
|
|
2392
|
+
// src/product-state.ts
|
|
2393
|
+
var PERCENT2 = 100;
|
|
2010
2394
|
var timesQuantity = (money, quantity) => fromMinorUnits(toMinorUnits(money) * quantity, money.currencyCode);
|
|
2011
2395
|
var selectionOf = (variant) => Object.fromEntries(variant.selectedOptions.map((option) => [option.name, option.value]));
|
|
2012
2396
|
function findVariant(product, selected) {
|
|
@@ -2021,14 +2405,18 @@ ${fragment}`, {
|
|
|
2021
2405
|
)
|
|
2022
2406
|
);
|
|
2023
2407
|
}
|
|
2024
|
-
|
|
2408
|
+
var purchasePlanId = (purchase) => purchase.kind === "subscription" ? purchase.planId : purchase.offeredPlanId;
|
|
2409
|
+
function linePurchase(purchase, variant) {
|
|
2410
|
+
if (purchase.kind === "subscription" && allocationFor(variant, purchase.planId)) return purchase;
|
|
2411
|
+
return { kind: "one-time" };
|
|
2412
|
+
}
|
|
2413
|
+
function normalisePurchase(product, variant, kind, preferredPlanId) {
|
|
2025
2414
|
const plans = allPlans(product);
|
|
2026
|
-
const known = plans.find((plan) => plan.id ===
|
|
2027
|
-
if (!known) return { kind: "one-time",
|
|
2415
|
+
const known = plans.find((plan) => plan.id === preferredPlanId) ?? plans[0];
|
|
2416
|
+
if (!known) return { kind: "one-time", offeredPlanId: null };
|
|
2028
2417
|
const onVariant = allocationFor(variant, known.id) ? known : plans.find((plan) => allocationFor(variant, plan.id)) ?? known;
|
|
2029
|
-
if (product.requiresSellingPlan ||
|
|
2030
|
-
|
|
2031
|
-
return { kind: "one-time", planId: onVariant.id };
|
|
2418
|
+
if (product.requiresSellingPlan || kind === "subscription") return { kind: "subscription", planId: onVariant.id };
|
|
2419
|
+
return { kind: "one-time", offeredPlanId: onVariant.id };
|
|
2032
2420
|
}
|
|
2033
2421
|
function initialChoice(product, { variant, sellingPlan, defaultPlan, defaultPurchase }) {
|
|
2034
2422
|
const variants = product.variants.nodes;
|
|
@@ -2039,13 +2427,14 @@ ${fragment}`, {
|
|
|
2039
2427
|
const kind = fromUrl || defaultPurchase === "subscription" ? "subscription" : "one-time";
|
|
2040
2428
|
return {
|
|
2041
2429
|
selected: start ? selectionOf(start) : {},
|
|
2042
|
-
purchase: normalisePurchase(product, start,
|
|
2430
|
+
purchase: normalisePurchase(product, start, kind, offered?.id ?? null)
|
|
2043
2431
|
};
|
|
2044
2432
|
}
|
|
2045
2433
|
function showConditions(variant, lowStock, variantCount, sale, requiresSellingPlan) {
|
|
2046
2434
|
const stock = variant.quantityAvailable;
|
|
2047
2435
|
const low = stock != null && stock > 0 && stock <= lowStock;
|
|
2048
2436
|
const onSale = sale.compareAtPrice !== null;
|
|
2437
|
+
const onPlan = sale.line.kind === "subscription";
|
|
2049
2438
|
return {
|
|
2050
2439
|
"in-stock": variant.availableForSale,
|
|
2051
2440
|
"out-of-stock": !variant.availableForSale,
|
|
@@ -2060,23 +2449,24 @@ ${fragment}`, {
|
|
|
2060
2449
|
"subscription-unavailable": !sale.subscriptionAvailable,
|
|
2061
2450
|
"subscription-only": requiresSellingPlan,
|
|
2062
2451
|
"one-time-available": !requiresSellingPlan,
|
|
2063
|
-
"subscription-selected":
|
|
2064
|
-
"one-time-selected": !
|
|
2065
|
-
"has-savings":
|
|
2452
|
+
"subscription-selected": onPlan,
|
|
2453
|
+
"one-time-selected": !onPlan,
|
|
2454
|
+
"has-savings": onPlan ? sale.savings.percentage > 0 : onSale
|
|
2066
2455
|
};
|
|
2067
2456
|
}
|
|
2068
2457
|
function productView(product, choice, lowStock) {
|
|
2069
2458
|
const exact = findVariant(product, choice.selected);
|
|
2070
2459
|
const variant = exact ?? product.variants.nodes[0];
|
|
2071
2460
|
if (!variant) return null;
|
|
2072
|
-
const purchase = normalisePurchase(product, variant, choice.purchase);
|
|
2073
|
-
const
|
|
2461
|
+
const purchase = normalisePurchase(product, variant, choice.purchase.kind, purchasePlanId(choice.purchase));
|
|
2462
|
+
const planId = purchasePlanId(purchase);
|
|
2074
2463
|
const plans = allPlans(product);
|
|
2075
|
-
const
|
|
2464
|
+
const line = linePurchase(purchase, variant);
|
|
2465
|
+
const allocation = line.kind === "subscription" ? allocationFor(variant, line.planId) : null;
|
|
2076
2466
|
const price = allocation ? planPrice(variant, planId) ?? variant.price : variant.price;
|
|
2077
2467
|
const compareAt = allocation?.priceAdjustments[0]?.compareAtPrice ?? variant.compareAtPrice;
|
|
2078
2468
|
const sale = {
|
|
2079
|
-
|
|
2469
|
+
line,
|
|
2080
2470
|
subscriptionAvailable: plans.some((plan) => allocationFor(variant, plan.id)),
|
|
2081
2471
|
compareAtPrice: compareAt && parseFloat(compareAt.amount) > parseFloat(price.amount) ? compareAt : null,
|
|
2082
2472
|
savings: planSavings(variant, planId)
|
|
@@ -2094,15 +2484,15 @@ ${fragment}`, {
|
|
|
2094
2484
|
}
|
|
2095
2485
|
function variantBindTexts(view, format) {
|
|
2096
2486
|
const { variant, plan, price, compareAtPrice, quantity } = view;
|
|
2097
|
-
const planId = view.purchase
|
|
2487
|
+
const planId = purchasePlanId(view.purchase);
|
|
2098
2488
|
const forQuantity = (money) => timesQuantity(money, Math.max(1, quantity));
|
|
2099
2489
|
const checkoutCharge = () => {
|
|
2100
|
-
const charge = view.
|
|
2490
|
+
const charge = view.line.kind === "subscription" ? planCheckoutCharge(variant, planId) ?? price : price;
|
|
2101
2491
|
return format.money(forQuantity(charge));
|
|
2102
2492
|
};
|
|
2103
2493
|
const discountPercentage = () => {
|
|
2104
2494
|
if (!compareAtPrice) return "0%";
|
|
2105
|
-
return `${Math.round((1 - parseFloat(price.amount) / parseFloat(compareAtPrice.amount)) *
|
|
2495
|
+
return `${Math.round((1 - parseFloat(price.amount) / parseFloat(compareAtPrice.amount)) * PERCENT2)}%`;
|
|
2106
2496
|
};
|
|
2107
2497
|
const discountAmount = () => {
|
|
2108
2498
|
if (!compareAtPrice) return "";
|
|
@@ -2151,7 +2541,7 @@ ${fragment}`, {
|
|
|
2151
2541
|
return new Map(Object.entries(texts));
|
|
2152
2542
|
}
|
|
2153
2543
|
|
|
2154
|
-
// src/product-render.
|
|
2544
|
+
// src/product-render.ts
|
|
2155
2545
|
var NTH_IMAGE_BIND = /^image:(\d+)$/;
|
|
2156
2546
|
var METAFIELD_BIND = "metafield:";
|
|
2157
2547
|
var MULTI_LINE_METAFIELD = /^(rich_text|multi_line_text)_field$/;
|
|
@@ -2172,7 +2562,7 @@ ${fragment}`, {
|
|
|
2172
2562
|
const dot = ref.indexOf(".");
|
|
2173
2563
|
const namespace = ref.slice(0, dot);
|
|
2174
2564
|
const key = ref.slice(dot + 1);
|
|
2175
|
-
const metafield = (product.metafields ?? []).find((
|
|
2565
|
+
const metafield = (product.metafields ?? []).find((field2) => field2?.namespace === namespace && field2.key === key);
|
|
2176
2566
|
if (!metafield) {
|
|
2177
2567
|
setText(el, "");
|
|
2178
2568
|
return;
|
|
@@ -2205,9 +2595,9 @@ ${fragment}`, {
|
|
|
2205
2595
|
});
|
|
2206
2596
|
}
|
|
2207
2597
|
function renderProductBind(el, key, product) {
|
|
2208
|
-
const
|
|
2209
|
-
if (
|
|
2210
|
-
setText(el,
|
|
2598
|
+
const text2 = PRODUCT_TEXTS.get(key);
|
|
2599
|
+
if (text2) {
|
|
2600
|
+
setText(el, text2(product));
|
|
2211
2601
|
return;
|
|
2212
2602
|
}
|
|
2213
2603
|
switch (key) {
|
|
@@ -2218,10 +2608,12 @@ ${fragment}`, {
|
|
|
2218
2608
|
setImage(el, product.featuredImage?.url, product.featuredImage?.altText || product.title);
|
|
2219
2609
|
break;
|
|
2220
2610
|
case "url":
|
|
2221
|
-
case "link":
|
|
2222
|
-
|
|
2611
|
+
case "link": {
|
|
2612
|
+
const url2 = productUrl2(product.handle, product.id);
|
|
2613
|
+
if (url2) el.setAttribute("href", url2);
|
|
2223
2614
|
if (key === "url" && !el.firstElementChild && !el.textContent.trim()) setText(el, product.title);
|
|
2224
2615
|
break;
|
|
2616
|
+
}
|
|
2225
2617
|
case "images":
|
|
2226
2618
|
renderGallery(el, product);
|
|
2227
2619
|
break;
|
|
@@ -2252,14 +2644,11 @@ ${fragment}`, {
|
|
|
2252
2644
|
}
|
|
2253
2645
|
repeat(container, option.optionValues, (node, value) => {
|
|
2254
2646
|
node.setAttribute(attr("option-value"), value.name);
|
|
2255
|
-
const
|
|
2256
|
-
if (
|
|
2647
|
+
const text2 = qsa(node, sel("bind", "option-value"))[0];
|
|
2648
|
+
if (text2) setText(text2, value.name);
|
|
2257
2649
|
else if (!node.firstElementChild) setText(node, value.name);
|
|
2258
2650
|
const swatchEl = qsa(node, sel("bind", "swatch"))[0];
|
|
2259
|
-
const swatch =
|
|
2260
|
-
/** @type {Swatch} */
|
|
2261
|
-
value.swatch
|
|
2262
|
-
);
|
|
2651
|
+
const { swatch } = value;
|
|
2263
2652
|
if (!swatchEl || !swatch) return;
|
|
2264
2653
|
if (swatch.color) swatchEl.style.backgroundColor = swatch.color;
|
|
2265
2654
|
if (swatch.image?.previewImage) setImage(swatchEl, swatch.image.previewImage.url, value.name);
|
|
@@ -2306,16 +2695,7 @@ ${fragment}`, {
|
|
|
2306
2695
|
renderPlanLists(scope);
|
|
2307
2696
|
}
|
|
2308
2697
|
|
|
2309
|
-
// src/status.
|
|
2310
|
-
var status_exports = {};
|
|
2311
|
-
__export(status_exports, {
|
|
2312
|
-
STATUS_KEYS: () => STATUS_KEYS,
|
|
2313
|
-
boundary: () => boundary,
|
|
2314
|
-
isNotFound: () => isNotFound,
|
|
2315
|
-
ownedBy: () => ownedBy,
|
|
2316
|
-
resolveProductKey: () => resolveProductKey,
|
|
2317
|
-
setScopeStatus: () => setScopeStatus
|
|
2318
|
-
});
|
|
2698
|
+
// src/status.ts
|
|
2319
2699
|
var URL_SOURCE = "url:";
|
|
2320
2700
|
function decodePathSegment(segment) {
|
|
2321
2701
|
try {
|
|
@@ -2325,17 +2705,17 @@ ${fragment}`, {
|
|
|
2325
2705
|
throw error;
|
|
2326
2706
|
}
|
|
2327
2707
|
}
|
|
2328
|
-
function resolveProductKey(el,
|
|
2708
|
+
function resolveProductKey(el, location2 = window.location, name = "product") {
|
|
2329
2709
|
const raw = (get(el, name) ?? "").trim();
|
|
2330
2710
|
if (!raw.toLowerCase().startsWith(URL_SOURCE)) return { key: raw || null, fromUrl: false, raw };
|
|
2331
2711
|
const source = raw.slice(URL_SOURCE.length).trim();
|
|
2332
2712
|
let key;
|
|
2333
2713
|
if (source.toLowerCase() === "path") {
|
|
2334
|
-
const segments =
|
|
2714
|
+
const segments = location2.pathname.split("/").filter(Boolean);
|
|
2335
2715
|
const segment = segments[segments.length - 1];
|
|
2336
2716
|
key = segment === void 0 ? null : decodePathSegment(segment);
|
|
2337
2717
|
} else {
|
|
2338
|
-
key = new URLSearchParams(
|
|
2718
|
+
key = new URLSearchParams(location2.search).get(source || "handle");
|
|
2339
2719
|
}
|
|
2340
2720
|
return { key: key?.trim() || null, fromUrl: true, raw };
|
|
2341
2721
|
}
|
|
@@ -2367,7 +2747,7 @@ ${fragment}`, {
|
|
|
2367
2747
|
});
|
|
2368
2748
|
}
|
|
2369
2749
|
|
|
2370
|
-
// src/variant-render.
|
|
2750
|
+
// src/variant-render.ts
|
|
2371
2751
|
var FORMAT = { money: formatMoney2, percent: formatPercent };
|
|
2372
2752
|
var isInput2 = (el) => el.tagName === "INPUT";
|
|
2373
2753
|
function addButtonLabel(button) {
|
|
@@ -2400,7 +2780,7 @@ ${fragment}`, {
|
|
|
2400
2780
|
});
|
|
2401
2781
|
}
|
|
2402
2782
|
function renderPurchaseStates(scope, view) {
|
|
2403
|
-
const { variant, purchase,
|
|
2783
|
+
const { variant, purchase, line } = view;
|
|
2404
2784
|
scope.$(sel("purchase")).forEach((el) => {
|
|
2405
2785
|
if (isSelect(el)) {
|
|
2406
2786
|
el.value = purchase.kind;
|
|
@@ -2413,17 +2793,17 @@ ${fragment}`, {
|
|
|
2413
2793
|
});
|
|
2414
2794
|
scope.$(sel("plan-value")).forEach((node) => {
|
|
2415
2795
|
const planId = get(node, "plan-value") ?? "";
|
|
2416
|
-
setSelected(node,
|
|
2796
|
+
setSelected(node, line.kind === "subscription" && planId === line.planId);
|
|
2417
2797
|
node.classList.toggle("is-unavailable", !allocationFor(variant, planId));
|
|
2418
2798
|
const plan = scope.plans.find((candidate) => candidate.id === planId);
|
|
2419
2799
|
const texts = planBindTexts(variant, planId, plan, FORMAT);
|
|
2420
2800
|
qsa(node, sel("plan-bind")).forEach((el) => {
|
|
2421
|
-
const
|
|
2422
|
-
if (
|
|
2801
|
+
const text2 = texts.get(get(el, "plan-bind") ?? "");
|
|
2802
|
+
if (text2) setText(el, text2());
|
|
2423
2803
|
});
|
|
2424
2804
|
});
|
|
2425
2805
|
selectsIn(scope, sel("plan")).forEach((select) => {
|
|
2426
|
-
select.value =
|
|
2806
|
+
select.value = line.kind === "subscription" ? line.planId : "";
|
|
2427
2807
|
});
|
|
2428
2808
|
}
|
|
2429
2809
|
function renderConditions2(scope, view) {
|
|
@@ -2447,10 +2827,10 @@ ${fragment}`, {
|
|
|
2447
2827
|
const subscribeText = get(button, "subscribe-text");
|
|
2448
2828
|
if (!soldOutText && !subscribeText) return;
|
|
2449
2829
|
const { label, original } = addButtonLabel(button);
|
|
2450
|
-
let
|
|
2451
|
-
if (view.
|
|
2452
|
-
if (disabled && soldOutText)
|
|
2453
|
-
if (button.getAttribute("aria-busy") !== "true") setText(label,
|
|
2830
|
+
let text2 = original;
|
|
2831
|
+
if (view.line.kind === "subscription" && subscribeText) text2 = subscribeText;
|
|
2832
|
+
if (disabled && soldOutText) text2 = soldOutText;
|
|
2833
|
+
if (button.getAttribute("aria-busy") !== "true") setText(label, text2);
|
|
2454
2834
|
});
|
|
2455
2835
|
}
|
|
2456
2836
|
function writeVariant(scope, view) {
|
|
@@ -2463,32 +2843,29 @@ ${fragment}`, {
|
|
|
2463
2843
|
setImage(el, variant.image?.url || product.featuredImage?.url, variant.image?.altText || product.title);
|
|
2464
2844
|
return;
|
|
2465
2845
|
}
|
|
2466
|
-
const
|
|
2467
|
-
if (
|
|
2846
|
+
const text2 = texts.get(key);
|
|
2847
|
+
if (text2) setText(el, text2());
|
|
2468
2848
|
});
|
|
2469
2849
|
renderOptionStates(scope, view);
|
|
2470
2850
|
renderPurchaseStates(scope, view);
|
|
2471
2851
|
renderConditions2(scope, view);
|
|
2472
|
-
scope.root.classList.toggle("is-subscription", view.
|
|
2473
|
-
scope.root.setAttribute(attr("purchase-mode"), view.
|
|
2852
|
+
scope.root.classList.toggle("is-subscription", view.line.kind === "subscription");
|
|
2853
|
+
scope.root.setAttribute(attr("purchase-mode"), view.line.kind);
|
|
2474
2854
|
renderAddButtons(scope, view);
|
|
2475
2855
|
}
|
|
2476
2856
|
|
|
2477
|
-
// src/product-events.
|
|
2857
|
+
// src/product-events.ts
|
|
2478
2858
|
var productSelector = () => sel("product");
|
|
2479
2859
|
var isField = (el) => el.tagName === "INPUT" || el.tagName === "SELECT" || el.tagName === "TEXTAREA";
|
|
2480
2860
|
var isElement = (target) => target instanceof window.Element;
|
|
2481
2861
|
function closestOwn(scope, target, selector) {
|
|
2482
|
-
const el = (
|
|
2483
|
-
/** @type {HTMLElement | null} */
|
|
2484
|
-
target.closest(selector)
|
|
2485
|
-
);
|
|
2862
|
+
const el = target.closest(selector);
|
|
2486
2863
|
return el?.closest(productSelector()) === scope.root ? el : null;
|
|
2487
2864
|
}
|
|
2488
2865
|
function onAddClick(scope, button) {
|
|
2489
2866
|
const { label, original } = addButtonLabel(button);
|
|
2490
2867
|
const addingText = get(button, "adding-text");
|
|
2491
|
-
|
|
2868
|
+
busy(button, async () => {
|
|
2492
2869
|
if (addingText) setText(label, addingText);
|
|
2493
2870
|
try {
|
|
2494
2871
|
await scope.addToCart();
|
|
@@ -2500,6 +2877,8 @@ ${fragment}`, {
|
|
|
2500
2877
|
}
|
|
2501
2878
|
}).then(() => {
|
|
2502
2879
|
scope.renderVariant();
|
|
2880
|
+
}).catch((error) => {
|
|
2881
|
+
reportError(error, "add to cart render");
|
|
2503
2882
|
});
|
|
2504
2883
|
}
|
|
2505
2884
|
function onClick(scope, event, target) {
|
|
@@ -2519,7 +2898,7 @@ ${fragment}`, {
|
|
|
2519
2898
|
const purchase = closestOwn(scope, target, `${sel("purchase")}:not(select)`);
|
|
2520
2899
|
if (purchase) {
|
|
2521
2900
|
if (purchase.tagName !== "INPUT") event.preventDefault();
|
|
2522
|
-
scope.setPurchase(get(purchase, "purchase"));
|
|
2901
|
+
scope.setPurchase(purchaseKind(get(purchase, "purchase")));
|
|
2523
2902
|
return;
|
|
2524
2903
|
}
|
|
2525
2904
|
const plus = closestOwn(scope, target, sel("qty-plus"));
|
|
@@ -2535,6 +2914,7 @@ ${fragment}`, {
|
|
|
2535
2914
|
onAddClick(scope, add);
|
|
2536
2915
|
}
|
|
2537
2916
|
}
|
|
2917
|
+
var purchaseKind = (value) => value === "subscription" ? "subscription" : "one-time";
|
|
2538
2918
|
function onChange(scope, target) {
|
|
2539
2919
|
const value = isField(target) ? target.value : "";
|
|
2540
2920
|
if (target.matches(`select${sel("option")}`)) {
|
|
@@ -2545,7 +2925,7 @@ ${fragment}`, {
|
|
|
2545
2925
|
if (value) scope.selectPlan(value);
|
|
2546
2926
|
else scope.setPurchase("one-time");
|
|
2547
2927
|
} else if (target.matches(`select${sel("purchase")}`)) {
|
|
2548
|
-
scope.setPurchase(value);
|
|
2928
|
+
scope.setPurchase(purchaseKind(value));
|
|
2549
2929
|
} else if (target.matches(sel("qty"))) {
|
|
2550
2930
|
scope.setQuantity(value);
|
|
2551
2931
|
}
|
|
@@ -2560,7 +2940,7 @@ ${fragment}`, {
|
|
|
2560
2940
|
});
|
|
2561
2941
|
}
|
|
2562
2942
|
|
|
2563
|
-
// src/product.
|
|
2943
|
+
// src/product.ts
|
|
2564
2944
|
var scopes = /* @__PURE__ */ new WeakMap();
|
|
2565
2945
|
var productSelector2 = () => sel("product");
|
|
2566
2946
|
var knownOptionNames = /* @__PURE__ */ new Map();
|
|
@@ -2585,11 +2965,15 @@ ${fragment}`, {
|
|
|
2585
2965
|
return isField2(el) && el.value || null;
|
|
2586
2966
|
}
|
|
2587
2967
|
var ProductScope = class {
|
|
2588
|
-
/**
|
|
2589
|
-
* @param {HTMLElement} root
|
|
2590
|
-
* @param {Product} product
|
|
2591
|
-
*/
|
|
2592
2968
|
constructor(root, product) {
|
|
2969
|
+
__publicField(this, "root");
|
|
2970
|
+
__publicField(this, "product");
|
|
2971
|
+
__publicField(this, "plans");
|
|
2972
|
+
__publicField(this, "selected");
|
|
2973
|
+
/** How the visitor buys; `setPurchase` and `selectPlan` change it. */
|
|
2974
|
+
__publicField(this, "purchase");
|
|
2975
|
+
__publicField(this, "variant");
|
|
2976
|
+
__publicField(this, "quantity");
|
|
2593
2977
|
this.root = root;
|
|
2594
2978
|
this.product = product;
|
|
2595
2979
|
this.plans = allPlans(product);
|
|
@@ -2601,7 +2985,7 @@ ${fragment}`, {
|
|
|
2601
2985
|
defaultPurchase: get(root, "default-purchase") || cfg.defaultPurchase
|
|
2602
2986
|
});
|
|
2603
2987
|
this.selected = choice.selected;
|
|
2604
|
-
this.
|
|
2988
|
+
this.purchase = choice.purchase;
|
|
2605
2989
|
this.variant = null;
|
|
2606
2990
|
this.quantity = has(root, "quantity") ? Math.max(0, parseInt(get(root, "quantity") ?? "", 10) || 0) : 1;
|
|
2607
2991
|
scopes.set(root, this);
|
|
@@ -2614,11 +2998,7 @@ ${fragment}`, {
|
|
|
2614
2998
|
notifyLayoutChanged("product");
|
|
2615
2999
|
emit("product-ready", { product, scope: this }, root);
|
|
2616
3000
|
}
|
|
2617
|
-
/**
|
|
2618
|
-
* New data for the same product (a country switch). Keeps the visitor's choice.
|
|
2619
|
-
*
|
|
2620
|
-
* @param {Product} product
|
|
2621
|
-
*/
|
|
3001
|
+
/** New data for the same product (a country switch). Keeps the visitor's choice. */
|
|
2622
3002
|
update(product) {
|
|
2623
3003
|
const variantId = this.variant?.id;
|
|
2624
3004
|
this.product = product;
|
|
@@ -2632,17 +3012,14 @@ ${fragment}`, {
|
|
|
2632
3012
|
notifyLayoutChanged("product");
|
|
2633
3013
|
emit("product-updated", { product, scope: this }, this.root);
|
|
2634
3014
|
}
|
|
2635
|
-
/**
|
|
2636
|
-
* @param {string} selector
|
|
2637
|
-
* @returns {HTMLElement[]} Matches in this scope, including the root itself (a card link with tether-bind="link").
|
|
2638
|
-
*/
|
|
3015
|
+
/** @returns Matches in this scope, including the root itself (a card link with tether-bind="link"). */
|
|
2639
3016
|
$(selector) {
|
|
2640
3017
|
const inner = scoped(this.root, selector, productSelector2());
|
|
2641
3018
|
return this.root.matches(selector) ? [this.root, ...inner] : inner;
|
|
2642
3019
|
}
|
|
2643
3020
|
/**
|
|
2644
|
-
* @param
|
|
2645
|
-
* @returns
|
|
3021
|
+
* @param authored An option name in the markup.
|
|
3022
|
+
* @returns The product's option of that name, or at that position in the build's language.
|
|
2646
3023
|
*/
|
|
2647
3024
|
optName(authored) {
|
|
2648
3025
|
if (!authored) return "";
|
|
@@ -2651,50 +3028,31 @@ ${fragment}`, {
|
|
|
2651
3028
|
const index = (knownOptionNames.get(this.product.handle) ?? []).indexOf(authored);
|
|
2652
3029
|
return options[index]?.name ?? authored;
|
|
2653
3030
|
}
|
|
2654
|
-
/** @returns {'one-time' | 'subscription'} */
|
|
2655
|
-
get purchase() {
|
|
2656
|
-
return this.purchaseChoice.kind;
|
|
2657
|
-
}
|
|
2658
|
-
/** @param {string} kind */
|
|
2659
|
-
set purchase(kind) {
|
|
2660
|
-
const wanted = kind === "subscription" ? "subscription" : "one-time";
|
|
2661
|
-
this.purchaseChoice = normalisePurchase(this.product, this.variant, { kind: wanted, planId: this.planId });
|
|
2662
|
-
}
|
|
2663
|
-
/** @returns {string | null} The plan bought on, or offered while buying once. */
|
|
2664
|
-
get planId() {
|
|
2665
|
-
return this.purchaseChoice.planId;
|
|
2666
|
-
}
|
|
2667
|
-
/** @param {string | null} planId */
|
|
2668
|
-
set planId(planId) {
|
|
2669
|
-
this.purchaseChoice = normalisePurchase(this.product, this.variant, { kind: this.purchase, planId });
|
|
2670
|
-
}
|
|
2671
|
-
/** @returns {PlanInGroup | null} */
|
|
2672
3031
|
get plan() {
|
|
2673
|
-
|
|
3032
|
+
const planId = purchasePlanId(this.purchase);
|
|
3033
|
+
return this.plans.find((plan) => plan.id === planId) ?? null;
|
|
2674
3034
|
}
|
|
2675
3035
|
/** True when the current variant can be bought on a plan. */
|
|
2676
3036
|
get subscriptionAvailable() {
|
|
2677
3037
|
return this.plans.some((plan) => allocationFor(this.variant, plan.id));
|
|
2678
3038
|
}
|
|
3039
|
+
/** True when the line is bought on a plan: subscribing, and the variant is on the plan. */
|
|
2679
3040
|
get isSubscription() {
|
|
2680
|
-
return this.purchase
|
|
3041
|
+
return linePurchase(this.purchase, this.variant).kind === "subscription";
|
|
2681
3042
|
}
|
|
2682
|
-
/** @returns
|
|
3043
|
+
/** @returns What would be added to the cart right now. */
|
|
2683
3044
|
getLine() {
|
|
2684
3045
|
if (!this.variant) return null;
|
|
2685
3046
|
const line = { merchandiseId: this.variant.id, quantity: this.quantity };
|
|
2686
|
-
|
|
3047
|
+
const bought = linePurchase(this.purchase, this.variant);
|
|
3048
|
+
if (bought.kind === "subscription") line.sellingPlanId = bought.planId;
|
|
2687
3049
|
const attributes = this.collectAttributes();
|
|
2688
3050
|
if (attributes.length) line.attributes = attributes;
|
|
2689
3051
|
return line;
|
|
2690
3052
|
}
|
|
2691
|
-
/** @returns
|
|
3053
|
+
/** @returns `tether-line-attributes` and the filled `tether-line-attribute` fields. */
|
|
2692
3054
|
collectAttributes() {
|
|
2693
|
-
const
|
|
2694
|
-
/** @type {Parameters<typeof toAttributes>[0]} */
|
|
2695
|
-
parseJSONAttr(this.root, "line-attributes")
|
|
2696
|
-
);
|
|
2697
|
-
const fixed = toAttributes(json);
|
|
3055
|
+
const fixed = attributesFromJson(parseJSONAttr(this.root, "line-attributes"));
|
|
2698
3056
|
const fields = this.$(sel("line-attribute")).flatMap((el) => {
|
|
2699
3057
|
const value = lineAttributeValue(el);
|
|
2700
3058
|
return value === null ? [] : [{ key: get(el, "line-attribute") ?? "", value }];
|
|
@@ -2704,9 +3062,6 @@ ${fragment}`, {
|
|
|
2704
3062
|
/**
|
|
2705
3063
|
* Selects an option value. A combination no variant has moves to the closest variant with the value: the most other
|
|
2706
3064
|
* choices kept, then one for sale.
|
|
2707
|
-
*
|
|
2708
|
-
* @param {string} name
|
|
2709
|
-
* @param {string} value
|
|
2710
3065
|
*/
|
|
2711
3066
|
select(name, value) {
|
|
2712
3067
|
if (this.selected[name] === value) return;
|
|
@@ -2732,29 +3087,26 @@ ${fragment}`, {
|
|
|
2732
3087
|
this.selected = next;
|
|
2733
3088
|
this.renderVariant();
|
|
2734
3089
|
}
|
|
2735
|
-
/** @param
|
|
3090
|
+
/** @param id A variant gid or numeric id. */
|
|
2736
3091
|
selectVariant(id) {
|
|
2737
3092
|
const variant = this.product.variants.nodes.find((candidate) => candidate.id === GID.variant(id));
|
|
2738
3093
|
if (!variant) return;
|
|
2739
3094
|
this.selected = selectionOf(variant);
|
|
2740
3095
|
this.renderVariant();
|
|
2741
3096
|
}
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
if (next.kind === this.purchase) return;
|
|
2747
|
-
this.purchaseChoice = next;
|
|
3097
|
+
setPurchase(kind) {
|
|
3098
|
+
const next = normalisePurchase(this.product, this.variant, kind, purchasePlanId(this.purchase));
|
|
3099
|
+
if (next.kind === this.purchase.kind) return;
|
|
3100
|
+
this.purchase = next;
|
|
2748
3101
|
this.renderVariant();
|
|
2749
3102
|
}
|
|
2750
|
-
/** @param
|
|
3103
|
+
/** @param ref A plan id, numeric id, name or option value. */
|
|
2751
3104
|
selectPlan(ref) {
|
|
2752
3105
|
const plan = findPlan(this.product, ref);
|
|
2753
3106
|
if (!plan) return;
|
|
2754
|
-
this.
|
|
3107
|
+
this.purchase = { kind: "subscription", planId: plan.id };
|
|
2755
3108
|
this.renderVariant();
|
|
2756
3109
|
}
|
|
2757
|
-
/** @param {number | string} quantity */
|
|
2758
3110
|
setQuantity(quantity) {
|
|
2759
3111
|
const minimum = has(this.root, "quantity") ? 0 : 1;
|
|
2760
3112
|
this.quantity = Math.max(minimum, parseInt(String(quantity), 10) || 0);
|
|
@@ -2763,7 +3115,7 @@ ${fragment}`, {
|
|
|
2763
3115
|
});
|
|
2764
3116
|
this.renderVariant();
|
|
2765
3117
|
}
|
|
2766
|
-
/** @returns
|
|
3118
|
+
/** @returns The cart after adding; `null` when the variant can't be added. */
|
|
2767
3119
|
async addToCart() {
|
|
2768
3120
|
if (!this.variant?.availableForSale) return null;
|
|
2769
3121
|
const line = this.getLine();
|
|
@@ -2789,7 +3141,7 @@ ${fragment}`, {
|
|
|
2789
3141
|
renderVariant() {
|
|
2790
3142
|
const view = productView(
|
|
2791
3143
|
this.product,
|
|
2792
|
-
{ selected: this.selected, purchase: this.
|
|
3144
|
+
{ selected: this.selected, purchase: this.purchase, quantity: this.quantity },
|
|
2793
3145
|
cfg.lowStock
|
|
2794
3146
|
);
|
|
2795
3147
|
if (!view) {
|
|
@@ -2798,7 +3150,7 @@ ${fragment}`, {
|
|
|
2798
3150
|
}
|
|
2799
3151
|
this.variant = view.variant;
|
|
2800
3152
|
this.selected = view.selected;
|
|
2801
|
-
this.
|
|
3153
|
+
this.purchase = view.purchase;
|
|
2802
3154
|
writeVariant(this, view);
|
|
2803
3155
|
emit(
|
|
2804
3156
|
"variant-changed",
|
|
@@ -2807,32 +3159,28 @@ ${fragment}`, {
|
|
|
2807
3159
|
variant: view.variant,
|
|
2808
3160
|
selected: { ...view.selected },
|
|
2809
3161
|
purchase: view.purchase.kind,
|
|
2810
|
-
sellingPlanId: view.
|
|
3162
|
+
sellingPlanId: view.line.kind === "subscription" ? view.line.planId : null
|
|
2811
3163
|
},
|
|
2812
3164
|
this.root
|
|
2813
3165
|
);
|
|
2814
3166
|
}
|
|
2815
3167
|
};
|
|
2816
3168
|
|
|
2817
|
-
// src/collection.
|
|
2818
|
-
var collection_exports = {};
|
|
2819
|
-
__export(collection_exports, {
|
|
2820
|
-
ListScope: () => ListScope,
|
|
2821
|
-
listSelector: () => listSelector
|
|
2822
|
-
});
|
|
3169
|
+
// src/collection.ts
|
|
2823
3170
|
var URL_QUERY_SOURCE = "url:";
|
|
2824
3171
|
var listSelector = () => `${sel("collection")},${sel("products")},${sel("recommendations")}`;
|
|
2825
3172
|
var isElement2 = (target) => target instanceof window.Element;
|
|
2826
3173
|
var toLimit = (raw) => parseInt(raw ?? "", 10) || void 0;
|
|
2827
3174
|
var toIntent = (raw) => raw === "COMPLEMENTARY" ? "COMPLEMENTARY" : "RELATED";
|
|
2828
3175
|
var ListScope = class {
|
|
2829
|
-
/** @param {HTMLElement} root */
|
|
2830
3176
|
constructor(root) {
|
|
3177
|
+
__publicField(this, "root");
|
|
3178
|
+
__publicField(this, "products", []);
|
|
3179
|
+
__publicField(this, "pageInfo", null);
|
|
3180
|
+
__publicField(this, "collection", null);
|
|
3181
|
+
/** Counts loads, so only the latest one renders. */
|
|
3182
|
+
__publicField(this, "loadToken", 0);
|
|
2831
3183
|
this.root = root;
|
|
2832
|
-
this.products = [];
|
|
2833
|
-
this.pageInfo = null;
|
|
2834
|
-
this.collection = null;
|
|
2835
|
-
this.loadToken = 0;
|
|
2836
3184
|
setScopeStatus(root, "loading");
|
|
2837
3185
|
root.addEventListener("click", (event) => {
|
|
2838
3186
|
if (!isElement2(event.target)) return;
|
|
@@ -2842,31 +3190,29 @@ ${fragment}`, {
|
|
|
2842
3190
|
void busy(more, () => this.load(true));
|
|
2843
3191
|
});
|
|
2844
3192
|
}
|
|
2845
|
-
/** @param {string} selector */
|
|
2846
3193
|
$(selector) {
|
|
2847
3194
|
return scoped(this.root, selector, listSelector());
|
|
2848
3195
|
}
|
|
2849
|
-
/**
|
|
3196
|
+
/** The collection or search query of the list's attributes. */
|
|
2850
3197
|
options() {
|
|
2851
3198
|
const { root } = this;
|
|
2852
|
-
const
|
|
3199
|
+
const query2 = { reverse: get(root, "reverse") === "true" };
|
|
2853
3200
|
const first = toLimit(get(root, "limit"));
|
|
2854
|
-
if (first)
|
|
3201
|
+
if (first) query2.first = first;
|
|
2855
3202
|
const sortKey = get(root, "sort");
|
|
2856
|
-
if (sortKey)
|
|
3203
|
+
if (sortKey) query2.sortKey = sortKey;
|
|
2857
3204
|
if (has(root, "collection")) {
|
|
2858
|
-
|
|
2859
|
-
return
|
|
3205
|
+
query2.collection = get(root, "collection") ?? "";
|
|
3206
|
+
return query2;
|
|
2860
3207
|
}
|
|
2861
3208
|
const raw = get(root, "products") ?? "";
|
|
2862
|
-
|
|
2863
|
-
return
|
|
3209
|
+
query2.query = raw.startsWith(URL_QUERY_SOURCE) ? new URL(window.location.href).searchParams.get(raw.slice(URL_QUERY_SOURCE.length)) || "" : raw;
|
|
3210
|
+
return query2;
|
|
2864
3211
|
}
|
|
2865
3212
|
/**
|
|
2866
3213
|
* The product of a recommendations list: a handle, `url:…` (one dynamic product page), or empty inside a product
|
|
2867
3214
|
* scope for that product.
|
|
2868
3215
|
*
|
|
2869
|
-
* @returns {Promise<Product[]>}
|
|
2870
3216
|
* @throws {TetherError} `NOT_FOUND` when there is no product to recommend for.
|
|
2871
3217
|
*/
|
|
2872
3218
|
async loadRecommendations() {
|
|
@@ -2876,7 +3222,7 @@ ${fragment}`, {
|
|
|
2876
3222
|
if (!key) throw new TetherError("No product for recommendations", { code: "NOT_FOUND" });
|
|
2877
3223
|
return getRecommendations(key, toIntent(get(this.root, "intent")));
|
|
2878
3224
|
}
|
|
2879
|
-
/** @param
|
|
3225
|
+
/** @param more Appends the next page instead of replacing the products. */
|
|
2880
3226
|
async load(more = false) {
|
|
2881
3227
|
this.loadToken += 1;
|
|
2882
3228
|
const token = this.loadToken;
|
|
@@ -2942,21 +3288,7 @@ ${fragment}`, {
|
|
|
2942
3288
|
}
|
|
2943
3289
|
};
|
|
2944
3290
|
|
|
2945
|
-
// src/mounting.
|
|
2946
|
-
var mounting_exports = {};
|
|
2947
|
-
__export(mounting_exports, {
|
|
2948
|
-
hydrateFromPage: () => hydrateFromPage,
|
|
2949
|
-
liveLists: () => liveLists,
|
|
2950
|
-
liveProducts: () => liveProducts,
|
|
2951
|
-
mountProduct: () => mountProduct,
|
|
2952
|
-
mountedLists: () => mountedLists,
|
|
2953
|
-
mountedProducts: () => mountedProducts,
|
|
2954
|
-
pruneLive: () => pruneLive,
|
|
2955
|
-
retryFailed: () => retryFailed,
|
|
2956
|
-
settleMarket: () => settleMarket,
|
|
2957
|
-
trackMarket: () => trackMarket,
|
|
2958
|
-
within: () => within
|
|
2959
|
-
});
|
|
3291
|
+
// src/mounting.ts
|
|
2960
3292
|
var HYDRATE_SELECTOR = 'script[type="application/json"][data-tether-hydrate]';
|
|
2961
3293
|
var mountedProducts = /* @__PURE__ */ new WeakSet();
|
|
2962
3294
|
var mountedLists = /* @__PURE__ */ new WeakSet();
|
|
@@ -2972,10 +3304,7 @@ ${fragment}`, {
|
|
|
2972
3304
|
});
|
|
2973
3305
|
}
|
|
2974
3306
|
function within(root, selector) {
|
|
2975
|
-
const self = root.matches?.(selector) ? [
|
|
2976
|
-
/** @type {HTMLElement} */
|
|
2977
|
-
root
|
|
2978
|
-
] : [];
|
|
3307
|
+
const self = root.matches?.(selector) ? [root] : [];
|
|
2979
3308
|
return [...self, ...qsa(root, selector)];
|
|
2980
3309
|
}
|
|
2981
3310
|
function applyPageTitle(el, product) {
|
|
@@ -3056,12 +3385,8 @@ ${fragment}`, {
|
|
|
3056
3385
|
function flushRefresh() {
|
|
3057
3386
|
const keys = [...refreshQueue];
|
|
3058
3387
|
refreshQueue = /* @__PURE__ */ new Set();
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
/** @type {string} */
|
|
3062
|
-
keys[index]
|
|
3063
|
-
);
|
|
3064
|
-
promise.then((fresh) => {
|
|
3388
|
+
keys.forEach((key) => {
|
|
3389
|
+
refreshProduct(key).then((fresh) => {
|
|
3065
3390
|
applyRefresh(key, fresh);
|
|
3066
3391
|
}).catch((error) => {
|
|
3067
3392
|
reportError(error, `refresh ${key}`);
|
|
@@ -3086,11 +3411,19 @@ ${fragment}`, {
|
|
|
3086
3411
|
document.documentElement.classList.remove(`${cfg.prefix}-market-pending`);
|
|
3087
3412
|
});
|
|
3088
3413
|
}
|
|
3089
|
-
|
|
3090
|
-
|
|
3414
|
+
function toHydratePayload(json) {
|
|
3415
|
+
if (!isRecord(json)) return { country: void 0, language: void 0, products: [] };
|
|
3416
|
+
const { products } = json;
|
|
3417
|
+
const entries = Array.isArray(products) ? products : [];
|
|
3418
|
+
return {
|
|
3419
|
+
country: toCountry(json["country"]) ?? void 0,
|
|
3420
|
+
language: toLanguage(json["language"]) ?? void 0,
|
|
3421
|
+
products: entries.filter(isProductShape)
|
|
3422
|
+
};
|
|
3423
|
+
}
|
|
3091
3424
|
var readScripts = /* @__PURE__ */ new WeakSet();
|
|
3092
3425
|
function hydrateFromPage(root = document) {
|
|
3093
|
-
|
|
3426
|
+
within(root, HYDRATE_SELECTOR).forEach((script) => {
|
|
3094
3427
|
if (readScripts.has(script)) return;
|
|
3095
3428
|
readScripts.add(script);
|
|
3096
3429
|
let json;
|
|
@@ -3100,14 +3433,10 @@ ${fragment}`, {
|
|
|
3100
3433
|
warn("bad data-tether-hydrate", error);
|
|
3101
3434
|
return;
|
|
3102
3435
|
}
|
|
3103
|
-
const { country, language, products } = (
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
const usable = Array.isArray(products) ? products.filter(isUsable) : [];
|
|
3108
|
-
rememberOptionNames(usable);
|
|
3109
|
-
if (sameCode(country, cfg.country) && sameCode(language, cfg.language)) {
|
|
3110
|
-
hydrateProducts(usable);
|
|
3436
|
+
const { country, language, products } = toHydratePayload(json);
|
|
3437
|
+
rememberOptionNames(products);
|
|
3438
|
+
if (country === cfg.country && language === cfg.language) {
|
|
3439
|
+
hydrateProducts(products);
|
|
3111
3440
|
return;
|
|
3112
3441
|
}
|
|
3113
3442
|
log("hydration skipped: other market");
|
|
@@ -3118,53 +3447,108 @@ ${fragment}`, {
|
|
|
3118
3447
|
});
|
|
3119
3448
|
}
|
|
3120
3449
|
|
|
3121
|
-
// src/
|
|
3122
|
-
var {
|
|
3450
|
+
// src/part-integrity.ts
|
|
3451
|
+
var PART_INTEGRITY = { "tether.diagnose.js": "sha384-OKLlZJrSZ5IF9SOjKnEe485fi5xZlb4oLLvSfGjAssABzqd3h9JH1yA/hDczhkjJ", "tether.markets.js": "sha384-QVvTGiZJKx2hOPIpQPrGycWnKVTfDMEJ8z9heHIYSqib+6WyUoTmzKf2oBiFE3le", "tether.search.js": "sha384-P63pnKFjWTIVS8kEbBr4sfZZI91u1tImDTe+y50l2pdOM+W/KQsMymB6YRH801Dc", "tether.extras.js": "sha384-DJwYPZWf7o63ooQuDRvvhLrl5quZwhWO1YQ9RpySNQKjlDIS3uCwxDpDfZYiEjla", "tether.account.js": "sha384-q3UfPH5vDMbKm05wkvEPTDoA/aaIPmRQBDrqmXA4cNF4VFZAo4dgt61TmrNM69mi", "tether.account-pages.js": "sha384-YiSgDwM6qeHXJBcbWLRuk50h2ntAamNadS0MrZByc3cl5PKXZO297l38zYWm0lLe", "tether.analytics.js": "sha384-I93erLFdRG9uy3t+mnaDk/MgECoYOSG3q+9TfIQ39NROWZLvFjYrpHOAnBfsXaJc" };
|
|
3452
|
+
|
|
3453
|
+
// src/lazy-iife.ts
|
|
3123
3454
|
var coreModules = {
|
|
3124
|
-
"config.
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
"
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
"
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3455
|
+
get "config.ts"() {
|
|
3456
|
+
return [cfg, isConfigured, log, MARKET_SOURCES, parseMarkets, reportError, warn];
|
|
3457
|
+
},
|
|
3458
|
+
get "client.ts"() {
|
|
3459
|
+
return [endpoint, gql, orNull, shaped, TetherError];
|
|
3460
|
+
},
|
|
3461
|
+
get "dom.ts"() {
|
|
3462
|
+
return [
|
|
3463
|
+
ANALYTICS_EVENTS,
|
|
3464
|
+
attr,
|
|
3465
|
+
attributesFromJson,
|
|
3466
|
+
emit,
|
|
3467
|
+
formatDate,
|
|
3468
|
+
formatMoney2,
|
|
3469
|
+
get,
|
|
3470
|
+
has,
|
|
3471
|
+
hasTemplate,
|
|
3472
|
+
isOwnEvent,
|
|
3473
|
+
parseJSONAttr,
|
|
3474
|
+
productUrl2,
|
|
3475
|
+
qsa,
|
|
3476
|
+
repeat,
|
|
3477
|
+
sel,
|
|
3478
|
+
setImage,
|
|
3479
|
+
setText,
|
|
3480
|
+
show
|
|
3481
|
+
];
|
|
3482
|
+
},
|
|
3483
|
+
get "loader.ts"() {
|
|
3484
|
+
return [clearProductCache, getProduct];
|
|
3485
|
+
},
|
|
3486
|
+
get "selling-plans.ts"() {
|
|
3487
|
+
return [allPlans];
|
|
3488
|
+
},
|
|
3489
|
+
get "status.ts"() {
|
|
3490
|
+
return [boundary, resolveProductKey];
|
|
3491
|
+
},
|
|
3492
|
+
get "cart.ts"() {
|
|
3493
|
+
return [Cart];
|
|
3494
|
+
},
|
|
3495
|
+
get "product.ts"() {
|
|
3496
|
+
return [scopes];
|
|
3497
|
+
},
|
|
3498
|
+
get "collection.ts"() {
|
|
3499
|
+
return [listSelector];
|
|
3500
|
+
},
|
|
3501
|
+
get "mounting.ts"() {
|
|
3502
|
+
return [liveLists, liveProducts, mountedProducts, mountProduct, pruneLive, within];
|
|
3503
|
+
},
|
|
3504
|
+
get "market-codes.ts"() {
|
|
3505
|
+
return [toCountry, toLanguage];
|
|
3506
|
+
},
|
|
3507
|
+
get "shopify-values.ts"() {
|
|
3508
|
+
return [isHttpsUrl];
|
|
3509
|
+
},
|
|
3510
|
+
// Parts that load other parts (account → account-pages) use this same loader.
|
|
3511
|
+
get "lazy.ts"() {
|
|
3512
|
+
return [loadPart];
|
|
3513
|
+
}
|
|
3138
3514
|
};
|
|
3139
3515
|
var MINIFIED_SCRIPT = /\.min\.js(\?|$)/;
|
|
3140
3516
|
var coreScript = typeof document === "undefined" ? "" : document.currentScript?.getAttribute("src") ?? "";
|
|
3141
3517
|
var coreScriptUrl = coreScript ? new URL(coreScript, document.baseURI).href : "";
|
|
3142
3518
|
var parts = /* @__PURE__ */ new Map();
|
|
3143
3519
|
function partsFolder() {
|
|
3144
|
-
if (
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3520
|
+
if (cfg.partsUrl) return new URL(cfg.partsUrl, document.baseURI).href;
|
|
3521
|
+
if (!coreScriptUrl) return null;
|
|
3522
|
+
return coreScriptUrl.replace(/[^/]*$/, "");
|
|
3523
|
+
}
|
|
3524
|
+
function applyIntegrity(script, integrity) {
|
|
3525
|
+
script.integrity = integrity;
|
|
3526
|
+
if (new URL(script.src).origin !== window.location.origin) script.crossOrigin = "anonymous";
|
|
3150
3527
|
}
|
|
3151
|
-
function loadScript(name,
|
|
3528
|
+
function loadScript(name, folder, file, integrity) {
|
|
3152
3529
|
return new Promise((resolve, reject) => {
|
|
3530
|
+
const url2 = `${folder.replace(/\/?$/, "/")}${file}`;
|
|
3153
3531
|
const script = document.createElement("script");
|
|
3154
|
-
script.src =
|
|
3532
|
+
script.src = url2;
|
|
3155
3533
|
script.async = true;
|
|
3534
|
+
applyIntegrity(script, integrity);
|
|
3156
3535
|
script.onload = () => {
|
|
3157
|
-
const { Tether } =
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3536
|
+
const { Tether } = window;
|
|
3537
|
+
const part = Tether?.__parts[name];
|
|
3538
|
+
if (part) {
|
|
3539
|
+
resolve(part);
|
|
3540
|
+
return;
|
|
3541
|
+
}
|
|
3542
|
+
parts.delete(name);
|
|
3543
|
+
reject(new Error(`${url2} did not register`));
|
|
3164
3544
|
};
|
|
3165
3545
|
script.onerror = () => {
|
|
3166
3546
|
parts.delete(name);
|
|
3167
|
-
reject(
|
|
3547
|
+
reject(
|
|
3548
|
+
new Error(
|
|
3549
|
+
`Could not load ${url2}, or it is not the file built with this Tether script. Host the same release's files next to the Tether script or at partsUrl.`
|
|
3550
|
+
)
|
|
3551
|
+
);
|
|
3168
3552
|
};
|
|
3169
3553
|
document.head.appendChild(script);
|
|
3170
3554
|
});
|
|
@@ -3172,39 +3556,30 @@ ${fragment}`, {
|
|
|
3172
3556
|
function loadPart(name) {
|
|
3173
3557
|
let loading = parts.get(name);
|
|
3174
3558
|
if (!loading) {
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3559
|
+
const folder = partsFolder();
|
|
3560
|
+
if (folder === null) {
|
|
3561
|
+
return Promise.reject(
|
|
3562
|
+
new Error(`The Tether script's own location is unknown, so its ${name} part can't be found: set partsUrl.`)
|
|
3563
|
+
);
|
|
3180
3564
|
}
|
|
3181
3565
|
const suffix = MINIFIED_SCRIPT.test(coreScriptUrl) || !coreScriptUrl ? ".min" : "";
|
|
3182
|
-
|
|
3566
|
+
const file = `tether.${name}${suffix}.js`;
|
|
3567
|
+
const integrity = PART_INTEGRITY[file];
|
|
3568
|
+
if (integrity === void 0) {
|
|
3569
|
+
return Promise.reject(new Error(`This Tether script was built without ${file}, so it can't load that part.`));
|
|
3570
|
+
}
|
|
3571
|
+
loading = loadScript(name, folder, file, integrity);
|
|
3183
3572
|
parts.set(name, loading);
|
|
3184
3573
|
}
|
|
3185
|
-
return
|
|
3186
|
-
/** @type {Promise<Parts[Name]>} */
|
|
3187
|
-
loading
|
|
3188
|
-
);
|
|
3574
|
+
return loading;
|
|
3189
3575
|
}
|
|
3190
|
-
coreModules["lazy.js"] = { loadPart, coreModules };
|
|
3191
3576
|
|
|
3192
|
-
// src/markets.
|
|
3577
|
+
// src/markets.ts
|
|
3193
3578
|
var storageKey = () => `${cfg.prefix}:market:${cfg.domain}`;
|
|
3194
|
-
|
|
3195
|
-
|
|
3579
|
+
var pageStorage = { getItem: (key) => getStored(key) };
|
|
3580
|
+
function parseJsonOrNull(text2) {
|
|
3196
3581
|
try {
|
|
3197
|
-
return
|
|
3198
|
-
} catch {
|
|
3199
|
-
return null;
|
|
3200
|
-
}
|
|
3201
|
-
}
|
|
3202
|
-
function parseJsonOrNull(text) {
|
|
3203
|
-
try {
|
|
3204
|
-
return (
|
|
3205
|
-
/** @type {unknown} */
|
|
3206
|
-
JSON.parse(text)
|
|
3207
|
-
);
|
|
3582
|
+
return JSON.parse(text2);
|
|
3208
3583
|
} catch (error) {
|
|
3209
3584
|
if (error instanceof SyntaxError) return null;
|
|
3210
3585
|
throw error;
|
|
@@ -3214,22 +3589,19 @@ ${fragment}`, {
|
|
|
3214
3589
|
if (typeof value !== "object" || value === null) return null;
|
|
3215
3590
|
const country = toCountry(Reflect.get(value, "country"));
|
|
3216
3591
|
if (!country) return null;
|
|
3217
|
-
const locale = (
|
|
3218
|
-
/** @type {unknown} */
|
|
3219
|
-
Reflect.get(value, "locale")
|
|
3220
|
-
);
|
|
3592
|
+
const locale = Reflect.get(value, "locale");
|
|
3221
3593
|
return {
|
|
3222
3594
|
country,
|
|
3223
3595
|
language: toLanguage(Reflect.get(value, "language")) ?? void 0,
|
|
3224
3596
|
locale: typeof locale === "string" && locale ? locale : void 0
|
|
3225
3597
|
};
|
|
3226
3598
|
}
|
|
3227
|
-
function readSavedMarket(storage =
|
|
3228
|
-
const
|
|
3229
|
-
return
|
|
3599
|
+
function readSavedMarket(storage = pageStorage) {
|
|
3600
|
+
const text2 = storage.getItem(storageKey());
|
|
3601
|
+
return text2 ? toMarket(parseJsonOrNull(text2)) : null;
|
|
3230
3602
|
}
|
|
3231
3603
|
|
|
3232
|
-
// src/page-market.
|
|
3604
|
+
// src/page-market.ts
|
|
3233
3605
|
var configuredMarket = { country: void 0, language: void 0, locale: void 0 };
|
|
3234
3606
|
var lastPageHadOwnMarket = false;
|
|
3235
3607
|
function pageMarket() {
|
|
@@ -3266,21 +3638,33 @@ ${fragment}`, {
|
|
|
3266
3638
|
if (market.locale && !overrides.locale) cfg.locale = market.locale;
|
|
3267
3639
|
log("market", market);
|
|
3268
3640
|
}
|
|
3269
|
-
var
|
|
3641
|
+
var sameCode = (a, b) => (a ?? "").toUpperCase() === (b ?? "").toUpperCase();
|
|
3270
3642
|
function marketForArrival() {
|
|
3271
3643
|
const page = pageMarket();
|
|
3272
3644
|
const leftOwnMarket = page === null && lastPageHadOwnMarket;
|
|
3273
3645
|
lastPageHadOwnMarket = page !== null;
|
|
3274
3646
|
const target = page ?? (leftOwnMarket ? readSavedMarket() ?? configuredMarket : null);
|
|
3275
3647
|
if (!target?.country) return null;
|
|
3276
|
-
if (
|
|
3648
|
+
if (sameCode(target.country, cfg.country) && sameCode(target.language, cfg.language)) return null;
|
|
3277
3649
|
return { country: target.country, language: target.language, locale: target.locale };
|
|
3278
3650
|
}
|
|
3279
3651
|
function setCountry(code, options) {
|
|
3280
3652
|
return loadPart("markets").then((part) => part.setCountry(code, options));
|
|
3281
3653
|
}
|
|
3282
3654
|
|
|
3283
|
-
// src/account-
|
|
3655
|
+
// src/account-marks.ts
|
|
3656
|
+
var hintKey = () => `${cfg.prefix}:account-hint:${cfg.domain}`;
|
|
3657
|
+
function removeFromBothAreas(key) {
|
|
3658
|
+
removeStored(key, "sessionStorage");
|
|
3659
|
+
removeStored(key, "localStorage");
|
|
3660
|
+
}
|
|
3661
|
+
function forgetBrowserModeTokens() {
|
|
3662
|
+
removeFromBothAreas(`${cfg.prefix}:account:${cfg.domain}`);
|
|
3663
|
+
removeFromBothAreas(`${cfg.prefix}:account-login:${cfg.domain}`);
|
|
3664
|
+
}
|
|
3665
|
+
var isLoginOutcome = (value) => value === "in" || value === "expired" || value === "error";
|
|
3666
|
+
|
|
3667
|
+
// src/account-api.ts
|
|
3284
3668
|
var ACCOUNT_ATTRIBUTES = [
|
|
3285
3669
|
"account-login",
|
|
3286
3670
|
"account-bind",
|
|
@@ -3293,20 +3677,12 @@ ${fragment}`, {
|
|
|
3293
3677
|
"address-form"
|
|
3294
3678
|
];
|
|
3295
3679
|
var accountSelector = () => [...ACCOUNT_ATTRIBUTES.map((name) => `[${attr(name)}]`), `[${attr("show")}^="logged-"]`].join(",");
|
|
3296
|
-
function
|
|
3297
|
-
|
|
3298
|
-
const hintKey = `${cfg.prefix}:account-hint:${cfg.domain}`;
|
|
3299
|
-
try {
|
|
3300
|
-
return Boolean(
|
|
3301
|
-
sessionStorage.getItem(sessionKey) || localStorage.getItem(sessionKey) || localStorage.getItem(hintKey)
|
|
3302
|
-
);
|
|
3303
|
-
} catch {
|
|
3304
|
-
return false;
|
|
3305
|
-
}
|
|
3680
|
+
function hasLoginHint() {
|
|
3681
|
+
return Boolean(getStored(hintKey()));
|
|
3306
3682
|
}
|
|
3307
3683
|
function mountAccountIfUsed(root) {
|
|
3308
|
-
if (!cfg.account
|
|
3309
|
-
const needed = within(root, accountSelector()).length > 0 ||
|
|
3684
|
+
if (!cfg.account) return;
|
|
3685
|
+
const needed = within(root, accountSelector()).length > 0 || isLoginOutcome(new URLSearchParams(window.location.search).get("account")) || hasLoginHint();
|
|
3310
3686
|
if (!needed) return;
|
|
3311
3687
|
loadPart("account").then((part) => part.mountAccount(root)).catch((error) => {
|
|
3312
3688
|
reportError(error, "account");
|
|
@@ -3314,22 +3690,14 @@ ${fragment}`, {
|
|
|
3314
3690
|
}
|
|
3315
3691
|
function fromPart(name) {
|
|
3316
3692
|
return (...args) => loadPart("account").then((part) => {
|
|
3317
|
-
const call =
|
|
3318
|
-
/**
|
|
3319
|
-
* @type {(...callArgs: Parameters<AccountPart[Name]>) => AccountResult<Name> | PromiseLike<AccountResult<Name>>}
|
|
3320
|
-
*/
|
|
3321
|
-
part[name]
|
|
3322
|
-
);
|
|
3693
|
+
const call = part[name];
|
|
3323
3694
|
return call(...args);
|
|
3324
3695
|
});
|
|
3325
3696
|
}
|
|
3326
3697
|
var accountApi = {
|
|
3327
3698
|
login: fromPart("login"),
|
|
3328
3699
|
logout: fromPart("logout"),
|
|
3329
|
-
handleCallback: fromPart("handleCallback"),
|
|
3330
|
-
getToken: fromPart("getToken"),
|
|
3331
3700
|
isLoggedIn: fromPart("isLoggedIn"),
|
|
3332
|
-
query: fromPart("query"),
|
|
3333
3701
|
customer: fromPart("customer"),
|
|
3334
3702
|
orders: fromPart("orders"),
|
|
3335
3703
|
order: fromPart("order"),
|
|
@@ -3344,7 +3712,7 @@ ${fragment}`, {
|
|
|
3344
3712
|
mountAccount: fromPart("mountAccount")
|
|
3345
3713
|
};
|
|
3346
3714
|
|
|
3347
|
-
// src/controls/cart-controls.
|
|
3715
|
+
// src/controls/cart-controls.ts
|
|
3348
3716
|
var lineKeyAttribute = () => `data-${cfg.prefix}-key`;
|
|
3349
3717
|
var isElement3 = (target) => target instanceof window.Element;
|
|
3350
3718
|
function lineOf(target) {
|
|
@@ -3441,7 +3809,7 @@ ${fragment}`, {
|
|
|
3441
3809
|
document.addEventListener("keydown", onKeydown);
|
|
3442
3810
|
}
|
|
3443
3811
|
|
|
3444
|
-
// src/controls/discount.
|
|
3812
|
+
// src/controls/discount.ts
|
|
3445
3813
|
var isElement4 = (target) => target instanceof window.Element;
|
|
3446
3814
|
function codeInputOf(form) {
|
|
3447
3815
|
const input = form.querySelector(sel("discount-input")) ?? form.querySelector("input");
|
|
@@ -3499,7 +3867,7 @@ ${fragment}`, {
|
|
|
3499
3867
|
document.addEventListener("click", onClick3);
|
|
3500
3868
|
}
|
|
3501
3869
|
|
|
3502
|
-
// src/controls/extras.
|
|
3870
|
+
// src/controls/extras.ts
|
|
3503
3871
|
var addGroup = (button) => loadPart("extras").then((part) => part.addGroup(button));
|
|
3504
3872
|
var reportExtrasError = (error) => {
|
|
3505
3873
|
reportError(error, "extras");
|
|
@@ -3518,6 +3886,7 @@ ${fragment}`, {
|
|
|
3518
3886
|
}).catch(reportExtrasError);
|
|
3519
3887
|
}
|
|
3520
3888
|
function onVariantChanged(event) {
|
|
3889
|
+
if (!isOwnEvent(event, "variant-changed")) return;
|
|
3521
3890
|
const { target } = event;
|
|
3522
3891
|
if (!(target instanceof window.Element)) return;
|
|
3523
3892
|
if (!cfg.variantUrl && !target.hasAttribute(attr("variant-url"))) return;
|
|
@@ -3528,14 +3897,10 @@ ${fragment}`, {
|
|
|
3528
3897
|
function bindExtrasControls() {
|
|
3529
3898
|
document.addEventListener("click", onClick4);
|
|
3530
3899
|
window.addEventListener("storage", onStorage);
|
|
3531
|
-
document.addEventListener(
|
|
3532
|
-
`${cfg.prefix}:variant-changed`,
|
|
3533
|
-
/** @type {EventListener} */
|
|
3534
|
-
onVariantChanged
|
|
3535
|
-
);
|
|
3900
|
+
document.addEventListener(`${cfg.prefix}:variant-changed`, onVariantChanged);
|
|
3536
3901
|
}
|
|
3537
3902
|
|
|
3538
|
-
// src/controls/navigation.
|
|
3903
|
+
// src/controls/navigation.ts
|
|
3539
3904
|
var mountedBody = null;
|
|
3540
3905
|
function rememberDocumentMount() {
|
|
3541
3906
|
mountedBody = document.body;
|
|
@@ -3556,7 +3921,9 @@ ${fragment}`, {
|
|
|
3556
3921
|
}
|
|
3557
3922
|
function bindAstroRouter(hooks) {
|
|
3558
3923
|
document.addEventListener("astro:page-load", () => {
|
|
3559
|
-
|
|
3924
|
+
onPageLoad(hooks).catch((error) => {
|
|
3925
|
+
reportError(error, "page mount");
|
|
3926
|
+
});
|
|
3560
3927
|
});
|
|
3561
3928
|
document.addEventListener("astro:before-swap", () => {
|
|
3562
3929
|
mountedBody = null;
|
|
@@ -3583,11 +3950,13 @@ ${fragment}`, {
|
|
|
3583
3950
|
window.addEventListener("online", onOnline);
|
|
3584
3951
|
}
|
|
3585
3952
|
|
|
3586
|
-
// src/index.
|
|
3587
|
-
var VERSION = "0.
|
|
3588
|
-
var ANALYTICS_EVENTS = ["product-ready", "list-ready", "cart-updated", "cart-toggled", "checkout", "search"];
|
|
3953
|
+
// src/index.ts
|
|
3954
|
+
var VERSION = "0.7.0";
|
|
3589
3955
|
var OBSERVE_DEBOUNCE_MS = 50;
|
|
3590
|
-
|
|
3956
|
+
function pageConfig() {
|
|
3957
|
+
const config = pageGlobal("TETHER_CONFIG");
|
|
3958
|
+
return typeof config === "object" && config !== null ? Object.fromEntries(Object.entries(config)) : {};
|
|
3959
|
+
}
|
|
3591
3960
|
var initPromise = null;
|
|
3592
3961
|
var observer = null;
|
|
3593
3962
|
var analyticsQueue = null;
|
|
@@ -3601,10 +3970,7 @@ ${fragment}`, {
|
|
|
3601
3970
|
const list = new ListScope(el);
|
|
3602
3971
|
liveLists.add(list);
|
|
3603
3972
|
el.addEventListener(`${cfg.prefix}:list-rendered`, (event) => {
|
|
3604
|
-
mount(
|
|
3605
|
-
/** @type {CustomEvent<{ container: ParentNode }>} */
|
|
3606
|
-
event.detail.container
|
|
3607
|
-
);
|
|
3973
|
+
if (isOwnEvent(event, "list-rendered")) mount(event.detail.container);
|
|
3608
3974
|
});
|
|
3609
3975
|
void trackMarket(list.load());
|
|
3610
3976
|
});
|
|
@@ -3625,21 +3991,39 @@ ${fragment}`, {
|
|
|
3625
3991
|
});
|
|
3626
3992
|
}
|
|
3627
3993
|
}
|
|
3628
|
-
function
|
|
3629
|
-
if (!isConfigured()) return 0;
|
|
3630
|
-
if (root === document) rememberDocumentMount();
|
|
3631
|
-
pruneLive();
|
|
3632
|
-
hydrateFromPage(root.ownerDocument ?? document);
|
|
3994
|
+
function mountWithin(root) {
|
|
3633
3995
|
let count = mountLists(root);
|
|
3634
3996
|
within(root, sel("product")).forEach((el) => {
|
|
3635
3997
|
if (mountProduct(el)) count++;
|
|
3636
3998
|
});
|
|
3637
3999
|
mountParts(root);
|
|
4000
|
+
return count;
|
|
4001
|
+
}
|
|
4002
|
+
function mount(root = document) {
|
|
4003
|
+
if (!isConfigured()) return 0;
|
|
4004
|
+
if (root === document) rememberDocumentMount();
|
|
4005
|
+
pruneLive();
|
|
4006
|
+
hydrateFromPage(root.ownerDocument ?? document);
|
|
4007
|
+
const count = mountWithin(root);
|
|
3638
4008
|
settleMarket();
|
|
3639
4009
|
Cart.render();
|
|
3640
4010
|
log(`mounted ${count} scope(s)`, root);
|
|
3641
4011
|
return count;
|
|
3642
4012
|
}
|
|
4013
|
+
function mountAdded(added) {
|
|
4014
|
+
const roots = added.filter(
|
|
4015
|
+
(root) => root.isConnected && !added.some((other) => other !== root && other.contains(root))
|
|
4016
|
+
);
|
|
4017
|
+
if (!roots.length || !isConfigured()) return;
|
|
4018
|
+
pruneLive();
|
|
4019
|
+
roots.forEach((root) => {
|
|
4020
|
+
hydrateFromPage(root);
|
|
4021
|
+
});
|
|
4022
|
+
const count = roots.reduce((sum, root) => sum + mountWithin(root), 0);
|
|
4023
|
+
settleMarket();
|
|
4024
|
+
Cart.render();
|
|
4025
|
+
log(`mounted ${count} scope(s) in ${roots.length} added element(s)`);
|
|
4026
|
+
}
|
|
3643
4027
|
function unmount() {
|
|
3644
4028
|
Cart.close();
|
|
3645
4029
|
}
|
|
@@ -3661,13 +4045,13 @@ ${fragment}`, {
|
|
|
3661
4045
|
ANALYTICS_EVENTS.forEach((name) => {
|
|
3662
4046
|
document.addEventListener(`${cfg.prefix}:${name}`, (event) => queue.push(event));
|
|
3663
4047
|
});
|
|
3664
|
-
document.addEventListener("astro:page-load", () => queue.push(
|
|
4048
|
+
document.addEventListener("astro:page-load", (event) => queue.push(event));
|
|
3665
4049
|
loadPart("analytics").then((part) => part.startAnalytics(queue)).catch((error) => {
|
|
3666
4050
|
reportError(error, "analytics");
|
|
3667
4051
|
});
|
|
3668
4052
|
}
|
|
3669
4053
|
async function cartInPageMarket(cart) {
|
|
3670
|
-
const country = cfg.country
|
|
4054
|
+
const country = cfg.country;
|
|
3671
4055
|
const cartCountry = cart?.buyerIdentity.countryCode;
|
|
3672
4056
|
if (!cart || !country || !cartCountry || cartCountry === country) return cart;
|
|
3673
4057
|
try {
|
|
@@ -3679,12 +4063,13 @@ ${fragment}`, {
|
|
|
3679
4063
|
}
|
|
3680
4064
|
function init2(overrides = {}) {
|
|
3681
4065
|
if (initPromise && !Object.keys(overrides).length) return initPromise;
|
|
3682
|
-
configure({ ...
|
|
4066
|
+
configure({ ...pageConfig(), ...overrides });
|
|
3683
4067
|
if (!isConfigured()) {
|
|
3684
4068
|
warn("TETHER_CONFIG is missing domain or token. Tether is idle.");
|
|
3685
4069
|
initPromise = Promise.resolve(null);
|
|
3686
4070
|
return initPromise;
|
|
3687
4071
|
}
|
|
4072
|
+
forgetBrowserModeTokens();
|
|
3688
4073
|
bindControls();
|
|
3689
4074
|
if (cfg.observe) startObserver();
|
|
3690
4075
|
if (cfg.analytics && !analyticsQueue) startAnalyticsQueue();
|
|
@@ -3706,10 +4091,9 @@ ${fragment}`, {
|
|
|
3706
4091
|
});
|
|
3707
4092
|
clearTimeout(timer);
|
|
3708
4093
|
timer = setTimeout(() => {
|
|
3709
|
-
|
|
3710
|
-
if (node.isConnected) mount(node);
|
|
3711
|
-
});
|
|
4094
|
+
const added = [...queued];
|
|
3712
4095
|
queued.clear();
|
|
4096
|
+
mountAdded(added);
|
|
3713
4097
|
}, OBSERVE_DEBOUNCE_MS);
|
|
3714
4098
|
});
|
|
3715
4099
|
observer.observe(document.body, { childList: true, subtree: true });
|
|
@@ -3718,32 +4102,28 @@ ${fragment}`, {
|
|
|
3718
4102
|
var getLocalization = () => loadPart("markets").then((part) => part.getLocalization());
|
|
3719
4103
|
var diagnose = (options) => loadPart("diagnose").then((part) => part.diagnose({ ...options, version: VERSION }));
|
|
3720
4104
|
function on(name, listener) {
|
|
4105
|
+
const type = `${cfg.prefix}:${name}`;
|
|
3721
4106
|
const handler = (event) => {
|
|
3722
|
-
|
|
3723
|
-
/** @type {CustomEvent} Tether's events are CustomEvents. */
|
|
3724
|
-
event
|
|
3725
|
-
);
|
|
3726
|
-
listener(custom.detail, custom);
|
|
4107
|
+
if (isOwnEvent(event, name)) listener(event.detail, event);
|
|
3727
4108
|
};
|
|
3728
|
-
document.addEventListener(
|
|
4109
|
+
document.addEventListener(type, handler);
|
|
3729
4110
|
return () => {
|
|
3730
|
-
document.removeEventListener(
|
|
4111
|
+
document.removeEventListener(type, handler);
|
|
3731
4112
|
};
|
|
3732
4113
|
}
|
|
3733
|
-
var
|
|
4114
|
+
var publicApi = {
|
|
3734
4115
|
VERSION,
|
|
3735
4116
|
init: init2,
|
|
3736
4117
|
mount,
|
|
3737
4118
|
unmount,
|
|
3738
|
-
/**
|
|
4119
|
+
/** The live config, read-only. */
|
|
3739
4120
|
get config() {
|
|
3740
|
-
return
|
|
4121
|
+
return configView;
|
|
3741
4122
|
},
|
|
3742
|
-
/** @returns {Promise<TetherApi | null>} */
|
|
3743
4123
|
get ready() {
|
|
3744
4124
|
return initPromise ?? Promise.resolve(null);
|
|
3745
4125
|
},
|
|
3746
|
-
query
|
|
4126
|
+
query,
|
|
3747
4127
|
withContext,
|
|
3748
4128
|
cart: Cart,
|
|
3749
4129
|
getProduct,
|
|
@@ -3764,19 +4144,15 @@ ${fragment}`, {
|
|
|
3764
4144
|
detectMarket,
|
|
3765
4145
|
getLocalization,
|
|
3766
4146
|
diagnose,
|
|
3767
|
-
__core: coreModules,
|
|
3768
|
-
// The `<script>` build's parts register here when their file has loaded.
|
|
3769
|
-
__parts: (
|
|
3770
|
-
/** @type {Record<string, unknown>} */
|
|
3771
|
-
{}
|
|
3772
|
-
),
|
|
3773
4147
|
on
|
|
3774
4148
|
};
|
|
4149
|
+
var internals = { __core: coreModules, __parts: {} };
|
|
4150
|
+
var api = Object.assign(publicApi, internals);
|
|
3775
4151
|
var index_default = api;
|
|
3776
4152
|
function autoStart() {
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
if (!
|
|
4153
|
+
Reflect.set(window, "Tether", api);
|
|
4154
|
+
const config = pageGlobal("TETHER_CONFIG");
|
|
4155
|
+
if (!config || field(config, "autoMount") === false || pageGlobal("TETHER_INTEGRATION")) return;
|
|
3780
4156
|
const start = () => {
|
|
3781
4157
|
init2().then(() => mount(document)).catch((error) => {
|
|
3782
4158
|
reportError(error, "init");
|