@occluze/tether 0.6.8 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -28
- package/dist/esm/astro.js +2 -2
- package/dist/esm/chunks/account-CXMA5RPZ.js +1 -0
- package/dist/esm/chunks/account-pages-IZO7FEOZ.js +1 -0
- package/dist/esm/chunks/analytics-PG5FPRDN.js +1 -0
- package/dist/esm/chunks/chunk-4HD4J5JC.js +1 -0
- package/dist/esm/chunks/chunk-6TFMLT4A.js +4 -0
- package/dist/esm/chunks/chunk-BBVUVPMO.js +1 -0
- package/dist/esm/chunks/chunk-E5YO6274.js +1 -0
- package/dist/esm/chunks/chunk-EW5SYO6L.js +1 -0
- package/dist/esm/chunks/chunk-HJY2JSNB.js +1 -0
- package/dist/esm/chunks/chunk-MQYB6BH4.js +1 -0
- package/dist/esm/chunks/chunk-N3V45JPS.js +1 -0
- package/dist/esm/chunks/chunk-RPMBCAGS.js +1 -0
- package/dist/esm/chunks/chunk-VFNHIGCS.js +1 -0
- package/dist/esm/chunks/chunk-WT7RGGJU.js +1 -0
- package/dist/esm/chunks/chunk-YN6LEBEN.js +1 -0
- package/dist/esm/chunks/chunk-Z6HS3H7K.js +1 -0
- package/dist/esm/chunks/diagnose-XCEBQKBO.js +14 -0
- package/dist/esm/chunks/extras-4HU3VSBQ.js +1 -0
- package/dist/esm/chunks/markets-ui-M4IQ62CT.js +1 -0
- package/dist/esm/chunks/search-A5DS2IOS.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 +332 -367
- package/dist/tether.account-pages.min.js +1 -1
- package/dist/tether.account.js +253 -492
- package/dist/tether.account.min.js +1 -1
- package/dist/tether.analytics.js +82 -89
- package/dist/tether.analytics.min.js +1 -1
- package/dist/tether.diagnose.js +52 -34
- package/dist/tether.diagnose.min.js +2 -2
- package/dist/tether.extras.js +42 -37
- package/dist/tether.extras.min.js +1 -1
- package/dist/tether.js +1361 -935
- package/dist/tether.markets.js +89 -91
- 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 +60 -69
- 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/cart-id.d.ts +2 -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 +85 -0
- package/dist/types/src/cart/render.d.ts +17 -0
- package/dist/types/src/cart/store.d.ts +185 -0
- package/dist/types/src/cart.d.ts +106 -0
- package/dist/types/src/client.d.ts +134 -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 +190 -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 +90 -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 -24
- 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-lines.js +0 -96
- package/src/account/order-list.js +0 -91
- package/src/account/order-page.js +0 -192
- package/src/account/page-events.js +0 -288
- 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/subscriptions-view.js +0 -158
- package/src/account-api.js +0 -118
- package/src/account-links.js +0 -35
- package/src/account-pages.js +0 -89
- package/src/account.js +0 -342
- package/src/amounts.js +0 -36
- package/src/analytics-shopify.js +0 -241
- package/src/analytics.js +0 -352
- package/src/api-version.js +0 -85
- package/src/astro.js +0 -266
- package/src/cart/checkout.js +0 -97
- package/src/cart/drawer.js +0 -138
- package/src/cart/mutations.js +0 -234
- package/src/cart/render.js +0 -294
- package/src/cart/store.js +0 -326
- package/src/cart.js +0 -391
- package/src/client.js +0 -267
- package/src/collection.js +0 -183
- package/src/config.js +0 -349
- package/src/controls/cart-controls.js +0 -155
- package/src/controls/discount.js +0 -96
- package/src/controls/extras.js +0 -63
- package/src/controls/navigation.js +0 -83
- package/src/diagnose-markup.js +0 -333
- package/src/diagnose.js +0 -295
- package/src/dom.js +0 -287
- package/src/extras.js +0 -145
- package/src/fragments.js +0 -237
- package/src/images.js +0 -104
- 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/loader.js +0 -337
- package/src/market-codes.js +0 -35
- package/src/markets-ui.js +0 -204
- package/src/markets.js +0 -334
- package/src/markup.js +0 -64
- package/src/mounting.js +0 -277
- package/src/page-market.js +0 -116
- 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-events.js +0 -130
- package/src/product-render.js +0 -257
- package/src/product-state.js +0 -318
- package/src/product.js +0 -344
- package/src/repeaters.js +0 -173
- package/src/search.js +0 -323
- package/src/selling-plans.js +0 -154
- package/src/server.js +0 -215
- package/src/shopify-values.js +0 -89
- package/src/status.js +0 -126
- package/src/store-html.js +0 -0
- package/src/storefront/catalog.js +0 -199
- package/src/storefront/json-ld.js +0 -167
- package/src/variant-render.js +0 -165
- 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,76 @@
|
|
|
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]) => inner === void 0 || 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 operationOf(document2) {
|
|
466
|
+
return /\bmutation\b/.test(document2.replace(STRINGS_AND_COMMENTS, "")) ? "mutation" : "query";
|
|
467
|
+
}
|
|
468
|
+
function inContext(query2, { country, language }) {
|
|
241
469
|
const args = [];
|
|
242
|
-
const country = toCountry(ctx.country);
|
|
243
|
-
const language = toLanguage(ctx.language);
|
|
244
470
|
if (country) args.push(`country: ${country}`);
|
|
245
471
|
if (language) args.push(`language: ${language}`);
|
|
246
|
-
if (!args.length
|
|
472
|
+
if (!args.length) return query2;
|
|
247
473
|
const tag = `@inContext(${args.join(", ")})`;
|
|
248
|
-
|
|
249
|
-
|
|
474
|
+
const blanked = query2.replace(STRINGS_AND_COMMENTS, (text2) => " ".repeat(text2.length));
|
|
475
|
+
const selectionStart = blanked.indexOf("{");
|
|
476
|
+
if (blanked.slice(0, selectionStart).includes("@inContext")) return query2;
|
|
477
|
+
if (blanked.trimStart().startsWith("{")) return `query ${tag} ${query2.slice(selectionStart)}`;
|
|
478
|
+
const header = OPERATION_START.exec(blanked);
|
|
479
|
+
if (!header) return query2;
|
|
480
|
+
const end = header[0].length;
|
|
481
|
+
return `${query2.slice(0, end).trimEnd()} ${tag} ${query2.slice(end).trimStart()}`;
|
|
482
|
+
}
|
|
483
|
+
function withContext(query2, ctx = cfg) {
|
|
484
|
+
const country = toCountry(ctx.country) ?? void 0;
|
|
485
|
+
const language = toLanguage(ctx.language) ?? void 0;
|
|
486
|
+
return inContext(query2, { country, language });
|
|
250
487
|
}
|
|
251
488
|
var RETRY_BACKOFF_MS = 300;
|
|
252
489
|
var THROTTLE_BACKOFF_MS = 500;
|
|
253
490
|
var SECURITY_REJECTION = 430;
|
|
491
|
+
var MS_PER_SECOND = 1e3;
|
|
254
492
|
var INVENTORY_FIELD = "quantityAvailable";
|
|
255
493
|
var storesWithoutInventory = /* @__PURE__ */ new Set();
|
|
256
494
|
var storeOf = (conf) => `${conf.domain} ${conf.apiVersion} ${conf.token}`;
|
|
495
|
+
function queryText(conf, query2, access, { context, operation }) {
|
|
496
|
+
const written = typeof query2 === "string" ? query2 : query2(access);
|
|
497
|
+
const text2 = context ? inContext(written, conf) : written;
|
|
498
|
+
if (conf.maxAge <= 0 || operation === "mutation") return text2;
|
|
499
|
+
return `${text2} #${Math.floor(Date.now() / (conf.maxAge * MS_PER_SECOND))}`;
|
|
500
|
+
}
|
|
257
501
|
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
258
502
|
function statusText(status2, conf) {
|
|
259
503
|
if (status2 === 401 || status2 === 403) {
|
|
@@ -265,75 +509,88 @@
|
|
|
265
509
|
}
|
|
266
510
|
return `HTTP ${status2} from ${conf.domain}`;
|
|
267
511
|
}
|
|
512
|
+
function toGraphQLError(value) {
|
|
513
|
+
const message = field(value, "message");
|
|
514
|
+
const code = field(field(value, "extensions"), "code");
|
|
515
|
+
const text2 = typeof message === "string" ? message : "";
|
|
516
|
+
return typeof code === "string" ? { message: text2, extensions: { code } } : { message: text2 };
|
|
517
|
+
}
|
|
518
|
+
function toGraphQLResponse(json) {
|
|
519
|
+
if (!isRecord(json)) return null;
|
|
520
|
+
const { data, errors } = json;
|
|
521
|
+
if (errors === void 0 || errors === null) return { data };
|
|
522
|
+
if (!Array.isArray(errors)) return null;
|
|
523
|
+
return { data, errors: errors.map(toGraphQLError) };
|
|
524
|
+
}
|
|
525
|
+
function invalidResponse(res, conf) {
|
|
526
|
+
return res.ok ? new TetherError(`Invalid response (${res.status})`, { code: "INVALID_RESPONSE", status: res.status }) : new TetherError(statusText(res.status, conf), { status: res.status });
|
|
527
|
+
}
|
|
268
528
|
function graphQLFailure(res, errors, conf) {
|
|
269
529
|
const message = errors.map((error) => error.message).filter(Boolean).join(" | ");
|
|
270
|
-
let
|
|
271
|
-
if (!res.ok)
|
|
272
|
-
return new TetherError(
|
|
530
|
+
let text2 = message || "Storefront API error";
|
|
531
|
+
if (!res.ok) text2 = res.status === 401 || res.status === 403 || !message ? statusText(res.status, conf) : message;
|
|
532
|
+
return new TetherError(text2, { status: res.status, errors });
|
|
533
|
+
}
|
|
534
|
+
function networkFailure(error, retry) {
|
|
535
|
+
if (retry === "any") return { kind: "retry", backoffMs: RETRY_BACKOFF_MS };
|
|
536
|
+
throw new TetherError(`Network error: ${messageOf(error)}`, { code: "NETWORK" });
|
|
273
537
|
}
|
|
274
|
-
async function attempt(conf,
|
|
538
|
+
async function attempt(conf, query2, variables, isData, retry) {
|
|
275
539
|
let res;
|
|
276
540
|
try {
|
|
277
541
|
res = await (conf.fetch ?? fetch)(endpoint(conf), {
|
|
278
542
|
method: "POST",
|
|
279
543
|
headers: { "Content-Type": "application/json", "X-Shopify-Storefront-Access-Token": conf.token },
|
|
280
|
-
body: JSON.stringify({ query, variables })
|
|
544
|
+
body: JSON.stringify({ query: query2, variables })
|
|
281
545
|
});
|
|
282
546
|
} catch (error) {
|
|
283
|
-
|
|
284
|
-
throw new TetherError(`Network error: ${error instanceof Error ? error.message : String(error)}`, {
|
|
285
|
-
code: "NETWORK"
|
|
286
|
-
});
|
|
547
|
+
return networkFailure(error, retry);
|
|
287
548
|
}
|
|
288
549
|
if (res.status === SECURITY_REJECTION) {
|
|
289
550
|
throw new TetherError("Shopify security rejection (430)", { status: SECURITY_REJECTION });
|
|
290
551
|
}
|
|
291
|
-
if (
|
|
552
|
+
if (retry !== "none" && res.status === 429 || retry === "any" && res.status >= 500) {
|
|
292
553
|
return { kind: "retry", backoffMs: RETRY_BACKOFF_MS };
|
|
293
554
|
}
|
|
294
555
|
let json;
|
|
295
556
|
try {
|
|
296
557
|
json = await res.json();
|
|
297
|
-
} catch {
|
|
298
|
-
|
|
299
|
-
|
|
558
|
+
} catch (error) {
|
|
559
|
+
if (error instanceof SyntaxError) throw invalidResponse(res, conf);
|
|
560
|
+
return networkFailure(error, retry);
|
|
300
561
|
}
|
|
301
|
-
const
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
);
|
|
562
|
+
const body = toGraphQLResponse(json);
|
|
563
|
+
if (!body) throw invalidResponse(res, conf);
|
|
564
|
+
const { data, errors = [] } = body;
|
|
305
565
|
if (errors.length) {
|
|
306
|
-
if (
|
|
307
|
-
return { kind: "inventory-refused" };
|
|
566
|
+
if (query2.includes(INVENTORY_FIELD) && errors.some((error) => error.message.includes(INVENTORY_FIELD))) {
|
|
567
|
+
return { kind: "inventory-refused", error: graphQLFailure(res, errors, conf) };
|
|
308
568
|
}
|
|
309
|
-
if (
|
|
569
|
+
if (retry !== "none" && errors.some((error) => error.extensions?.code === "THROTTLED")) {
|
|
310
570
|
return { kind: "retry", backoffMs: THROTTLE_BACKOFF_MS };
|
|
311
571
|
}
|
|
312
572
|
throw graphQLFailure(res, errors, conf);
|
|
313
573
|
}
|
|
314
574
|
if (!res.ok) throw new TetherError(statusText(res.status, conf), { status: res.status });
|
|
575
|
+
if (!isData(data)) throw invalidResponse(res, conf);
|
|
315
576
|
return { kind: "data", data };
|
|
316
577
|
}
|
|
317
|
-
async function request(conf,
|
|
578
|
+
async function request(conf, query2, variables, isData, { context = true, operation = "query" } = {}) {
|
|
318
579
|
if (!conf.domain || !conf.token) {
|
|
319
580
|
throw new TetherError("Tether is not configured: domain and token are required", { code: "NOT_CONFIGURED" });
|
|
320
581
|
}
|
|
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
582
|
const store = storeOf(conf);
|
|
583
|
+
const retryWhileLeft = operation === "mutation" ? "not-run" : "any";
|
|
326
584
|
let retries = 0;
|
|
327
585
|
for (; ; ) {
|
|
328
|
-
const sent = storesWithoutInventory.has(store)
|
|
329
|
-
const
|
|
586
|
+
const sent = queryText(conf, query2, { inventory: !storesWithoutInventory.has(store) }, { context, operation });
|
|
587
|
+
const retry = retries < conf.retries ? retryWhileLeft : "none";
|
|
588
|
+
const outcome = await attempt(conf, sent, variables, isData, retry);
|
|
330
589
|
switch (outcome.kind) {
|
|
331
590
|
case "data":
|
|
332
|
-
return
|
|
333
|
-
/** @type {T} */
|
|
334
|
-
outcome.data
|
|
335
|
-
);
|
|
591
|
+
return outcome.data;
|
|
336
592
|
case "inventory-refused":
|
|
593
|
+
if (typeof query2 === "string") throw outcome.error;
|
|
337
594
|
log(`the token can't read inventory: ${INVENTORY_FIELD} is left out from now on`);
|
|
338
595
|
storesWithoutInventory.add(store);
|
|
339
596
|
break;
|
|
@@ -345,7 +602,14 @@
|
|
|
345
602
|
}
|
|
346
603
|
}
|
|
347
604
|
}
|
|
348
|
-
|
|
605
|
+
function gql(query2, variables, isData, options) {
|
|
606
|
+
return request(cfg, query2, variables, isData, options);
|
|
607
|
+
}
|
|
608
|
+
var hasData = (data) => typeof data === "object" && data !== null;
|
|
609
|
+
function query(text2, variables = {}, options) {
|
|
610
|
+
const operation = operationOf(typeof text2 === "string" ? text2 : text2({ inventory: true }));
|
|
611
|
+
return gql(text2, variables, hasData, { ...options, operation });
|
|
612
|
+
}
|
|
349
613
|
function unwrap(payload, name) {
|
|
350
614
|
if (!payload) throw new TetherError(`${name}: empty response`);
|
|
351
615
|
const userErrors = payload.userErrors ?? [];
|
|
@@ -355,133 +619,237 @@
|
|
|
355
619
|
return payload;
|
|
356
620
|
}
|
|
357
621
|
|
|
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
|
|
622
|
+
// src/markup.ts
|
|
399
623
|
var attr = (name) => `${cfg.prefix}-${name}`;
|
|
400
624
|
var sel = (name, value) => value === void 0 ? `[${attr(name)}]` : `[${attr(name)}="${value}"]`;
|
|
401
625
|
var get = (el, name) => el.getAttribute(attr(name));
|
|
402
626
|
var has = (el, name) => el.hasAttribute(attr(name));
|
|
403
627
|
function qsa(root, selector) {
|
|
404
628
|
if (!root) return [];
|
|
405
|
-
return [
|
|
406
|
-
root.querySelectorAll(selector)];
|
|
629
|
+
return [...root.querySelectorAll(selector)];
|
|
407
630
|
}
|
|
408
631
|
var scoped = (root, selector, scopeSelector) => qsa(root, selector).filter((el) => el.closest(scopeSelector) === root);
|
|
632
|
+
var ANALYTICS_EVENTS = [
|
|
633
|
+
"product-ready",
|
|
634
|
+
"list-ready",
|
|
635
|
+
"cart-updated",
|
|
636
|
+
"cart-toggled",
|
|
637
|
+
"checkout",
|
|
638
|
+
"search"
|
|
639
|
+
];
|
|
640
|
+
var ownEvents = /* @__PURE__ */ new WeakMap();
|
|
641
|
+
function ownEvent(name, detail) {
|
|
642
|
+
const event = new CustomEvent(`${cfg.prefix}:${name}`, { detail, bubbles: true });
|
|
643
|
+
ownEvents.set(event, name);
|
|
644
|
+
return event;
|
|
645
|
+
}
|
|
646
|
+
function isOwnEvent(event, name) {
|
|
647
|
+
return ownEvents.get(event) === name;
|
|
648
|
+
}
|
|
409
649
|
function emit(name, detail, target = document) {
|
|
410
|
-
target.dispatchEvent(
|
|
650
|
+
target.dispatchEvent(ownEvent(name, detail));
|
|
411
651
|
}
|
|
412
652
|
|
|
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"
|
|
653
|
+
// src/store-html.ts
|
|
654
|
+
var HTML_NAMESPACE = "http://www.w3.org/1999/xhtml";
|
|
655
|
+
var ALLOWED_ELEMENTS = /* @__PURE__ */ new Set([
|
|
656
|
+
...["h1", "h2", "h3", "h4", "h5", "h6", "p", "span", "div", "br", "hr", "blockquote", "code", "pre"],
|
|
657
|
+
...["strong", "em", "b", "i", "u", "s", "small", "sub", "sup"],
|
|
658
|
+
...["ul", "ol", "li", "dl", "dt", "dd"],
|
|
659
|
+
...["table", "caption", "colgroup", "col", "thead", "tbody", "tfoot", "tr", "th", "td"],
|
|
660
|
+
...["a", "img", "picture", "source", "video", "figure", "figcaption", "iframe"]
|
|
430
661
|
]);
|
|
431
|
-
var
|
|
432
|
-
var
|
|
433
|
-
var
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
662
|
+
var REMOVED_WITH_CONTENT = /* @__PURE__ */ new Set(["script", "style", "template", "noscript", "textarea", "select", "title"]);
|
|
663
|
+
var GLOBAL_ATTRIBUTES = /* @__PURE__ */ new Set(["style", "title", "lang", "dir", "role", "align", "width", "height"]);
|
|
664
|
+
var ELEMENT_ATTRIBUTES = /* @__PURE__ */ new Map([
|
|
665
|
+
["a", /* @__PURE__ */ new Set(["href", "target", "rel", "hreflang"])],
|
|
666
|
+
["img", /* @__PURE__ */ new Set(["src", "srcset", "sizes", "alt", "loading", "decoding"])],
|
|
667
|
+
["source", /* @__PURE__ */ new Set(["src", "srcset", "sizes", "type", "media"])],
|
|
668
|
+
["video", /* @__PURE__ */ new Set(["src", "poster", "controls", "autoplay", "muted", "loop", "playsinline", "preload"])],
|
|
669
|
+
["iframe", /* @__PURE__ */ new Set(["src", "allow", "allowfullscreen", "frameborder", "loading"])],
|
|
670
|
+
["blockquote", /* @__PURE__ */ new Set(["cite"])],
|
|
671
|
+
["ol", /* @__PURE__ */ new Set(["start", "type", "reversed"])],
|
|
672
|
+
["li", /* @__PURE__ */ new Set(["value"])],
|
|
673
|
+
["table", /* @__PURE__ */ new Set(["border", "cellpadding", "cellspacing", "summary"])],
|
|
674
|
+
["col", /* @__PURE__ */ new Set(["span"])],
|
|
675
|
+
["colgroup", /* @__PURE__ */ new Set(["span"])],
|
|
676
|
+
["th", /* @__PURE__ */ new Set(["colspan", "rowspan", "headers", "scope", "valign"])],
|
|
677
|
+
["td", /* @__PURE__ */ new Set(["colspan", "rowspan", "headers", "valign"])]
|
|
678
|
+
]);
|
|
679
|
+
var URL_ATTRIBUTES = /* @__PURE__ */ new Set(["href", "src", "poster", "cite"]);
|
|
680
|
+
var SCRIPT_URL = /^(javascript|vbscript|data):/i;
|
|
681
|
+
var DATA_IMAGE = /^data:image\/(png|gif|jpeg|webp|avif);/i;
|
|
682
|
+
var OUT_OF_PLACE_PROPERTIES = /* @__PURE__ */ new Set([
|
|
683
|
+
"position",
|
|
684
|
+
"top",
|
|
685
|
+
"right",
|
|
686
|
+
"bottom",
|
|
687
|
+
"left",
|
|
688
|
+
"z-index",
|
|
689
|
+
"-webkit-box-reflect"
|
|
690
|
+
]);
|
|
691
|
+
var OUT_OF_PLACE_PREFIXES = ["inset", "transform", "translate", "rotate", "scale", "offset"];
|
|
692
|
+
var VIEWPORT_LENGTH = /\d(?:[sld]?v|cq)(?:w|h|i|b|min|max)\b/i;
|
|
693
|
+
var PAINTS_BEYOND = /^(?:outline|box-shadow|text-shadow|filter|text-decoration|text-underline-offset|text-stroke)|^(?:border-image|mask-border|mask-box-image)(?:-outset)?$/;
|
|
694
|
+
var MAX_PAINT_PX = 40;
|
|
695
|
+
var NEGATIVE_LIMITS = /* @__PURE__ */ new Map([
|
|
696
|
+
["letter-spacing", { px: 4, em: 0.25 }],
|
|
697
|
+
["word-spacing", { px: 4, em: 0.25 }],
|
|
698
|
+
["text-indent", { px: MAX_PAINT_PX }]
|
|
699
|
+
]);
|
|
700
|
+
var NUMBER = /(?<![\w#.-])[+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?(%|[a-z]+)?/gi;
|
|
701
|
+
var NON_LENGTH_FUNCTION = /(?<![\w-])(?!drop-shadow\(|blur\()[\w-]+\([^()]*\)/g;
|
|
702
|
+
var COMPUTED_VALUE = /(?:var|calc|min|max|clamp|env|attr)\(/i;
|
|
703
|
+
var FETCHING_VALUE = /url\(|expression\(|@import/i;
|
|
704
|
+
var IGNORED_IN_SCHEME = /[\u0000- ]/g;
|
|
705
|
+
var IFRAME_SANDBOX = "allow-scripts allow-same-origin allow-popups allow-presentation";
|
|
706
|
+
var IFRAME_FEATURES = /* @__PURE__ */ new Set([
|
|
707
|
+
...["autoplay", "encrypted-media", "picture-in-picture", "fullscreen", "clipboard-write"],
|
|
708
|
+
...["accelerometer", "gyroscope", "web-share"]
|
|
709
|
+
]);
|
|
710
|
+
var IFRAME_REFERRER_POLICY = "strict-origin-when-cross-origin";
|
|
711
|
+
var EMBED_ORIGINS = [
|
|
712
|
+
"https://www.youtube.com",
|
|
713
|
+
"https://www.youtube-nocookie.com",
|
|
714
|
+
"https://player.vimeo.com",
|
|
715
|
+
"https://www.loom.com",
|
|
716
|
+
"https://fast.wistia.net"
|
|
717
|
+
];
|
|
718
|
+
function isAllowedAttribute(tag, name) {
|
|
719
|
+
return GLOBAL_ATTRIBUTES.has(name) || name.startsWith("aria-") || (ELEMENT_ATTRIBUTES.get(tag)?.has(name) ?? false);
|
|
720
|
+
}
|
|
721
|
+
function isSafeUrl(tag, name, value) {
|
|
722
|
+
const url2 = value.replace(IGNORED_IN_SCHEME, "");
|
|
723
|
+
if (!SCRIPT_URL.test(url2)) return true;
|
|
724
|
+
return tag === "img" && name === "src" && DATA_IMAGE.test(url2);
|
|
725
|
+
}
|
|
726
|
+
function urlOrNull(value, base) {
|
|
727
|
+
try {
|
|
728
|
+
return new URL(value, base);
|
|
729
|
+
} catch {
|
|
730
|
+
return null;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
function pageOf(document2, styleParser, embedOrigins) {
|
|
734
|
+
const baseUrl = document2.baseURI;
|
|
735
|
+
const origin = urlOrNull(baseUrl)?.origin ?? "null";
|
|
736
|
+
return {
|
|
737
|
+
origin: origin === "null" ? null : origin,
|
|
738
|
+
baseUrl,
|
|
739
|
+
styleParser,
|
|
740
|
+
embedOrigins: /* @__PURE__ */ new Set([...EMBED_ORIGINS, ...embedOrigins])
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
function isAllowedEmbed(value, page) {
|
|
744
|
+
const url2 = urlOrNull(value.trim(), page.baseUrl);
|
|
745
|
+
return url2 !== null && url2.origin !== page.origin && page.embedOrigins.has(url2.origin);
|
|
746
|
+
}
|
|
747
|
+
function allowedFeatures(value) {
|
|
748
|
+
const features = value.split(";").map((directive) => directive.trim().split(/\s+/)[0]?.toLowerCase() ?? "");
|
|
749
|
+
return [...new Set(features.filter((feature) => IFRAME_FEATURES.has(feature)))].join("; ");
|
|
750
|
+
}
|
|
751
|
+
function isOutOfPlace(property, value) {
|
|
752
|
+
if (OUT_OF_PLACE_PROPERTIES.has(property) || OUT_OF_PLACE_PREFIXES.some((prefix) => property.startsWith(prefix))) {
|
|
753
|
+
return true;
|
|
754
|
+
}
|
|
755
|
+
if (VIEWPORT_LENGTH.test(value)) return true;
|
|
756
|
+
const limits = NEGATIVE_LIMITS.get(property);
|
|
757
|
+
if (limits) return COMPUTED_VALUE.test(value) || movesBackTooFar(value, limits);
|
|
758
|
+
return property.startsWith("margin") && value.includes("-");
|
|
759
|
+
}
|
|
760
|
+
function movesBackTooFar(value, limits) {
|
|
761
|
+
return [...value.matchAll(NUMBER)].some(([number = "", unit = ""]) => {
|
|
762
|
+
const amount = parseFloat(number);
|
|
763
|
+
if (amount >= 0) return false;
|
|
764
|
+
const limit = limits[unit.toLowerCase()];
|
|
765
|
+
return limit === void 0 || -amount > limit;
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
function paintsTooFar(property, value) {
|
|
769
|
+
if (!PAINTS_BEYOND.test(property.replace(/^-webkit-/, ""))) return false;
|
|
770
|
+
if (COMPUTED_VALUE.test(value)) return true;
|
|
771
|
+
return [...value.replace(NON_LENGTH_FUNCTION, "").matchAll(NUMBER)].some(([number = "", unit = ""]) => {
|
|
772
|
+
const amount = Math.abs(parseFloat(number));
|
|
773
|
+
return amount !== 0 && (unit.toLowerCase() !== "px" || amount > MAX_PAINT_PX);
|
|
774
|
+
});
|
|
775
|
+
}
|
|
776
|
+
function cleanStyle(value, styleParser) {
|
|
777
|
+
const style = styleParser.style;
|
|
778
|
+
style.cssText = value;
|
|
779
|
+
const properties = Array.from({ length: style.length }, (_, index) => style.item(index));
|
|
780
|
+
for (const property of properties) {
|
|
781
|
+
const value2 = style.getPropertyValue(property);
|
|
782
|
+
const remove = isOutOfPlace(property, value2) || paintsTooFar(property, value2) || FETCHING_VALUE.test(value2);
|
|
783
|
+
if (remove) style.removeProperty(property);
|
|
784
|
+
}
|
|
785
|
+
const cleaned = style.cssText;
|
|
786
|
+
styleParser.removeAttribute("style");
|
|
787
|
+
return cleaned;
|
|
788
|
+
}
|
|
789
|
+
function cleanAttributes(element, page) {
|
|
790
|
+
const tag = element.localName;
|
|
791
|
+
for (const attribute of [...element.attributes]) {
|
|
792
|
+
const name = attribute.name.toLowerCase();
|
|
793
|
+
const isUrl = URL_ATTRIBUTES.has(name);
|
|
794
|
+
const keep = isAllowedAttribute(tag, name) && (!isUrl || isSafeUrl(tag, name, attribute.value));
|
|
795
|
+
if (!keep) element.removeAttribute(attribute.name);
|
|
796
|
+
}
|
|
797
|
+
const style = element.getAttribute("style");
|
|
798
|
+
if (style === null) return;
|
|
799
|
+
const cleaned = cleanStyle(style, page.styleParser);
|
|
800
|
+
if (cleaned === "") element.removeAttribute("style");
|
|
801
|
+
else element.setAttribute("style", cleaned);
|
|
802
|
+
}
|
|
803
|
+
function hardenEmbedsAndLinks(element) {
|
|
804
|
+
if (element.localName === "iframe") {
|
|
805
|
+
const allow = allowedFeatures(element.getAttribute("allow") ?? "");
|
|
806
|
+
if (allow === "") element.removeAttribute("allow");
|
|
807
|
+
else element.setAttribute("allow", allow);
|
|
808
|
+
element.setAttribute("sandbox", IFRAME_SANDBOX);
|
|
809
|
+
element.setAttribute("referrerpolicy", IFRAME_REFERRER_POLICY);
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
if (element.localName !== "a" || element.getAttribute("target")?.toLowerCase() !== "_blank") return;
|
|
813
|
+
const rel = new Set((element.getAttribute("rel") ?? "").split(/\s+/).filter(Boolean));
|
|
814
|
+
rel.add("noopener");
|
|
815
|
+
rel.add("noreferrer");
|
|
816
|
+
element.setAttribute("rel", [...rel].join(" "));
|
|
817
|
+
}
|
|
818
|
+
function cleanElement(element, page) {
|
|
819
|
+
const tag = element.localName;
|
|
820
|
+
const isHtml = element.namespaceURI === HTML_NAMESPACE;
|
|
821
|
+
if (isHtml && REMOVED_WITH_CONTENT.has(tag)) {
|
|
822
|
+
element.remove();
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
if (!isHtml || !ALLOWED_ELEMENTS.has(tag)) {
|
|
826
|
+
element.replaceWith(...element.childNodes);
|
|
827
|
+
return;
|
|
828
|
+
}
|
|
829
|
+
if (tag === "iframe" && !isAllowedEmbed(element.getAttribute("src") ?? "", page)) {
|
|
830
|
+
element.remove();
|
|
831
|
+
return;
|
|
451
832
|
}
|
|
833
|
+
cleanAttributes(element, page);
|
|
834
|
+
hardenEmbedsAndLinks(element);
|
|
452
835
|
}
|
|
453
|
-
function storeHtmlFragment(html, document2 = globalThis.document) {
|
|
836
|
+
function storeHtmlFragment(html, document2 = globalThis.document, embedOrigins = []) {
|
|
454
837
|
const template = document2.createElement("template");
|
|
455
838
|
template.innerHTML = html ?? "";
|
|
456
|
-
|
|
839
|
+
const page = pageOf(document2, template, embedOrigins);
|
|
840
|
+
for (const element of [...template.content.querySelectorAll("*")]) cleanElement(element, page);
|
|
457
841
|
return template.content;
|
|
458
842
|
}
|
|
459
843
|
|
|
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;
|
|
844
|
+
// src/shopify-values.ts
|
|
845
|
+
function isHttpsUrl(url2) {
|
|
846
|
+
if (typeof url2 !== "string") return false;
|
|
476
847
|
try {
|
|
477
|
-
return new URL(
|
|
848
|
+
return new URL(url2).protocol === "https:";
|
|
478
849
|
} catch {
|
|
479
850
|
return false;
|
|
480
851
|
}
|
|
481
852
|
}
|
|
482
|
-
function isSecretShopifyToken(token) {
|
|
483
|
-
return /^shp[a-z]{2}_/.test(token.trim());
|
|
484
|
-
}
|
|
485
853
|
function formatMoney(money, locale) {
|
|
486
854
|
if (!money) return "";
|
|
487
855
|
const amount = parseFloat(money.amount);
|
|
@@ -495,8 +863,12 @@
|
|
|
495
863
|
}
|
|
496
864
|
}
|
|
497
865
|
function productUrl(productPath, handle, id) {
|
|
498
|
-
if (!/\{(handle|id)\}/.test(productPath))
|
|
499
|
-
|
|
866
|
+
if (!/\{(handle|id)\}/.test(productPath)) {
|
|
867
|
+
return handle ? `${productPath.replace(/\/$/, "")}/${encodeURIComponent(handle)}` : null;
|
|
868
|
+
}
|
|
869
|
+
const numericId2 = id ? id.split("/").at(-1) ?? "" : "";
|
|
870
|
+
if (productPath.includes("{handle}") && !handle) return null;
|
|
871
|
+
if (productPath.includes("{id}") && !numericId2) return null;
|
|
500
872
|
return productPath.replace(/\{handle\}/g, encodeURIComponent(handle ?? "")).replace(/\{id\}/g, encodeURIComponent(numericId2));
|
|
501
873
|
}
|
|
502
874
|
function productKey(raw) {
|
|
@@ -507,43 +879,44 @@
|
|
|
507
879
|
return /\/products\/([^/?#]+)/.exec(value)?.[1] ?? value;
|
|
508
880
|
}
|
|
509
881
|
|
|
510
|
-
// src/images.
|
|
511
|
-
function imageUrl(
|
|
512
|
-
if (!
|
|
882
|
+
// src/images.ts
|
|
883
|
+
function imageUrl(url2, { width, height, crop } = {}) {
|
|
884
|
+
if (!url2) return url2;
|
|
513
885
|
let parsed;
|
|
514
886
|
try {
|
|
515
|
-
parsed = new URL(
|
|
887
|
+
parsed = new URL(url2);
|
|
516
888
|
} catch (error) {
|
|
517
|
-
if (error instanceof TypeError) return
|
|
889
|
+
if (error instanceof TypeError) return url2;
|
|
518
890
|
throw error;
|
|
519
891
|
}
|
|
520
892
|
if (width) parsed.searchParams.set("width", String(width));
|
|
521
893
|
if (height) parsed.searchParams.set("height", String(height));
|
|
522
894
|
if (crop) parsed.searchParams.set("crop", crop);
|
|
523
|
-
return (
|
|
524
|
-
/** @type {Url} */
|
|
525
|
-
parsed.toString()
|
|
526
|
-
);
|
|
895
|
+
return parsed.toString();
|
|
527
896
|
}
|
|
528
897
|
function sizeAttribute(el, name) {
|
|
529
898
|
return el.getAttribute(name) ?? el.getAttribute(attr(name)) ?? el.getAttribute(`data-${name}`);
|
|
530
899
|
}
|
|
531
|
-
function cdnUrl(
|
|
532
|
-
if (!
|
|
900
|
+
function cdnUrl(url2, el) {
|
|
901
|
+
if (!url2 || !el) return url2;
|
|
533
902
|
const width = sizeAttribute(el, "max-width");
|
|
534
903
|
const height = sizeAttribute(el, "max-height");
|
|
535
|
-
return imageUrl(
|
|
904
|
+
return imageUrl(url2, { width, height, crop: width || height ? sizeAttribute(el, "crop") : null });
|
|
536
905
|
}
|
|
537
|
-
var
|
|
538
|
-
function
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
906
|
+
var withoutQuery = (url2) => url2.replace(/\?.*$/, "");
|
|
907
|
+
function sameImage(a, b) {
|
|
908
|
+
if (!a || !b) return false;
|
|
909
|
+
return withoutQuery(a) === withoutQuery(b);
|
|
910
|
+
}
|
|
911
|
+
function cssUrl(url2) {
|
|
912
|
+
const hasScheme = /^\s*[a-z][a-z0-9+.-]*:/i.test(url2);
|
|
913
|
+
if (hasScheme && !/^\s*(https?:|data:image\/)/i.test(url2)) return "none";
|
|
914
|
+
const escaped = url2.replace(/["\\\n\r\f]/g, (char) => `\\${char.charCodeAt(0).toString(16)} `);
|
|
542
915
|
return `url("${escaped}")`;
|
|
543
916
|
}
|
|
544
|
-
function setImage(el,
|
|
545
|
-
if (!
|
|
546
|
-
const src = cdnUrl(
|
|
917
|
+
function setImage(el, url2, alt) {
|
|
918
|
+
if (!url2) return;
|
|
919
|
+
const src = cdnUrl(url2, el);
|
|
547
920
|
if (el.tagName !== "IMG") {
|
|
548
921
|
el.style.backgroundImage = cssUrl(src);
|
|
549
922
|
return;
|
|
@@ -559,21 +932,15 @@
|
|
|
559
932
|
if (!el.getAttribute("loading")) el.setAttribute("loading", "lazy");
|
|
560
933
|
}
|
|
561
934
|
|
|
562
|
-
// src/repeaters.
|
|
935
|
+
// src/repeaters.ts
|
|
563
936
|
var templates = /* @__PURE__ */ new WeakMap();
|
|
564
937
|
var hasTemplate = (container) => Boolean(templates.get(container));
|
|
565
|
-
var copyOf = (template) => (
|
|
566
|
-
/** @type {HTMLElement} */
|
|
567
|
-
template.cloneNode(true)
|
|
568
|
-
);
|
|
938
|
+
var copyOf = (template) => template.cloneNode(true);
|
|
569
939
|
function templateOf(container) {
|
|
570
940
|
const known = templates.get(container);
|
|
571
941
|
if (known !== void 0) return known;
|
|
572
942
|
const first = container.firstElementChild;
|
|
573
|
-
const template = first ? copyOf(
|
|
574
|
-
/** @type {HTMLElement} */
|
|
575
|
-
first
|
|
576
|
-
) : null;
|
|
943
|
+
const template = first ? copyOf(first) : null;
|
|
577
944
|
templates.set(container, template);
|
|
578
945
|
container.replaceChildren();
|
|
579
946
|
return template;
|
|
@@ -628,8 +995,7 @@
|
|
|
628
995
|
}
|
|
629
996
|
const keyAttribute = `data-${cfg.prefix}-key`;
|
|
630
997
|
const leavingAttribute = `data-${cfg.prefix}-leaving`;
|
|
631
|
-
const live = () => [
|
|
632
|
-
container.children].filter(
|
|
998
|
+
const live = () => [...container.children].filter(
|
|
633
999
|
(node) => node.hasAttribute(keyAttribute) && !node.hasAttribute(leavingAttribute)
|
|
634
1000
|
);
|
|
635
1001
|
const existing = new Map(live().map((node) => [node.getAttribute(keyAttribute), node]));
|
|
@@ -667,7 +1033,7 @@
|
|
|
667
1033
|
});
|
|
668
1034
|
}
|
|
669
1035
|
|
|
670
|
-
// src/dom.
|
|
1036
|
+
// src/dom.ts
|
|
671
1037
|
var SEEN = "data-tether-seen";
|
|
672
1038
|
var SHOWN = "data-tether-shown";
|
|
673
1039
|
var PREVIOUS_DISPLAY = "data-tether-prev-display";
|
|
@@ -698,14 +1064,14 @@
|
|
|
698
1064
|
return formatOr(() => new Date(iso).toLocaleDateString(cfg.locale || void 0), "");
|
|
699
1065
|
}
|
|
700
1066
|
function setStoreHtml(el, html) {
|
|
701
|
-
el.replaceChildren(storeHtmlFragment(html, el.ownerDocument));
|
|
1067
|
+
el.replaceChildren(storeHtmlFragment(html, el.ownerDocument, cfg.embedOrigins));
|
|
702
1068
|
}
|
|
703
1069
|
var isTextField = (el) => el.tagName === "INPUT" || el.tagName === "TEXTAREA";
|
|
704
1070
|
var isInput = (el) => el.tagName === "INPUT";
|
|
705
1071
|
function setText(el, value) {
|
|
706
|
-
const
|
|
707
|
-
if (isTextField(el)) el.value =
|
|
708
|
-
else el.textContent =
|
|
1072
|
+
const text2 = value == null ? "" : String(value);
|
|
1073
|
+
if (isTextField(el)) el.value = text2;
|
|
1074
|
+
else el.textContent = text2;
|
|
709
1075
|
}
|
|
710
1076
|
var readText = (el) => isTextField(el) ? el.value : el.textContent;
|
|
711
1077
|
function setTextTracked(el, value) {
|
|
@@ -754,15 +1120,22 @@
|
|
|
754
1120
|
if (input && isInput(input)) input.checked = selected;
|
|
755
1121
|
}
|
|
756
1122
|
var layoutTimer;
|
|
1123
|
+
var pageGlobal = (name) => Reflect.get(window, name);
|
|
1124
|
+
function refreshScrollTrigger() {
|
|
1125
|
+
const scrollTrigger = pageGlobal("ScrollTrigger");
|
|
1126
|
+
if (typeof scrollTrigger !== "function") return;
|
|
1127
|
+
const refresh = Reflect.get(scrollTrigger, "refresh");
|
|
1128
|
+
if (typeof refresh === "function") Reflect.apply(refresh, scrollTrigger, []);
|
|
1129
|
+
}
|
|
757
1130
|
function notifyLayoutChanged(reason) {
|
|
758
1131
|
clearTimeout(layoutTimer);
|
|
759
1132
|
layoutTimer = setTimeout(() => {
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
)
|
|
764
|
-
|
|
765
|
-
|
|
1133
|
+
try {
|
|
1134
|
+
if (cfg.onLayoutChange) cfg.onLayoutChange(reason);
|
|
1135
|
+
else refreshScrollTrigger();
|
|
1136
|
+
} catch (error) {
|
|
1137
|
+
reportError(error, "layout-changed");
|
|
1138
|
+
}
|
|
766
1139
|
emit("layout-changed", { reason });
|
|
767
1140
|
}, LAYOUT_DEBOUNCE_MS);
|
|
768
1141
|
}
|
|
@@ -789,15 +1162,27 @@
|
|
|
789
1162
|
const raw = el ? get(el, name) : null;
|
|
790
1163
|
if (!raw) return null;
|
|
791
1164
|
try {
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
JSON.parse(raw)
|
|
795
|
-
);
|
|
1165
|
+
const json = JSON.parse(raw);
|
|
1166
|
+
return json;
|
|
796
1167
|
} catch {
|
|
797
1168
|
warn(`invalid JSON in ${attr(name)}`, el);
|
|
798
1169
|
return null;
|
|
799
1170
|
}
|
|
800
1171
|
}
|
|
1172
|
+
function attributeText(value) {
|
|
1173
|
+
if (value === null || value === void 0) return "";
|
|
1174
|
+
if (typeof value === "string") return value;
|
|
1175
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
1176
|
+
return JSON.stringify(value);
|
|
1177
|
+
}
|
|
1178
|
+
function attributesFromJson(json) {
|
|
1179
|
+
if (Array.isArray(json)) {
|
|
1180
|
+
const entries = json;
|
|
1181
|
+
return entries.filter(isRecord).map((entry) => ({ key: attributeText(entry["key"]), value: attributeText(entry["value"]) }));
|
|
1182
|
+
}
|
|
1183
|
+
if (!isRecord(json)) return [];
|
|
1184
|
+
return Object.entries(json).map(([key, value]) => ({ key, value: attributeText(value) }));
|
|
1185
|
+
}
|
|
801
1186
|
function toAttributes(attributes) {
|
|
802
1187
|
if (!attributes) return [];
|
|
803
1188
|
if (Array.isArray(attributes)) {
|
|
@@ -806,14 +1191,10 @@
|
|
|
806
1191
|
return Object.entries(attributes).map(([key, value]) => ({ key, value: String(value ?? "") }));
|
|
807
1192
|
}
|
|
808
1193
|
|
|
809
|
-
// src/fragments.
|
|
810
|
-
var fragments_exports = {};
|
|
811
|
-
__export(fragments_exports, {
|
|
812
|
-
cartFragment: () => cartFragment,
|
|
813
|
-
collectionFields: () => collectionFields,
|
|
814
|
-
productFragment: () => productFragment
|
|
815
|
-
});
|
|
1194
|
+
// src/fragments.ts
|
|
816
1195
|
var MONEY = "amount currencyCode";
|
|
1196
|
+
var FULL_ACCESS = { inventory: true };
|
|
1197
|
+
var inventoryField = (access) => access.inventory ? "quantityAvailable" : "";
|
|
817
1198
|
var IMAGE = "url altText width height";
|
|
818
1199
|
function metafieldsField(metafields) {
|
|
819
1200
|
const identifiers = [];
|
|
@@ -827,11 +1208,11 @@
|
|
|
827
1208
|
}
|
|
828
1209
|
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
1210
|
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
|
|
1211
|
+
function productFragment(o = cfg, access = FULL_ACCESS) {
|
|
1212
|
+
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
1213
|
}
|
|
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
|
|
1214
|
+
function cartFragment(access = FULL_ACCESS) {
|
|
1215
|
+
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
1216
|
}
|
|
836
1217
|
function collectionFields() {
|
|
837
1218
|
return `
|
|
@@ -840,7 +1221,7 @@
|
|
|
840
1221
|
seo { title description }`;
|
|
841
1222
|
}
|
|
842
1223
|
|
|
843
|
-
// src/selling-plans.
|
|
1224
|
+
// src/selling-plans.ts
|
|
844
1225
|
var selling_plans_exports = {};
|
|
845
1226
|
__export(selling_plans_exports, {
|
|
846
1227
|
GID: () => GID,
|
|
@@ -855,19 +1236,59 @@
|
|
|
855
1236
|
plansForVariant: () => plansForVariant,
|
|
856
1237
|
toGid: () => toGid
|
|
857
1238
|
});
|
|
1239
|
+
|
|
1240
|
+
// src/amounts.ts
|
|
1241
|
+
var DECIMAL = /^(-?)(\d+)(?:\.(\d+))?$/;
|
|
1242
|
+
function toScaledInteger(amount, fractionDigits) {
|
|
1243
|
+
const match = DECIMAL.exec(amount.trim());
|
|
1244
|
+
if (!match) return null;
|
|
1245
|
+
const [, sign = "", whole = "0", fraction = ""] = match;
|
|
1246
|
+
const digits = BigInt(`${whole}${fraction.padEnd(fractionDigits, "0")}`);
|
|
1247
|
+
return sign === "-" ? -digits : digits;
|
|
1248
|
+
}
|
|
1249
|
+
var fractionLength = (amount) => amount.split(".")[1]?.length ?? 0;
|
|
1250
|
+
function isGreaterAmount(amount, other) {
|
|
1251
|
+
if (amount == null || other == null) return false;
|
|
1252
|
+
const fractionDigits = Math.max(fractionLength(amount.trim()), fractionLength(other.trim()));
|
|
1253
|
+
const scaled = toScaledInteger(amount, fractionDigits);
|
|
1254
|
+
const scaledOther = toScaledInteger(other, fractionDigits);
|
|
1255
|
+
if (scaled === null || scaledOther === null) return false;
|
|
1256
|
+
return scaled > scaledOther;
|
|
1257
|
+
}
|
|
1258
|
+
var DEFAULT_FRACTION_DIGITS = 2;
|
|
1259
|
+
var fractionDigitsByCurrency = /* @__PURE__ */ new Map();
|
|
1260
|
+
function currencyFractionDigits(currencyCode) {
|
|
1261
|
+
const known = fractionDigitsByCurrency.get(currencyCode);
|
|
1262
|
+
if (known !== void 0) return known;
|
|
1263
|
+
let digits = DEFAULT_FRACTION_DIGITS;
|
|
1264
|
+
try {
|
|
1265
|
+
const format = new Intl.NumberFormat("en", { style: "currency", currency: currencyCode });
|
|
1266
|
+
digits = format.resolvedOptions().maximumFractionDigits ?? DEFAULT_FRACTION_DIGITS;
|
|
1267
|
+
} catch {
|
|
1268
|
+
}
|
|
1269
|
+
fractionDigitsByCurrency.set(currencyCode, digits);
|
|
1270
|
+
return digits;
|
|
1271
|
+
}
|
|
1272
|
+
function toMinorUnits(money) {
|
|
1273
|
+
return Math.round(parseFloat(money.amount) * 10 ** currencyFractionDigits(money.currencyCode));
|
|
1274
|
+
}
|
|
1275
|
+
function fromMinorUnits(minorUnits, currencyCode) {
|
|
1276
|
+
const digits = currencyFractionDigits(currencyCode);
|
|
1277
|
+
return { amount: (minorUnits / 10 ** digits).toFixed(digits), currencyCode };
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
// src/selling-plans.ts
|
|
1281
|
+
var PERCENT = 100;
|
|
858
1282
|
function toGid(value, type) {
|
|
859
|
-
const
|
|
860
|
-
if (!
|
|
861
|
-
if (
|
|
862
|
-
if (/^\d+$/.test(
|
|
863
|
-
return
|
|
1283
|
+
const text2 = typeof value === "string" || typeof value === "number" ? String(value).trim() : "";
|
|
1284
|
+
if (!text2) return null;
|
|
1285
|
+
if (text2.startsWith("gid://")) return text2;
|
|
1286
|
+
if (/^\d+$/.test(text2)) return `gid://shopify/${type}/${text2}`;
|
|
1287
|
+
return text2;
|
|
864
1288
|
}
|
|
865
1289
|
var GID = {
|
|
866
|
-
/** @param {unknown} value */
|
|
867
1290
|
product: (value) => toGid(value, "Product"),
|
|
868
|
-
/** @param {unknown} value */
|
|
869
1291
|
variant: (value) => toGid(value, "ProductVariant"),
|
|
870
|
-
/** @param {unknown} value */
|
|
871
1292
|
sellingPlan: (value) => toGid(value, "SellingPlan")
|
|
872
1293
|
};
|
|
873
1294
|
function allPlans(product) {
|
|
@@ -894,14 +1315,17 @@
|
|
|
894
1315
|
return allocation?.checkoutChargeAmount ?? allocation?.priceAdjustments[0]?.price ?? variant?.price ?? null;
|
|
895
1316
|
}
|
|
896
1317
|
function planSavings(variant, planId) {
|
|
897
|
-
const
|
|
898
|
-
|
|
899
|
-
|
|
1318
|
+
const onPlanPrice = planPrice(variant, planId);
|
|
1319
|
+
if (!variant || !onPlanPrice) return { amount: null, percentage: 0 };
|
|
1320
|
+
const oneTime = parseFloat(variant.price.amount);
|
|
1321
|
+
const onPlan = parseFloat(onPlanPrice.amount);
|
|
1322
|
+
if (!Number.isFinite(oneTime) || !Number.isFinite(onPlan) || oneTime <= 0 || onPlan >= oneTime) {
|
|
900
1323
|
return { amount: null, percentage: 0 };
|
|
901
1324
|
}
|
|
1325
|
+
const saved = toMinorUnits(variant.price) - toMinorUnits(onPlanPrice);
|
|
902
1326
|
return {
|
|
903
|
-
amount:
|
|
904
|
-
percentage: Math.round((1 - onPlan / oneTime) *
|
|
1327
|
+
amount: fromMinorUnits(saved, variant.price.currencyCode),
|
|
1328
|
+
percentage: Math.round((1 - onPlan / oneTime) * PERCENT)
|
|
905
1329
|
};
|
|
906
1330
|
}
|
|
907
1331
|
function planAdjustment(plan) {
|
|
@@ -926,7 +1350,43 @@
|
|
|
926
1350
|
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
1351
|
}
|
|
928
1352
|
|
|
929
|
-
// src/
|
|
1353
|
+
// src/product-shape.ts
|
|
1354
|
+
var MONEY2 = { amount: "string", currencyCode: "string" };
|
|
1355
|
+
var PRODUCT = {
|
|
1356
|
+
id: "string",
|
|
1357
|
+
handle: "string",
|
|
1358
|
+
title: "string",
|
|
1359
|
+
options: ["object"],
|
|
1360
|
+
images: { nodes: ["object"] },
|
|
1361
|
+
priceRange: { minVariantPrice: MONEY2, maxVariantPrice: MONEY2 },
|
|
1362
|
+
variants: { nodes: [{ id: "string", price: MONEY2, selectedOptions: ["object"] }] }
|
|
1363
|
+
};
|
|
1364
|
+
var PRODUCT_CONNECTION = {
|
|
1365
|
+
nodes: [PRODUCT],
|
|
1366
|
+
pageInfo: { hasNextPage: "boolean" }
|
|
1367
|
+
};
|
|
1368
|
+
var isProductShape = (value) => field(value, "id") !== "" && field(value, "handle") !== "" && matches(value, PRODUCT);
|
|
1369
|
+
var PRODUCT_NODE = (node) => isRecord(node) && (node["id"] === void 0 || matches(node, PRODUCT));
|
|
1370
|
+
|
|
1371
|
+
// src/cart/cart-shape.ts
|
|
1372
|
+
var LINE = {
|
|
1373
|
+
id: "string",
|
|
1374
|
+
quantity: "number",
|
|
1375
|
+
attributes: ["object"],
|
|
1376
|
+
cost: { totalAmount: MONEY2, subtotalAmount: MONEY2, amountPerQuantity: MONEY2 },
|
|
1377
|
+
merchandise: { id: "string", selectedOptions: ["object"], product: "object" }
|
|
1378
|
+
};
|
|
1379
|
+
var CART = {
|
|
1380
|
+
id: "string",
|
|
1381
|
+
checkoutUrl: "string",
|
|
1382
|
+
totalQuantity: "number",
|
|
1383
|
+
attributes: ["object"],
|
|
1384
|
+
discountCodes: ["object"],
|
|
1385
|
+
cost: { totalAmount: MONEY2, subtotalAmount: MONEY2 },
|
|
1386
|
+
lines: { nodes: [LINE] }
|
|
1387
|
+
};
|
|
1388
|
+
|
|
1389
|
+
// src/cart/drawer.ts
|
|
930
1390
|
var FOCUSABLE = 'a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])';
|
|
931
1391
|
var FOCUS_AFTER_OPEN_MS = 60;
|
|
932
1392
|
var openWithoutDrawer = false;
|
|
@@ -1014,19 +1474,7 @@
|
|
|
1014
1474
|
(event.shiftKey ? last : first).focus();
|
|
1015
1475
|
}
|
|
1016
1476
|
|
|
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
|
-
});
|
|
1477
|
+
// src/loader.ts
|
|
1030
1478
|
var MISS_CACHE_MS = 1e4;
|
|
1031
1479
|
var FRESH_MS = 6e4;
|
|
1032
1480
|
var productCache = /* @__PURE__ */ new Map();
|
|
@@ -1060,6 +1508,7 @@
|
|
|
1060
1508
|
productCache.set(key, loading);
|
|
1061
1509
|
return loading;
|
|
1062
1510
|
}
|
|
1511
|
+
var withProductFragment = (operation) => (access) => `${operation}${productFragment(cfg, access)}`;
|
|
1063
1512
|
function batchQuery(keys) {
|
|
1064
1513
|
const ids = keys.filter(isGid);
|
|
1065
1514
|
const handles = keys.filter((key) => !isGid(key));
|
|
@@ -1076,8 +1525,8 @@
|
|
|
1076
1525
|
variables[`h${index}`] = handle;
|
|
1077
1526
|
selections.push(`h${index}: product(handle: $h${index}) { ...TetherProduct }`);
|
|
1078
1527
|
});
|
|
1079
|
-
const
|
|
1080
|
-
return { query, variables, handles };
|
|
1528
|
+
const query2 = withProductFragment(`query TetherProducts(${definitions.join(",")}){${selections.join(" ")}}`);
|
|
1529
|
+
return { query: query2, variables, handles };
|
|
1081
1530
|
}
|
|
1082
1531
|
function settleWaiting(key, settle) {
|
|
1083
1532
|
const waiter = waiting.get(key);
|
|
@@ -1091,16 +1540,21 @@
|
|
|
1091
1540
|
if (productCache.get(key) === miss) productCache.delete(key);
|
|
1092
1541
|
}, MISS_CACHE_MS);
|
|
1093
1542
|
}
|
|
1543
|
+
function isBatchData(data) {
|
|
1544
|
+
if (!isRecord(data)) return false;
|
|
1545
|
+
const { byId = [], ...byHandle } = data;
|
|
1546
|
+
return matches(byId, [orNull(PRODUCT_NODE)]) && Object.values(byHandle).every(orNull(PRODUCT));
|
|
1547
|
+
}
|
|
1094
1548
|
async function flush() {
|
|
1095
1549
|
flushScheduled = false;
|
|
1096
1550
|
const batchGeneration = generation;
|
|
1097
1551
|
const keys = pending.splice(0);
|
|
1098
1552
|
if (!keys.length) return;
|
|
1099
|
-
const { query, variables, handles } = batchQuery(keys);
|
|
1553
|
+
const { query: query2, variables, handles } = batchQuery(keys);
|
|
1100
1554
|
log("fetch products", keys);
|
|
1101
1555
|
let data;
|
|
1102
1556
|
try {
|
|
1103
|
-
data = await gql(
|
|
1557
|
+
data = await gql(query2, variables, isBatchData);
|
|
1104
1558
|
} catch (error) {
|
|
1105
1559
|
keys.forEach((key) => {
|
|
1106
1560
|
settleWaiting(key, (waiter) => {
|
|
@@ -1111,7 +1565,7 @@
|
|
|
1111
1565
|
return;
|
|
1112
1566
|
}
|
|
1113
1567
|
const found = /* @__PURE__ */ new Map();
|
|
1114
|
-
for (const product of data.byId ?? []) if (product) found.set(product.id, product);
|
|
1568
|
+
for (const product of data.byId ?? []) if (product?.id) found.set(product.id, product);
|
|
1115
1569
|
handles.forEach((handle, index) => {
|
|
1116
1570
|
const product = data[`h${index}`];
|
|
1117
1571
|
if (product) found.set(handle, product);
|
|
@@ -1157,51 +1611,57 @@
|
|
|
1157
1611
|
void productCache.get(key)?.then((product) => {
|
|
1158
1612
|
stale.delete(product.handle);
|
|
1159
1613
|
stale.delete(product.id);
|
|
1160
|
-
});
|
|
1614
|
+
}, failureIsReportedByTheRender);
|
|
1161
1615
|
return true;
|
|
1162
1616
|
}
|
|
1163
|
-
function
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
return
|
|
1617
|
+
function failureIsReportedByTheRender() {
|
|
1618
|
+
}
|
|
1619
|
+
function refreshProduct(key) {
|
|
1620
|
+
const normalized = normalizeProductKey(key);
|
|
1621
|
+
if (normalized) productCache.delete(normalized);
|
|
1622
|
+
return getProduct(key);
|
|
1169
1623
|
}
|
|
1624
|
+
var isCollectionAnswer = shaped({
|
|
1625
|
+
collection: orNull({ handle: "string", products: PRODUCT_CONNECTION })
|
|
1626
|
+
});
|
|
1627
|
+
var isSearchAnswer = shaped({ products: PRODUCT_CONNECTION });
|
|
1628
|
+
var isRecommendationsAnswer = shaped({
|
|
1629
|
+
productRecommendations: orNull([PRODUCT])
|
|
1630
|
+
});
|
|
1170
1631
|
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
1632
|
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
1633
|
async function getProducts({
|
|
1173
1634
|
collection,
|
|
1174
|
-
query,
|
|
1635
|
+
query: query2,
|
|
1175
1636
|
first = cfg.collectionFirst,
|
|
1176
1637
|
after = null,
|
|
1177
1638
|
sortKey,
|
|
1178
1639
|
reverse = false,
|
|
1179
1640
|
filters
|
|
1180
1641
|
} = {}) {
|
|
1181
|
-
const fragment = productFragment();
|
|
1182
1642
|
if (collection) {
|
|
1183
|
-
const data2 = await gql(
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1643
|
+
const data2 = await gql(
|
|
1644
|
+
withProductFragment(COLLECTION_QUERY),
|
|
1645
|
+
{
|
|
1646
|
+
handle: collection,
|
|
1647
|
+
first,
|
|
1648
|
+
after,
|
|
1649
|
+
sortKey: sortKey || void 0,
|
|
1650
|
+
reverse,
|
|
1651
|
+
filters: filters ?? void 0
|
|
1652
|
+
},
|
|
1653
|
+
isCollectionAnswer
|
|
1654
|
+
);
|
|
1192
1655
|
if (!data2.collection) throw new TetherError(`Collection not found: ${collection}`, { code: "NOT_FOUND" });
|
|
1193
1656
|
const { products, ...rest } = data2.collection;
|
|
1194
1657
|
products.nodes.forEach(primeProduct);
|
|
1195
1658
|
return { collection: rest, products: products.nodes, pageInfo: products.pageInfo };
|
|
1196
1659
|
}
|
|
1197
|
-
const data = await gql(
|
|
1198
|
-
|
|
1199
|
-
first,
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
sortKey: sortKey || void 0,
|
|
1203
|
-
reverse
|
|
1204
|
-
});
|
|
1660
|
+
const data = await gql(
|
|
1661
|
+
withProductFragment(SEARCH_QUERY),
|
|
1662
|
+
{ first, after, query: query2 || void 0, sortKey: sortKey || void 0, reverse },
|
|
1663
|
+
isSearchAnswer
|
|
1664
|
+
);
|
|
1205
1665
|
data.products.nodes.forEach(primeProduct);
|
|
1206
1666
|
return { collection: null, products: data.products.nodes, pageInfo: data.products.pageInfo };
|
|
1207
1667
|
}
|
|
@@ -1209,45 +1669,30 @@ ${fragment}`, {
|
|
|
1209
1669
|
const key = normalizeProductKey(typeof product === "object" ? product?.id : product);
|
|
1210
1670
|
const byId = key?.startsWith("gid://") ?? false;
|
|
1211
1671
|
const data = await gql(
|
|
1212
|
-
|
|
1213
|
-
|
|
1672
|
+
withProductFragment(
|
|
1673
|
+
`query TetherRecs(${byId ? "$id: ID!" : "$handle: String!"},$intent:ProductRecommendationIntent){productRecommendations(${byId ? "productId: $id" : "productHandle: $handle"},intent:$intent){...TetherProduct}}`
|
|
1674
|
+
),
|
|
1675
|
+
byId ? { id: key, intent } : { handle: key, intent },
|
|
1676
|
+
isRecommendationsAnswer
|
|
1214
1677
|
);
|
|
1215
1678
|
const list = data.productRecommendations ?? [];
|
|
1216
1679
|
list.forEach(primeProduct);
|
|
1217
1680
|
return list;
|
|
1218
1681
|
}
|
|
1219
1682
|
|
|
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
|
|
1683
|
+
// src/cart/render.ts
|
|
1240
1684
|
var DEFAULT_TITLE = "Default Title";
|
|
1241
1685
|
var quantityOf = (line, pending2) => pending2.get(line.id)?.quantity ?? line.quantity;
|
|
1242
1686
|
var numericId = (gid) => gid.split("/").pop() ?? gid;
|
|
1243
1687
|
function lineUrl(line) {
|
|
1244
1688
|
const variant = line.merchandise;
|
|
1245
1689
|
const base = productUrl2(variant.product.handle, variant.product.id);
|
|
1246
|
-
|
|
1247
|
-
|
|
1690
|
+
if (!base) return null;
|
|
1691
|
+
const query2 = new URLSearchParams();
|
|
1692
|
+
query2.set("variant", numericId(variant.id));
|
|
1248
1693
|
const planId = line.sellingPlanAllocation?.sellingPlan.id;
|
|
1249
|
-
if (planId)
|
|
1250
|
-
return `${base}${base.includes("?") ? "&" : "?"}${
|
|
1694
|
+
if (planId) query2.set("selling_plan", numericId(planId));
|
|
1695
|
+
return `${base}${base.includes("?") ? "&" : "?"}${query2.toString()}`;
|
|
1251
1696
|
}
|
|
1252
1697
|
var productTitle = (line) => line.merchandise.product.title;
|
|
1253
1698
|
var LINE_TEXT = {
|
|
@@ -1305,19 +1750,17 @@ ${fragment}`, {
|
|
|
1305
1750
|
qsa(node, sel("line", "warning")).forEach((el) => {
|
|
1306
1751
|
setTextTracked(el, warning?.message ?? "");
|
|
1307
1752
|
});
|
|
1308
|
-
const conditions =
|
|
1309
|
-
subscription
|
|
1310
|
-
"one-time"
|
|
1311
|
-
"on-sale"
|
|
1312
|
-
"has-components"
|
|
1313
|
-
warning
|
|
1314
|
-
|
|
1753
|
+
const conditions = /* @__PURE__ */ new Map([
|
|
1754
|
+
["subscription", isSubscription],
|
|
1755
|
+
["one-time", !isSubscription],
|
|
1756
|
+
["on-sale", isGreaterAmount(line.cost.compareAtAmountPerQuantity?.amount, line.cost.amountPerQuantity.amount)],
|
|
1757
|
+
["has-components", (line.lineComponents ?? []).length > 0],
|
|
1758
|
+
["warning", Boolean(warning)]
|
|
1759
|
+
]);
|
|
1315
1760
|
qsa(node, sel("line-show")).forEach((el) => {
|
|
1316
1761
|
const key = get(el, "line-show");
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
key
|
|
1320
|
-
]);
|
|
1762
|
+
const visible = key === null ? void 0 : conditions.get(key);
|
|
1763
|
+
if (visible !== void 0) show(el, visible);
|
|
1321
1764
|
});
|
|
1322
1765
|
}
|
|
1323
1766
|
function renderLine(node, line, view) {
|
|
@@ -1330,7 +1773,8 @@ ${fragment}`, {
|
|
|
1330
1773
|
return;
|
|
1331
1774
|
}
|
|
1332
1775
|
if (key === "url") {
|
|
1333
|
-
|
|
1776
|
+
const url2 = lineUrl(line);
|
|
1777
|
+
if (url2) el.setAttribute("href", url2);
|
|
1334
1778
|
return;
|
|
1335
1779
|
}
|
|
1336
1780
|
const value = lineValue(line, key, view);
|
|
@@ -1339,12 +1783,7 @@ ${fragment}`, {
|
|
|
1339
1783
|
renderLineState(node, line, view);
|
|
1340
1784
|
qsa(node, `select${sel("line-plan")}`).forEach((select) => {
|
|
1341
1785
|
getProduct(variant.product.handle).then((product) => {
|
|
1342
|
-
fillPlanSelect(
|
|
1343
|
-
/** @type {HTMLSelectElement} */
|
|
1344
|
-
select,
|
|
1345
|
-
line,
|
|
1346
|
-
product
|
|
1347
|
-
);
|
|
1786
|
+
fillPlanSelect(select, line, product);
|
|
1348
1787
|
}).catch((error) => {
|
|
1349
1788
|
reportError(error, "line plan options");
|
|
1350
1789
|
});
|
|
@@ -1353,22 +1792,20 @@ ${fragment}`, {
|
|
|
1353
1792
|
function renderConditions(cart, view) {
|
|
1354
1793
|
const count = cart?.totalQuantity ?? 0;
|
|
1355
1794
|
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
|
-
|
|
1795
|
+
const conditions = /* @__PURE__ */ new Map([
|
|
1796
|
+
["cart-empty", count === 0],
|
|
1797
|
+
["cart-filled", count > 0],
|
|
1798
|
+
["cart-has-subscription", hasSubscription],
|
|
1799
|
+
["cart-no-subscription", !hasSubscription],
|
|
1800
|
+
["cart-has-discount", (cart?.discountCodes ?? []).some((code) => code.applicable)],
|
|
1801
|
+
["cart-error", view.failed],
|
|
1802
|
+
["cart-warning", view.warnings.length > 0]
|
|
1803
|
+
]);
|
|
1365
1804
|
qsa(document, sel("show")).forEach((el) => {
|
|
1366
1805
|
const key = get(el, "show");
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
key
|
|
1371
|
-
]);
|
|
1806
|
+
const visible = key === null ? void 0 : conditions.get(key);
|
|
1807
|
+
if (visible === void 0 || el.closest(sel("product"))) return;
|
|
1808
|
+
show(el, visible);
|
|
1372
1809
|
});
|
|
1373
1810
|
qsa(document, sel("checkout")).forEach((el) => {
|
|
1374
1811
|
el.classList.toggle("is-disabled", !count);
|
|
@@ -1424,15 +1861,38 @@ ${fragment}`, {
|
|
|
1424
1861
|
}
|
|
1425
1862
|
function showSavedCount(count) {
|
|
1426
1863
|
qsa(document, sel("cart-count")).forEach((el) => {
|
|
1427
|
-
if (/^\s*0?\s*$/.test(el.textContent)) el.textContent = count;
|
|
1864
|
+
if (/^\s*0?\s*$/.test(el.textContent)) el.textContent = String(count);
|
|
1428
1865
|
});
|
|
1429
1866
|
}
|
|
1430
1867
|
|
|
1431
|
-
// src/
|
|
1868
|
+
// src/storage.ts
|
|
1869
|
+
function getStored(key, area = "localStorage") {
|
|
1870
|
+
try {
|
|
1871
|
+
return window[area].getItem(key);
|
|
1872
|
+
} catch {
|
|
1873
|
+
return null;
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
function setStored(key, value, area = "localStorage") {
|
|
1877
|
+
try {
|
|
1878
|
+
window[area].setItem(key, value);
|
|
1879
|
+
} catch {
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
function removeStored(key, area = "localStorage") {
|
|
1883
|
+
try {
|
|
1884
|
+
window[area].removeItem(key);
|
|
1885
|
+
} catch {
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
|
|
1889
|
+
// server/src/shared/cart-id.ts
|
|
1890
|
+
var CART_ID = /^gid:\/\/shopify\/Cart\/[\w-]+(\?key=[\w-]+)?$/;
|
|
1891
|
+
|
|
1892
|
+
// src/cart/store.ts
|
|
1432
1893
|
var status = { kind: "unknown" };
|
|
1433
1894
|
var warnings = [];
|
|
1434
1895
|
var pendingQuantities = /* @__PURE__ */ new Map();
|
|
1435
|
-
var customer = { accessToken: null, beforeCheckout: null };
|
|
1436
1896
|
var cartStatus = () => status;
|
|
1437
1897
|
function currentCart() {
|
|
1438
1898
|
switch (status.kind) {
|
|
@@ -1447,27 +1907,19 @@ ${fragment}`, {
|
|
|
1447
1907
|
var currentWarnings = () => warnings;
|
|
1448
1908
|
var statusOf = (cart) => cart ? { kind: "ready", cart } : { kind: "empty" };
|
|
1449
1909
|
var seenKey = () => `${cfg.storageKey}:seen`;
|
|
1450
|
-
function
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
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
|
-
}
|
|
1910
|
+
function savedCartId() {
|
|
1911
|
+
const stored = getStored(cfg.storageKey);
|
|
1912
|
+
if (!stored) return null;
|
|
1913
|
+
if (CART_ID.test(stored)) return stored;
|
|
1914
|
+
forgetSavedCart();
|
|
1915
|
+
return null;
|
|
1463
1916
|
}
|
|
1464
|
-
var savedCartId = () => readStorageIfAllowed(cfg.storageKey) || null;
|
|
1465
1917
|
function forgetSavedCart() {
|
|
1466
|
-
|
|
1918
|
+
removeStored(cfg.storageKey);
|
|
1467
1919
|
}
|
|
1468
1920
|
function keepQuietly(cart) {
|
|
1469
1921
|
status = statusOf(cart);
|
|
1470
|
-
if (cart)
|
|
1922
|
+
if (cart) setStored(cfg.storageKey, cart.id);
|
|
1471
1923
|
}
|
|
1472
1924
|
function render() {
|
|
1473
1925
|
renderCart({
|
|
@@ -1483,7 +1935,7 @@ ${fragment}`, {
|
|
|
1483
1935
|
}
|
|
1484
1936
|
function commit(cart, reason) {
|
|
1485
1937
|
keepQuietly(cart);
|
|
1486
|
-
|
|
1938
|
+
setStored(seenKey(), `${Date.now()}|${cart?.totalQuantity ?? 0}`);
|
|
1487
1939
|
announce(reason);
|
|
1488
1940
|
return cart;
|
|
1489
1941
|
}
|
|
@@ -1491,14 +1943,15 @@ ${fragment}`, {
|
|
|
1491
1943
|
status = outcome.kind === "loaded" ? statusOf(outcome.cart) : { kind: "error", error: outcome.error, cart: null };
|
|
1492
1944
|
announce("load");
|
|
1493
1945
|
}
|
|
1494
|
-
function
|
|
1495
|
-
const
|
|
1496
|
-
|
|
1946
|
+
function lastKnownCount() {
|
|
1947
|
+
const text2 = getStored(seenKey())?.split("|")[1] ?? "";
|
|
1948
|
+
const count = Number(text2);
|
|
1949
|
+
return /^\d+$/.test(text2) && Number.isSafeInteger(count) ? count : null;
|
|
1497
1950
|
}
|
|
1498
1951
|
function showLastKnownCount() {
|
|
1499
1952
|
if (status.kind !== "unknown") return;
|
|
1500
|
-
const count =
|
|
1501
|
-
if (count) showSavedCount(count);
|
|
1953
|
+
const count = lastKnownCount();
|
|
1954
|
+
if (count !== null) showSavedCount(count);
|
|
1502
1955
|
}
|
|
1503
1956
|
function setWarnings(notes) {
|
|
1504
1957
|
warnings = notes.filter((note, index) => notes.findIndex((other) => other.message === note.message) === index).map((note) => ({ ...note, message: cfg.warningText?.(note) || note.message }));
|
|
@@ -1536,7 +1989,7 @@ ${fragment}`, {
|
|
|
1536
1989
|
open: isOpen(),
|
|
1537
1990
|
count: currentCart()?.totalQuantity ?? 0,
|
|
1538
1991
|
loaded: status.kind !== "unknown",
|
|
1539
|
-
warnings
|
|
1992
|
+
warnings: [...warnings]
|
|
1540
1993
|
});
|
|
1541
1994
|
function subscribe(listener) {
|
|
1542
1995
|
const events = ["cart-updated", "cart-toggled"].map((name) => `${cfg.prefix}:${name}`);
|
|
@@ -1554,15 +2007,19 @@ ${fragment}`, {
|
|
|
1554
2007
|
};
|
|
1555
2008
|
}
|
|
1556
2009
|
|
|
1557
|
-
// src/cart/mutations.
|
|
2010
|
+
// src/cart/mutations.ts
|
|
1558
2011
|
var UNKNOWN_CART_MESSAGE = /does not exist|not found|invalid id/i;
|
|
2012
|
+
var isPayloadOf = (name) => shaped({ [name]: orNull({ cart: orNull(CART) }) });
|
|
1559
2013
|
function isCartGone(error) {
|
|
1560
2014
|
if (!(error instanceof TetherError)) return false;
|
|
1561
2015
|
if (error.userErrors?.some((userError) => userError.field?.[0] === "cartId")) return true;
|
|
1562
2016
|
return (error.errors ?? []).some((graphQLError) => UNKNOWN_CART_MESSAGE.test(graphQLError.message));
|
|
1563
2017
|
}
|
|
2018
|
+
function isUnclearFailure(error) {
|
|
2019
|
+
return error instanceof TetherError && (error.code === "NETWORK" || error.code === "INVALID_RESPONSE" || (error.status ?? 0) >= 500);
|
|
2020
|
+
}
|
|
1564
2021
|
function toLineInput(input, quantity = 1, options = {}) {
|
|
1565
|
-
const spec = typeof input === "object" && input !== null ? input : { ...options, variantId: input
|
|
2022
|
+
const spec = typeof input === "object" && input !== null ? input : { ...options, quantity, ...input === null || input === void 0 ? {} : { variantId: input } };
|
|
1566
2023
|
const merchandiseId = GID.variant(spec.merchandiseId || spec.variantId || spec.id);
|
|
1567
2024
|
if (!merchandiseId) throw new TetherError("add(): variantId is required");
|
|
1568
2025
|
const asked = parseInt(String(spec.quantity ?? 1), 10);
|
|
@@ -1574,23 +2031,25 @@ ${fragment}`, {
|
|
|
1574
2031
|
return line;
|
|
1575
2032
|
}
|
|
1576
2033
|
function mutationText(name, args, inner) {
|
|
1577
|
-
return `mutation Tether_${name}(${args}){${name}(${inner}){cart{...TetherCart}userErrors{field message}warnings{code message target}}}${cartFragment()}`;
|
|
2034
|
+
return (access) => `mutation Tether_${name}(${args}){${name}(${inner}){cart{...TetherCart}userErrors{field message}warnings{code message target}}}${cartFragment(access)}`;
|
|
1578
2035
|
}
|
|
1579
2036
|
async function send(name, args, inner, variables) {
|
|
1580
|
-
const data = await gql(mutationText(name, args, inner), variables);
|
|
2037
|
+
const data = await gql(mutationText(name, args, inner), variables, isPayloadOf(name), { operation: "mutation" });
|
|
1581
2038
|
const payload = unwrap(data[name], name);
|
|
1582
2039
|
setWarnings(payload.warnings ?? []);
|
|
1583
2040
|
return payload.cart;
|
|
1584
2041
|
}
|
|
1585
2042
|
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;
|
|
2043
|
+
const withIdentity = cfg.country ? { buyerIdentity: { countryCode: cfg.country }, ...input } : input;
|
|
1591
2044
|
return send("cartCreate", "$input: CartInput!", "input: $input", { input: withIdentity });
|
|
1592
2045
|
}
|
|
1593
|
-
async function mutateCart({
|
|
2046
|
+
async function mutateCart({
|
|
2047
|
+
name,
|
|
2048
|
+
args,
|
|
2049
|
+
inner,
|
|
2050
|
+
variables,
|
|
2051
|
+
createInput
|
|
2052
|
+
}) {
|
|
1594
2053
|
const cartId = savedCartId();
|
|
1595
2054
|
if (!cartId) return createInput ? createCart(createInput) : null;
|
|
1596
2055
|
try {
|
|
@@ -1604,11 +2063,19 @@ ${fragment}`, {
|
|
|
1604
2063
|
}
|
|
1605
2064
|
}
|
|
1606
2065
|
async function fetchCart(id) {
|
|
1607
|
-
const data = await gql(
|
|
2066
|
+
const data = await gql(
|
|
2067
|
+
(access) => `query TetherCart($id:ID!){cart(id:$id){...TetherCart}}${cartFragment(access)}`,
|
|
2068
|
+
{ id },
|
|
2069
|
+
shaped({ cart: orNull(CART) })
|
|
2070
|
+
);
|
|
1608
2071
|
return data.cart;
|
|
1609
2072
|
}
|
|
1610
2073
|
async function fetchCheckoutUrl(id) {
|
|
1611
|
-
const data = await gql(
|
|
2074
|
+
const data = await gql(
|
|
2075
|
+
"query TetherCheckout($id: ID!) { cart(id: $id) { checkoutUrl } }",
|
|
2076
|
+
{ id },
|
|
2077
|
+
shaped({ cart: orNull({ checkoutUrl: "string" }) })
|
|
2078
|
+
);
|
|
1612
2079
|
return data.cart?.checkoutUrl ?? null;
|
|
1613
2080
|
}
|
|
1614
2081
|
function toLineUpdate(lineId, patch) {
|
|
@@ -1638,21 +2105,20 @@ ${fragment}`, {
|
|
|
1638
2105
|
});
|
|
1639
2106
|
var setLineQuantity = (lineId, target) => target > 0 ? updateLines([{ id: lineId, quantity: target }]) : removeLines([lineId]);
|
|
1640
2107
|
|
|
1641
|
-
// src/cart/checkout.
|
|
2108
|
+
// src/cart/checkout.ts
|
|
2109
|
+
var resolveAccountCheckout = null;
|
|
2110
|
+
function setCheckoutResolver(resolver) {
|
|
2111
|
+
resolveAccountCheckout = resolver;
|
|
2112
|
+
}
|
|
1642
2113
|
var askCheckoutUrl = (cartId) => fetchCheckoutUrl(cartId).then(
|
|
1643
|
-
(
|
|
1644
|
-
|
|
1645
|
-
"url"
|
|
1646
|
-
), url }),
|
|
1647
|
-
(error) => ({ kind: (
|
|
1648
|
-
/** @type {const} */
|
|
1649
|
-
"failed"
|
|
1650
|
-
), error })
|
|
2114
|
+
(url2) => ({ kind: "url", url: url2 }),
|
|
2115
|
+
(error) => ({ kind: "failed", error })
|
|
1651
2116
|
);
|
|
1652
2117
|
async function accountCheckoutUrl() {
|
|
1653
|
-
if (!
|
|
2118
|
+
if (!resolveAccountCheckout) return null;
|
|
1654
2119
|
try {
|
|
1655
|
-
|
|
2120
|
+
const url2 = await resolveAccountCheckout();
|
|
2121
|
+
return url2 && `${url2}${url2.includes("?") ? "&" : "?"}logged_in=true`;
|
|
1656
2122
|
} catch (error) {
|
|
1657
2123
|
reportError(error, "account checkout");
|
|
1658
2124
|
return null;
|
|
@@ -1660,15 +2126,12 @@ ${fragment}`, {
|
|
|
1660
2126
|
}
|
|
1661
2127
|
async function checkoutUrl(cartId) {
|
|
1662
2128
|
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`;
|
|
2129
|
+
const accountUrl = await accountCheckoutUrl();
|
|
2130
|
+
if (accountUrl) return accountUrl;
|
|
2131
|
+
const answer = await cartAnswer;
|
|
2132
|
+
if (answer.kind === "failed") throw answer.error;
|
|
2133
|
+
if (!answer.url) throw new TetherError("No checkoutUrl (cart empty or expired)");
|
|
2134
|
+
return answer.url;
|
|
1672
2135
|
}
|
|
1673
2136
|
function detachOpenerIfAllowed(tab) {
|
|
1674
2137
|
try {
|
|
@@ -1683,12 +2146,12 @@ ${fragment}`, {
|
|
|
1683
2146
|
const tab = target === "_blank" ? window.open("", "_blank") : null;
|
|
1684
2147
|
if (tab) detachOpenerIfAllowed(tab);
|
|
1685
2148
|
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
|
|
2149
|
+
const url2 = await checkoutUrl(cartId);
|
|
2150
|
+
if (!isHttpsUrl(url2)) throw new TetherError("The checkout URL is not https: not opened");
|
|
2151
|
+
emit("checkout", { url: url2, cart: currentCart() });
|
|
2152
|
+
if (tab && !tab.closed) tab.location.href = url2;
|
|
2153
|
+
else window.location.href = url2;
|
|
2154
|
+
return url2;
|
|
1692
2155
|
} catch (error) {
|
|
1693
2156
|
tab?.close();
|
|
1694
2157
|
reportError(error, "checkout failed");
|
|
@@ -1696,8 +2159,27 @@ ${fragment}`, {
|
|
|
1696
2159
|
}
|
|
1697
2160
|
}
|
|
1698
2161
|
|
|
1699
|
-
// src/cart.
|
|
2162
|
+
// src/cart.ts
|
|
1700
2163
|
var currentLines = () => currentCart()?.lines.nodes ?? [];
|
|
2164
|
+
async function reloadAfterUnclearFailure() {
|
|
2165
|
+
const id = savedCartId();
|
|
2166
|
+
if (!id) return;
|
|
2167
|
+
try {
|
|
2168
|
+
const cart = await fetchCart(id);
|
|
2169
|
+
if (!cart) forgetSavedCart();
|
|
2170
|
+
commit(cart, "load");
|
|
2171
|
+
} catch (error) {
|
|
2172
|
+
reportError(error, "cart reload after a failed change");
|
|
2173
|
+
}
|
|
2174
|
+
}
|
|
2175
|
+
function runChange(task) {
|
|
2176
|
+
return enqueue(
|
|
2177
|
+
() => task().catch(async (error) => {
|
|
2178
|
+
if (isUnclearFailure(error)) await reloadAfterUnclearFailure();
|
|
2179
|
+
throw error;
|
|
2180
|
+
})
|
|
2181
|
+
);
|
|
2182
|
+
}
|
|
1701
2183
|
function changeQuantity(lineId, delta) {
|
|
1702
2184
|
const line = currentLines().find((candidate) => candidate.id === lineId);
|
|
1703
2185
|
if (!line) return Promise.resolve(currentCart());
|
|
@@ -1706,7 +2188,7 @@ ${fragment}`, {
|
|
|
1706
2188
|
pendingQuantities.set(lineId, job);
|
|
1707
2189
|
announce("pending");
|
|
1708
2190
|
const isLatest = () => pendingQuantities.get(lineId) === job;
|
|
1709
|
-
const sent =
|
|
2191
|
+
const sent = runChange(async () => {
|
|
1710
2192
|
if (!isLatest()) return currentCart();
|
|
1711
2193
|
const now = currentLines().find((candidate) => candidate.id === lineId);
|
|
1712
2194
|
if (now?.quantity === target) return currentCart();
|
|
@@ -1753,84 +2235,68 @@ ${fragment}`, {
|
|
|
1753
2235
|
}
|
|
1754
2236
|
var Cart = {
|
|
1755
2237
|
init,
|
|
1756
|
-
/**
|
|
2238
|
+
/** The saved cart's id. */
|
|
1757
2239
|
id: savedCartId,
|
|
1758
|
-
/** @returns {CartStatus} */
|
|
1759
2240
|
get status() {
|
|
1760
2241
|
return cartStatus();
|
|
1761
2242
|
},
|
|
1762
|
-
/** @returns {ShopifyCart | null} */
|
|
1763
2243
|
get state() {
|
|
1764
2244
|
return currentCart();
|
|
1765
2245
|
},
|
|
1766
|
-
/**
|
|
2246
|
+
/** The cart's lines, a copy: changing it changes nothing in the cart. */
|
|
1767
2247
|
get lines() {
|
|
1768
|
-
return currentLines();
|
|
2248
|
+
return [...currentLines()];
|
|
1769
2249
|
},
|
|
1770
|
-
/** @returns {number} */
|
|
1771
2250
|
get count() {
|
|
1772
2251
|
return currentCart()?.totalQuantity ?? 0;
|
|
1773
2252
|
},
|
|
1774
|
-
/**
|
|
2253
|
+
/** True once the saved cart, or that there is none, is known. */
|
|
1775
2254
|
get loaded() {
|
|
1776
2255
|
return cartStatus().kind !== "unknown";
|
|
1777
2256
|
},
|
|
1778
|
-
/**
|
|
2257
|
+
/**
|
|
2258
|
+
* The failure of the last load or change, until a change succeeds. Requests fail with an `Error`; anything else
|
|
2259
|
+
* thrown is wrapped, so the public type holds.
|
|
2260
|
+
*/
|
|
1779
2261
|
get lastError() {
|
|
1780
2262
|
const current = cartStatus();
|
|
1781
|
-
|
|
2263
|
+
if (current.kind !== "error") return null;
|
|
2264
|
+
return current.error instanceof Error ? current.error : new Error(String(current.error));
|
|
1782
2265
|
},
|
|
1783
|
-
|
|
1784
|
-
setFailure(error);
|
|
1785
|
-
},
|
|
1786
|
-
/** @returns {readonly CartWarning[]} Shopify's notes on the last change. */
|
|
2266
|
+
/** Shopify's notes on the last change, a copy. */
|
|
1787
2267
|
get warnings() {
|
|
1788
|
-
return currentWarnings();
|
|
2268
|
+
return [...currentWarnings()];
|
|
1789
2269
|
},
|
|
1790
|
-
/**
|
|
2270
|
+
/** Line id → the quantity quick clicks asked for. */
|
|
1791
2271
|
get pendingQty() {
|
|
1792
2272
|
return pendingQuantities;
|
|
1793
2273
|
},
|
|
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
2274
|
/**
|
|
1809
|
-
*
|
|
1810
|
-
* customer
|
|
1811
|
-
*
|
|
1812
|
-
* @param {Partial<CustomerLink>} link Fields left out keep their value.
|
|
2275
|
+
* Sets where a logged-in customer checks out, or clears it with `null`: the account part's resolver answers the
|
|
2276
|
+
* customer's checkout URL for the cart (`null` uses the cart's own).
|
|
1813
2277
|
*/
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
2278
|
+
setCheckoutResolver,
|
|
2279
|
+
/**
|
|
2280
|
+
* Forgets the saved cart on this device, as if there were none: the page shows an empty cart and
|
|
2281
|
+
* `tether:cart-updated` fires with reason `load`. The next add starts a new cart.
|
|
2282
|
+
*/
|
|
2283
|
+
forgetSaved() {
|
|
2284
|
+
forgetSavedCart();
|
|
2285
|
+
settleLoad({ kind: "loaded", cart: null });
|
|
1817
2286
|
},
|
|
1818
|
-
/** Keeps a cart and shows it, as a change does; tests seed the cart with it. */
|
|
1819
|
-
_commit: commit,
|
|
1820
2287
|
render,
|
|
1821
2288
|
subscribe,
|
|
1822
2289
|
/**
|
|
1823
2290
|
* `add(variantId, 2)`, `add({ variantId, quantity, sellingPlanId, attributes })`, or `add([line, line])` for
|
|
1824
2291
|
* bundles in one request. Creates the cart when there is none.
|
|
1825
2292
|
*
|
|
1826
|
-
* @
|
|
1827
|
-
* @param {number} [quantity]
|
|
1828
|
-
* @param {Omit<LineSpec, 'variantId' | 'quantity'>} [options]
|
|
1829
|
-
* @returns {Promise<ShopifyCart | null>}
|
|
2293
|
+
* @throws {TetherError} (rejected) When the add fails, or Shopify answers it without a cart.
|
|
1830
2294
|
*/
|
|
1831
2295
|
add(input, quantity = 1, options = {}) {
|
|
1832
|
-
const lines = (Array.isArray(input) ? input : [input]).map(
|
|
1833
|
-
|
|
2296
|
+
const lines = (Array.isArray(input) ? input : [input]).map(
|
|
2297
|
+
(line) => toLineInput(line, quantity, options)
|
|
2298
|
+
);
|
|
2299
|
+
return runChange(async () => {
|
|
1834
2300
|
const cart = await mutateCart({
|
|
1835
2301
|
name: "cartLinesAdd",
|
|
1836
2302
|
args: "$lines: [CartLineInput!]!",
|
|
@@ -1838,6 +2304,7 @@ ${fragment}`, {
|
|
|
1838
2304
|
variables: { lines },
|
|
1839
2305
|
createInput: { lines }
|
|
1840
2306
|
});
|
|
2307
|
+
if (!cart) throw new TetherError("Shopify answered the add without a cart");
|
|
1841
2308
|
commit(cart, "add");
|
|
1842
2309
|
emit("added", { cart, lines });
|
|
1843
2310
|
if (cfg.openCartOnAdd) openCart();
|
|
@@ -1847,43 +2314,29 @@ ${fragment}`, {
|
|
|
1847
2314
|
/**
|
|
1848
2315
|
* `updateLine(lineId, 3)` or `updateLine(lineId, { quantity, sellingPlanId, attributes })`.
|
|
1849
2316
|
*
|
|
1850
|
-
* @param
|
|
1851
|
-
* @param {number | LinePatch} patch A quantity of 0 or less removes the line.
|
|
1852
|
-
* @returns {Promise<ShopifyCart | null>}
|
|
2317
|
+
* @param patch A quantity of 0 or less removes the line.
|
|
1853
2318
|
*/
|
|
1854
2319
|
updateLine(lineId, patch) {
|
|
1855
2320
|
const quantity = typeof patch === "number" ? patch : patch.quantity;
|
|
1856
2321
|
if (quantity !== void 0 && quantity <= 0) return Cart.removeLine(lineId);
|
|
1857
2322
|
const line = toLineUpdate(lineId, patch);
|
|
1858
|
-
return
|
|
2323
|
+
return runChange(async () => commit(await updateLines([line]), "update"));
|
|
1859
2324
|
},
|
|
1860
2325
|
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
|
-
*/
|
|
2326
|
+
/** The quantity to show: the one asked for while an update is on its way. */
|
|
1865
2327
|
quantityOf: pendingQuantityOf,
|
|
1866
|
-
/**
|
|
1867
|
-
* @param {string | string[]} lineIds
|
|
1868
|
-
* @returns {Promise<ShopifyCart | null>}
|
|
1869
|
-
*/
|
|
1870
2328
|
removeLine(lineIds) {
|
|
1871
2329
|
const ids = Array.isArray(lineIds) ? lineIds : [lineIds];
|
|
1872
|
-
return
|
|
2330
|
+
return runChange(async () => commit(await removeLines(ids), "remove"));
|
|
1873
2331
|
},
|
|
1874
|
-
/** @returns {Promise<ShopifyCart | null>} */
|
|
1875
2332
|
clear() {
|
|
1876
2333
|
const ids = currentLines().map((line) => line.id);
|
|
1877
2334
|
if (!ids.length) return Promise.resolve(currentCart());
|
|
1878
2335
|
return Cart.removeLine(ids);
|
|
1879
2336
|
},
|
|
1880
|
-
/**
|
|
1881
|
-
* @param {string | null | undefined} note
|
|
1882
|
-
* @returns {Promise<ShopifyCart | null>}
|
|
1883
|
-
*/
|
|
1884
2337
|
setNote(note) {
|
|
1885
2338
|
const variables = { note: note ?? "" };
|
|
1886
|
-
return
|
|
2339
|
+
return runChange(
|
|
1887
2340
|
async () => commit(
|
|
1888
2341
|
await mutateCart({
|
|
1889
2342
|
name: "cartNoteUpdate",
|
|
@@ -1899,11 +2352,10 @@ ${fragment}`, {
|
|
|
1899
2352
|
/**
|
|
1900
2353
|
* Cart attributes, merged with the cart's (as it is when the change runs, so quick calls don't drop each other).
|
|
1901
2354
|
*
|
|
1902
|
-
* @param
|
|
1903
|
-
* @returns {Promise<ShopifyCart | null>}
|
|
2355
|
+
* @param attributes The value `''` removes a key.
|
|
1904
2356
|
*/
|
|
1905
2357
|
setAttributes(attributes) {
|
|
1906
|
-
return
|
|
2358
|
+
return runChange(async () => {
|
|
1907
2359
|
const variables = { attributes: mergedAttributes(attributes) };
|
|
1908
2360
|
const cart = await mutateCart({
|
|
1909
2361
|
name: "cartAttributesUpdate",
|
|
@@ -1915,14 +2367,9 @@ ${fragment}`, {
|
|
|
1915
2367
|
return commit(cart, "attributes");
|
|
1916
2368
|
});
|
|
1917
2369
|
},
|
|
1918
|
-
/**
|
|
1919
|
-
* Replaces the discount codes; `[]` removes them all.
|
|
1920
|
-
*
|
|
1921
|
-
* @param {string[] | string | ((current: string[]) => string[])} codes
|
|
1922
|
-
* @returns {Promise<ShopifyCart | null>}
|
|
1923
|
-
*/
|
|
2370
|
+
/** Replaces the discount codes; `[]` removes them all. */
|
|
1924
2371
|
setDiscountCodes(codes) {
|
|
1925
|
-
return
|
|
2372
|
+
return runChange(async () => {
|
|
1926
2373
|
const variables = { discountCodes: discountCodesFrom(codes) };
|
|
1927
2374
|
const cart = await mutateCart({
|
|
1928
2375
|
name: "cartDiscountCodesUpdate",
|
|
@@ -1937,8 +2384,6 @@ ${fragment}`, {
|
|
|
1937
2384
|
/**
|
|
1938
2385
|
* Adds a code. A code Shopify doesn't accept is removed again, so it doesn't warn on every later change.
|
|
1939
2386
|
*
|
|
1940
|
-
* @param {string} code
|
|
1941
|
-
* @returns {Promise<ShopifyCart | null>}
|
|
1942
2387
|
* @throws {TetherError} `INVALID_DISCOUNT` (rejected) when Shopify doesn't accept the code.
|
|
1943
2388
|
*/
|
|
1944
2389
|
async applyDiscount(code) {
|
|
@@ -1953,10 +2398,6 @@ ${fragment}`, {
|
|
|
1953
2398
|
}
|
|
1954
2399
|
throw new TetherError(`Discount code not valid: ${code}`, { code: "INVALID_DISCOUNT" });
|
|
1955
2400
|
},
|
|
1956
|
-
/**
|
|
1957
|
-
* @param {string} code
|
|
1958
|
-
* @returns {Promise<ShopifyCart | null>}
|
|
1959
|
-
*/
|
|
1960
2401
|
removeDiscount(code) {
|
|
1961
2402
|
const unwanted = code.toLowerCase();
|
|
1962
2403
|
return Cart.setDiscountCodes((current) => current.filter((existing) => existing.toLowerCase() !== unwanted));
|
|
@@ -1964,12 +2405,10 @@ ${fragment}`, {
|
|
|
1964
2405
|
/**
|
|
1965
2406
|
* Sets who is buying; a country changes the cart's currency and prices to that market's.
|
|
1966
2407
|
*
|
|
1967
|
-
* @param
|
|
1968
|
-
* @param {{ create?: boolean }} [options] `create: false` forgets an expired cart instead of creating an empty one.
|
|
1969
|
-
* @returns {Promise<ShopifyCart | null>}
|
|
2408
|
+
* @param options `create: false` forgets an expired cart instead of creating an empty one.
|
|
1970
2409
|
*/
|
|
1971
2410
|
setBuyerIdentity(buyerIdentity, { create = true } = {}) {
|
|
1972
|
-
return
|
|
2411
|
+
return runChange(async () => {
|
|
1973
2412
|
const cart = await mutateCart({
|
|
1974
2413
|
name: "cartBuyerIdentityUpdate",
|
|
1975
2414
|
args: "$buyerIdentity: CartBuyerIdentityInput!",
|
|
@@ -1991,22 +2430,8 @@ ${fragment}`, {
|
|
|
1991
2430
|
toggle: toggleCart
|
|
1992
2431
|
};
|
|
1993
2432
|
|
|
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
|
-
});
|
|
2433
|
+
// src/product-state.ts
|
|
2434
|
+
var PERCENT2 = 100;
|
|
2010
2435
|
var timesQuantity = (money, quantity) => fromMinorUnits(toMinorUnits(money) * quantity, money.currencyCode);
|
|
2011
2436
|
var selectionOf = (variant) => Object.fromEntries(variant.selectedOptions.map((option) => [option.name, option.value]));
|
|
2012
2437
|
function findVariant(product, selected) {
|
|
@@ -2021,14 +2446,18 @@ ${fragment}`, {
|
|
|
2021
2446
|
)
|
|
2022
2447
|
);
|
|
2023
2448
|
}
|
|
2024
|
-
|
|
2449
|
+
var purchasePlanId = (purchase) => purchase.kind === "subscription" ? purchase.planId : purchase.offeredPlanId;
|
|
2450
|
+
function linePurchase(purchase, variant) {
|
|
2451
|
+
if (purchase.kind === "subscription" && allocationFor(variant, purchase.planId)) return purchase;
|
|
2452
|
+
return { kind: "one-time" };
|
|
2453
|
+
}
|
|
2454
|
+
function normalisePurchase(product, variant, kind, preferredPlanId) {
|
|
2025
2455
|
const plans = allPlans(product);
|
|
2026
|
-
const known = plans.find((plan) => plan.id ===
|
|
2027
|
-
if (!known) return { kind: "one-time",
|
|
2456
|
+
const known = plans.find((plan) => plan.id === preferredPlanId) ?? plans[0];
|
|
2457
|
+
if (!known) return { kind: "one-time", offeredPlanId: null };
|
|
2028
2458
|
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 };
|
|
2459
|
+
if (product.requiresSellingPlan || kind === "subscription") return { kind: "subscription", planId: onVariant.id };
|
|
2460
|
+
return { kind: "one-time", offeredPlanId: onVariant.id };
|
|
2032
2461
|
}
|
|
2033
2462
|
function initialChoice(product, { variant, sellingPlan, defaultPlan, defaultPurchase }) {
|
|
2034
2463
|
const variants = product.variants.nodes;
|
|
@@ -2039,13 +2468,14 @@ ${fragment}`, {
|
|
|
2039
2468
|
const kind = fromUrl || defaultPurchase === "subscription" ? "subscription" : "one-time";
|
|
2040
2469
|
return {
|
|
2041
2470
|
selected: start ? selectionOf(start) : {},
|
|
2042
|
-
purchase: normalisePurchase(product, start,
|
|
2471
|
+
purchase: normalisePurchase(product, start, kind, offered?.id ?? null)
|
|
2043
2472
|
};
|
|
2044
2473
|
}
|
|
2045
2474
|
function showConditions(variant, lowStock, variantCount, sale, requiresSellingPlan) {
|
|
2046
2475
|
const stock = variant.quantityAvailable;
|
|
2047
2476
|
const low = stock != null && stock > 0 && stock <= lowStock;
|
|
2048
2477
|
const onSale = sale.compareAtPrice !== null;
|
|
2478
|
+
const onPlan = sale.line.kind === "subscription";
|
|
2049
2479
|
return {
|
|
2050
2480
|
"in-stock": variant.availableForSale,
|
|
2051
2481
|
"out-of-stock": !variant.availableForSale,
|
|
@@ -2060,23 +2490,24 @@ ${fragment}`, {
|
|
|
2060
2490
|
"subscription-unavailable": !sale.subscriptionAvailable,
|
|
2061
2491
|
"subscription-only": requiresSellingPlan,
|
|
2062
2492
|
"one-time-available": !requiresSellingPlan,
|
|
2063
|
-
"subscription-selected":
|
|
2064
|
-
"one-time-selected": !
|
|
2065
|
-
"has-savings":
|
|
2493
|
+
"subscription-selected": onPlan,
|
|
2494
|
+
"one-time-selected": !onPlan,
|
|
2495
|
+
"has-savings": onPlan ? sale.savings.percentage > 0 : onSale
|
|
2066
2496
|
};
|
|
2067
2497
|
}
|
|
2068
2498
|
function productView(product, choice, lowStock) {
|
|
2069
2499
|
const exact = findVariant(product, choice.selected);
|
|
2070
2500
|
const variant = exact ?? product.variants.nodes[0];
|
|
2071
2501
|
if (!variant) return null;
|
|
2072
|
-
const purchase = normalisePurchase(product, variant, choice.purchase);
|
|
2073
|
-
const
|
|
2502
|
+
const purchase = normalisePurchase(product, variant, choice.purchase.kind, purchasePlanId(choice.purchase));
|
|
2503
|
+
const planId = purchasePlanId(purchase);
|
|
2074
2504
|
const plans = allPlans(product);
|
|
2075
|
-
const
|
|
2505
|
+
const line = linePurchase(purchase, variant);
|
|
2506
|
+
const allocation = line.kind === "subscription" ? allocationFor(variant, line.planId) : null;
|
|
2076
2507
|
const price = allocation ? planPrice(variant, planId) ?? variant.price : variant.price;
|
|
2077
2508
|
const compareAt = allocation?.priceAdjustments[0]?.compareAtPrice ?? variant.compareAtPrice;
|
|
2078
2509
|
const sale = {
|
|
2079
|
-
|
|
2510
|
+
line,
|
|
2080
2511
|
subscriptionAvailable: plans.some((plan) => allocationFor(variant, plan.id)),
|
|
2081
2512
|
compareAtPrice: compareAt && parseFloat(compareAt.amount) > parseFloat(price.amount) ? compareAt : null,
|
|
2082
2513
|
savings: planSavings(variant, planId)
|
|
@@ -2094,15 +2525,15 @@ ${fragment}`, {
|
|
|
2094
2525
|
}
|
|
2095
2526
|
function variantBindTexts(view, format) {
|
|
2096
2527
|
const { variant, plan, price, compareAtPrice, quantity } = view;
|
|
2097
|
-
const planId = view.purchase
|
|
2528
|
+
const planId = purchasePlanId(view.purchase);
|
|
2098
2529
|
const forQuantity = (money) => timesQuantity(money, Math.max(1, quantity));
|
|
2099
2530
|
const checkoutCharge = () => {
|
|
2100
|
-
const charge = view.
|
|
2531
|
+
const charge = view.line.kind === "subscription" ? planCheckoutCharge(variant, planId) ?? price : price;
|
|
2101
2532
|
return format.money(forQuantity(charge));
|
|
2102
2533
|
};
|
|
2103
2534
|
const discountPercentage = () => {
|
|
2104
2535
|
if (!compareAtPrice) return "0%";
|
|
2105
|
-
return `${Math.round((1 - parseFloat(price.amount) / parseFloat(compareAtPrice.amount)) *
|
|
2536
|
+
return `${Math.round((1 - parseFloat(price.amount) / parseFloat(compareAtPrice.amount)) * PERCENT2)}%`;
|
|
2106
2537
|
};
|
|
2107
2538
|
const discountAmount = () => {
|
|
2108
2539
|
if (!compareAtPrice) return "";
|
|
@@ -2151,7 +2582,7 @@ ${fragment}`, {
|
|
|
2151
2582
|
return new Map(Object.entries(texts));
|
|
2152
2583
|
}
|
|
2153
2584
|
|
|
2154
|
-
// src/product-render.
|
|
2585
|
+
// src/product-render.ts
|
|
2155
2586
|
var NTH_IMAGE_BIND = /^image:(\d+)$/;
|
|
2156
2587
|
var METAFIELD_BIND = "metafield:";
|
|
2157
2588
|
var MULTI_LINE_METAFIELD = /^(rich_text|multi_line_text)_field$/;
|
|
@@ -2172,7 +2603,7 @@ ${fragment}`, {
|
|
|
2172
2603
|
const dot = ref.indexOf(".");
|
|
2173
2604
|
const namespace = ref.slice(0, dot);
|
|
2174
2605
|
const key = ref.slice(dot + 1);
|
|
2175
|
-
const metafield = (product.metafields ?? []).find((
|
|
2606
|
+
const metafield = (product.metafields ?? []).find((field2) => field2?.namespace === namespace && field2.key === key);
|
|
2176
2607
|
if (!metafield) {
|
|
2177
2608
|
setText(el, "");
|
|
2178
2609
|
return;
|
|
@@ -2205,9 +2636,9 @@ ${fragment}`, {
|
|
|
2205
2636
|
});
|
|
2206
2637
|
}
|
|
2207
2638
|
function renderProductBind(el, key, product) {
|
|
2208
|
-
const
|
|
2209
|
-
if (
|
|
2210
|
-
setText(el,
|
|
2639
|
+
const text2 = PRODUCT_TEXTS.get(key);
|
|
2640
|
+
if (text2) {
|
|
2641
|
+
setText(el, text2(product));
|
|
2211
2642
|
return;
|
|
2212
2643
|
}
|
|
2213
2644
|
switch (key) {
|
|
@@ -2218,10 +2649,12 @@ ${fragment}`, {
|
|
|
2218
2649
|
setImage(el, product.featuredImage?.url, product.featuredImage?.altText || product.title);
|
|
2219
2650
|
break;
|
|
2220
2651
|
case "url":
|
|
2221
|
-
case "link":
|
|
2222
|
-
|
|
2652
|
+
case "link": {
|
|
2653
|
+
const url2 = productUrl2(product.handle, product.id);
|
|
2654
|
+
if (url2) el.setAttribute("href", url2);
|
|
2223
2655
|
if (key === "url" && !el.firstElementChild && !el.textContent.trim()) setText(el, product.title);
|
|
2224
2656
|
break;
|
|
2657
|
+
}
|
|
2225
2658
|
case "images":
|
|
2226
2659
|
renderGallery(el, product);
|
|
2227
2660
|
break;
|
|
@@ -2252,14 +2685,11 @@ ${fragment}`, {
|
|
|
2252
2685
|
}
|
|
2253
2686
|
repeat(container, option.optionValues, (node, value) => {
|
|
2254
2687
|
node.setAttribute(attr("option-value"), value.name);
|
|
2255
|
-
const
|
|
2256
|
-
if (
|
|
2688
|
+
const text2 = qsa(node, sel("bind", "option-value"))[0];
|
|
2689
|
+
if (text2) setText(text2, value.name);
|
|
2257
2690
|
else if (!node.firstElementChild) setText(node, value.name);
|
|
2258
2691
|
const swatchEl = qsa(node, sel("bind", "swatch"))[0];
|
|
2259
|
-
const swatch =
|
|
2260
|
-
/** @type {Swatch} */
|
|
2261
|
-
value.swatch
|
|
2262
|
-
);
|
|
2692
|
+
const { swatch } = value;
|
|
2263
2693
|
if (!swatchEl || !swatch) return;
|
|
2264
2694
|
if (swatch.color) swatchEl.style.backgroundColor = swatch.color;
|
|
2265
2695
|
if (swatch.image?.previewImage) setImage(swatchEl, swatch.image.previewImage.url, value.name);
|
|
@@ -2306,16 +2736,7 @@ ${fragment}`, {
|
|
|
2306
2736
|
renderPlanLists(scope);
|
|
2307
2737
|
}
|
|
2308
2738
|
|
|
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
|
-
});
|
|
2739
|
+
// src/status.ts
|
|
2319
2740
|
var URL_SOURCE = "url:";
|
|
2320
2741
|
function decodePathSegment(segment) {
|
|
2321
2742
|
try {
|
|
@@ -2325,17 +2746,17 @@ ${fragment}`, {
|
|
|
2325
2746
|
throw error;
|
|
2326
2747
|
}
|
|
2327
2748
|
}
|
|
2328
|
-
function resolveProductKey(el,
|
|
2749
|
+
function resolveProductKey(el, location2 = window.location, name = "product") {
|
|
2329
2750
|
const raw = (get(el, name) ?? "").trim();
|
|
2330
2751
|
if (!raw.toLowerCase().startsWith(URL_SOURCE)) return { key: raw || null, fromUrl: false, raw };
|
|
2331
2752
|
const source = raw.slice(URL_SOURCE.length).trim();
|
|
2332
2753
|
let key;
|
|
2333
2754
|
if (source.toLowerCase() === "path") {
|
|
2334
|
-
const segments =
|
|
2755
|
+
const segments = location2.pathname.split("/").filter(Boolean);
|
|
2335
2756
|
const segment = segments[segments.length - 1];
|
|
2336
2757
|
key = segment === void 0 ? null : decodePathSegment(segment);
|
|
2337
2758
|
} else {
|
|
2338
|
-
key = new URLSearchParams(
|
|
2759
|
+
key = new URLSearchParams(location2.search).get(source || "handle");
|
|
2339
2760
|
}
|
|
2340
2761
|
return { key: key?.trim() || null, fromUrl: true, raw };
|
|
2341
2762
|
}
|
|
@@ -2367,7 +2788,7 @@ ${fragment}`, {
|
|
|
2367
2788
|
});
|
|
2368
2789
|
}
|
|
2369
2790
|
|
|
2370
|
-
// src/variant-render.
|
|
2791
|
+
// src/variant-render.ts
|
|
2371
2792
|
var FORMAT = { money: formatMoney2, percent: formatPercent };
|
|
2372
2793
|
var isInput2 = (el) => el.tagName === "INPUT";
|
|
2373
2794
|
function addButtonLabel(button) {
|
|
@@ -2400,7 +2821,7 @@ ${fragment}`, {
|
|
|
2400
2821
|
});
|
|
2401
2822
|
}
|
|
2402
2823
|
function renderPurchaseStates(scope, view) {
|
|
2403
|
-
const { variant, purchase,
|
|
2824
|
+
const { variant, purchase, line } = view;
|
|
2404
2825
|
scope.$(sel("purchase")).forEach((el) => {
|
|
2405
2826
|
if (isSelect(el)) {
|
|
2406
2827
|
el.value = purchase.kind;
|
|
@@ -2413,17 +2834,17 @@ ${fragment}`, {
|
|
|
2413
2834
|
});
|
|
2414
2835
|
scope.$(sel("plan-value")).forEach((node) => {
|
|
2415
2836
|
const planId = get(node, "plan-value") ?? "";
|
|
2416
|
-
setSelected(node,
|
|
2837
|
+
setSelected(node, line.kind === "subscription" && planId === line.planId);
|
|
2417
2838
|
node.classList.toggle("is-unavailable", !allocationFor(variant, planId));
|
|
2418
2839
|
const plan = scope.plans.find((candidate) => candidate.id === planId);
|
|
2419
2840
|
const texts = planBindTexts(variant, planId, plan, FORMAT);
|
|
2420
2841
|
qsa(node, sel("plan-bind")).forEach((el) => {
|
|
2421
|
-
const
|
|
2422
|
-
if (
|
|
2842
|
+
const text2 = texts.get(get(el, "plan-bind") ?? "");
|
|
2843
|
+
if (text2) setText(el, text2());
|
|
2423
2844
|
});
|
|
2424
2845
|
});
|
|
2425
2846
|
selectsIn(scope, sel("plan")).forEach((select) => {
|
|
2426
|
-
select.value =
|
|
2847
|
+
select.value = line.kind === "subscription" ? line.planId : "";
|
|
2427
2848
|
});
|
|
2428
2849
|
}
|
|
2429
2850
|
function renderConditions2(scope, view) {
|
|
@@ -2447,10 +2868,10 @@ ${fragment}`, {
|
|
|
2447
2868
|
const subscribeText = get(button, "subscribe-text");
|
|
2448
2869
|
if (!soldOutText && !subscribeText) return;
|
|
2449
2870
|
const { label, original } = addButtonLabel(button);
|
|
2450
|
-
let
|
|
2451
|
-
if (view.
|
|
2452
|
-
if (disabled && soldOutText)
|
|
2453
|
-
if (button.getAttribute("aria-busy") !== "true") setText(label,
|
|
2871
|
+
let text2 = original;
|
|
2872
|
+
if (view.line.kind === "subscription" && subscribeText) text2 = subscribeText;
|
|
2873
|
+
if (disabled && soldOutText) text2 = soldOutText;
|
|
2874
|
+
if (button.getAttribute("aria-busy") !== "true") setText(label, text2);
|
|
2454
2875
|
});
|
|
2455
2876
|
}
|
|
2456
2877
|
function writeVariant(scope, view) {
|
|
@@ -2463,32 +2884,29 @@ ${fragment}`, {
|
|
|
2463
2884
|
setImage(el, variant.image?.url || product.featuredImage?.url, variant.image?.altText || product.title);
|
|
2464
2885
|
return;
|
|
2465
2886
|
}
|
|
2466
|
-
const
|
|
2467
|
-
if (
|
|
2887
|
+
const text2 = texts.get(key);
|
|
2888
|
+
if (text2) setText(el, text2());
|
|
2468
2889
|
});
|
|
2469
2890
|
renderOptionStates(scope, view);
|
|
2470
2891
|
renderPurchaseStates(scope, view);
|
|
2471
2892
|
renderConditions2(scope, view);
|
|
2472
|
-
scope.root.classList.toggle("is-subscription", view.
|
|
2473
|
-
scope.root.setAttribute(attr("purchase-mode"), view.
|
|
2893
|
+
scope.root.classList.toggle("is-subscription", view.line.kind === "subscription");
|
|
2894
|
+
scope.root.setAttribute(attr("purchase-mode"), view.line.kind);
|
|
2474
2895
|
renderAddButtons(scope, view);
|
|
2475
2896
|
}
|
|
2476
2897
|
|
|
2477
|
-
// src/product-events.
|
|
2898
|
+
// src/product-events.ts
|
|
2478
2899
|
var productSelector = () => sel("product");
|
|
2479
2900
|
var isField = (el) => el.tagName === "INPUT" || el.tagName === "SELECT" || el.tagName === "TEXTAREA";
|
|
2480
2901
|
var isElement = (target) => target instanceof window.Element;
|
|
2481
2902
|
function closestOwn(scope, target, selector) {
|
|
2482
|
-
const el = (
|
|
2483
|
-
/** @type {HTMLElement | null} */
|
|
2484
|
-
target.closest(selector)
|
|
2485
|
-
);
|
|
2903
|
+
const el = target.closest(selector);
|
|
2486
2904
|
return el?.closest(productSelector()) === scope.root ? el : null;
|
|
2487
2905
|
}
|
|
2488
2906
|
function onAddClick(scope, button) {
|
|
2489
2907
|
const { label, original } = addButtonLabel(button);
|
|
2490
2908
|
const addingText = get(button, "adding-text");
|
|
2491
|
-
|
|
2909
|
+
busy(button, async () => {
|
|
2492
2910
|
if (addingText) setText(label, addingText);
|
|
2493
2911
|
try {
|
|
2494
2912
|
await scope.addToCart();
|
|
@@ -2500,6 +2918,8 @@ ${fragment}`, {
|
|
|
2500
2918
|
}
|
|
2501
2919
|
}).then(() => {
|
|
2502
2920
|
scope.renderVariant();
|
|
2921
|
+
}).catch((error) => {
|
|
2922
|
+
reportError(error, "add to cart render");
|
|
2503
2923
|
});
|
|
2504
2924
|
}
|
|
2505
2925
|
function onClick(scope, event, target) {
|
|
@@ -2519,7 +2939,7 @@ ${fragment}`, {
|
|
|
2519
2939
|
const purchase = closestOwn(scope, target, `${sel("purchase")}:not(select)`);
|
|
2520
2940
|
if (purchase) {
|
|
2521
2941
|
if (purchase.tagName !== "INPUT") event.preventDefault();
|
|
2522
|
-
scope.setPurchase(get(purchase, "purchase"));
|
|
2942
|
+
scope.setPurchase(purchaseKind(get(purchase, "purchase")));
|
|
2523
2943
|
return;
|
|
2524
2944
|
}
|
|
2525
2945
|
const plus = closestOwn(scope, target, sel("qty-plus"));
|
|
@@ -2535,6 +2955,7 @@ ${fragment}`, {
|
|
|
2535
2955
|
onAddClick(scope, add);
|
|
2536
2956
|
}
|
|
2537
2957
|
}
|
|
2958
|
+
var purchaseKind = (value) => value === "subscription" ? "subscription" : "one-time";
|
|
2538
2959
|
function onChange(scope, target) {
|
|
2539
2960
|
const value = isField(target) ? target.value : "";
|
|
2540
2961
|
if (target.matches(`select${sel("option")}`)) {
|
|
@@ -2545,7 +2966,7 @@ ${fragment}`, {
|
|
|
2545
2966
|
if (value) scope.selectPlan(value);
|
|
2546
2967
|
else scope.setPurchase("one-time");
|
|
2547
2968
|
} else if (target.matches(`select${sel("purchase")}`)) {
|
|
2548
|
-
scope.setPurchase(value);
|
|
2969
|
+
scope.setPurchase(purchaseKind(value));
|
|
2549
2970
|
} else if (target.matches(sel("qty"))) {
|
|
2550
2971
|
scope.setQuantity(value);
|
|
2551
2972
|
}
|
|
@@ -2560,7 +2981,7 @@ ${fragment}`, {
|
|
|
2560
2981
|
});
|
|
2561
2982
|
}
|
|
2562
2983
|
|
|
2563
|
-
// src/product.
|
|
2984
|
+
// src/product.ts
|
|
2564
2985
|
var scopes = /* @__PURE__ */ new WeakMap();
|
|
2565
2986
|
var productSelector2 = () => sel("product");
|
|
2566
2987
|
var knownOptionNames = /* @__PURE__ */ new Map();
|
|
@@ -2585,11 +3006,15 @@ ${fragment}`, {
|
|
|
2585
3006
|
return isField2(el) && el.value || null;
|
|
2586
3007
|
}
|
|
2587
3008
|
var ProductScope = class {
|
|
2588
|
-
/**
|
|
2589
|
-
* @param {HTMLElement} root
|
|
2590
|
-
* @param {Product} product
|
|
2591
|
-
*/
|
|
2592
3009
|
constructor(root, product) {
|
|
3010
|
+
__publicField(this, "root");
|
|
3011
|
+
__publicField(this, "product");
|
|
3012
|
+
__publicField(this, "plans");
|
|
3013
|
+
__publicField(this, "selected");
|
|
3014
|
+
/** How the visitor buys; `setPurchase` and `selectPlan` change it. */
|
|
3015
|
+
__publicField(this, "purchase");
|
|
3016
|
+
__publicField(this, "variant");
|
|
3017
|
+
__publicField(this, "quantity");
|
|
2593
3018
|
this.root = root;
|
|
2594
3019
|
this.product = product;
|
|
2595
3020
|
this.plans = allPlans(product);
|
|
@@ -2601,7 +3026,7 @@ ${fragment}`, {
|
|
|
2601
3026
|
defaultPurchase: get(root, "default-purchase") || cfg.defaultPurchase
|
|
2602
3027
|
});
|
|
2603
3028
|
this.selected = choice.selected;
|
|
2604
|
-
this.
|
|
3029
|
+
this.purchase = choice.purchase;
|
|
2605
3030
|
this.variant = null;
|
|
2606
3031
|
this.quantity = has(root, "quantity") ? Math.max(0, parseInt(get(root, "quantity") ?? "", 10) || 0) : 1;
|
|
2607
3032
|
scopes.set(root, this);
|
|
@@ -2614,11 +3039,7 @@ ${fragment}`, {
|
|
|
2614
3039
|
notifyLayoutChanged("product");
|
|
2615
3040
|
emit("product-ready", { product, scope: this }, root);
|
|
2616
3041
|
}
|
|
2617
|
-
/**
|
|
2618
|
-
* New data for the same product (a country switch). Keeps the visitor's choice.
|
|
2619
|
-
*
|
|
2620
|
-
* @param {Product} product
|
|
2621
|
-
*/
|
|
3042
|
+
/** New data for the same product (a country switch). Keeps the visitor's choice. */
|
|
2622
3043
|
update(product) {
|
|
2623
3044
|
const variantId = this.variant?.id;
|
|
2624
3045
|
this.product = product;
|
|
@@ -2632,17 +3053,14 @@ ${fragment}`, {
|
|
|
2632
3053
|
notifyLayoutChanged("product");
|
|
2633
3054
|
emit("product-updated", { product, scope: this }, this.root);
|
|
2634
3055
|
}
|
|
2635
|
-
/**
|
|
2636
|
-
* @param {string} selector
|
|
2637
|
-
* @returns {HTMLElement[]} Matches in this scope, including the root itself (a card link with tether-bind="link").
|
|
2638
|
-
*/
|
|
3056
|
+
/** @returns Matches in this scope, including the root itself (a card link with tether-bind="link"). */
|
|
2639
3057
|
$(selector) {
|
|
2640
3058
|
const inner = scoped(this.root, selector, productSelector2());
|
|
2641
3059
|
return this.root.matches(selector) ? [this.root, ...inner] : inner;
|
|
2642
3060
|
}
|
|
2643
3061
|
/**
|
|
2644
|
-
* @param
|
|
2645
|
-
* @returns
|
|
3062
|
+
* @param authored An option name in the markup.
|
|
3063
|
+
* @returns The product's option of that name, or at that position in the build's language.
|
|
2646
3064
|
*/
|
|
2647
3065
|
optName(authored) {
|
|
2648
3066
|
if (!authored) return "";
|
|
@@ -2651,50 +3069,31 @@ ${fragment}`, {
|
|
|
2651
3069
|
const index = (knownOptionNames.get(this.product.handle) ?? []).indexOf(authored);
|
|
2652
3070
|
return options[index]?.name ?? authored;
|
|
2653
3071
|
}
|
|
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
3072
|
get plan() {
|
|
2673
|
-
|
|
3073
|
+
const planId = purchasePlanId(this.purchase);
|
|
3074
|
+
return this.plans.find((plan) => plan.id === planId) ?? null;
|
|
2674
3075
|
}
|
|
2675
3076
|
/** True when the current variant can be bought on a plan. */
|
|
2676
3077
|
get subscriptionAvailable() {
|
|
2677
3078
|
return this.plans.some((plan) => allocationFor(this.variant, plan.id));
|
|
2678
3079
|
}
|
|
3080
|
+
/** True when the line is bought on a plan: subscribing, and the variant is on the plan. */
|
|
2679
3081
|
get isSubscription() {
|
|
2680
|
-
return this.purchase
|
|
3082
|
+
return linePurchase(this.purchase, this.variant).kind === "subscription";
|
|
2681
3083
|
}
|
|
2682
|
-
/** @returns
|
|
3084
|
+
/** @returns What would be added to the cart right now. */
|
|
2683
3085
|
getLine() {
|
|
2684
3086
|
if (!this.variant) return null;
|
|
2685
3087
|
const line = { merchandiseId: this.variant.id, quantity: this.quantity };
|
|
2686
|
-
|
|
3088
|
+
const bought = linePurchase(this.purchase, this.variant);
|
|
3089
|
+
if (bought.kind === "subscription") line.sellingPlanId = bought.planId;
|
|
2687
3090
|
const attributes = this.collectAttributes();
|
|
2688
3091
|
if (attributes.length) line.attributes = attributes;
|
|
2689
3092
|
return line;
|
|
2690
3093
|
}
|
|
2691
|
-
/** @returns
|
|
3094
|
+
/** @returns `tether-line-attributes` and the filled `tether-line-attribute` fields. */
|
|
2692
3095
|
collectAttributes() {
|
|
2693
|
-
const
|
|
2694
|
-
/** @type {Parameters<typeof toAttributes>[0]} */
|
|
2695
|
-
parseJSONAttr(this.root, "line-attributes")
|
|
2696
|
-
);
|
|
2697
|
-
const fixed = toAttributes(json);
|
|
3096
|
+
const fixed = attributesFromJson(parseJSONAttr(this.root, "line-attributes"));
|
|
2698
3097
|
const fields = this.$(sel("line-attribute")).flatMap((el) => {
|
|
2699
3098
|
const value = lineAttributeValue(el);
|
|
2700
3099
|
return value === null ? [] : [{ key: get(el, "line-attribute") ?? "", value }];
|
|
@@ -2704,9 +3103,6 @@ ${fragment}`, {
|
|
|
2704
3103
|
/**
|
|
2705
3104
|
* Selects an option value. A combination no variant has moves to the closest variant with the value: the most other
|
|
2706
3105
|
* choices kept, then one for sale.
|
|
2707
|
-
*
|
|
2708
|
-
* @param {string} name
|
|
2709
|
-
* @param {string} value
|
|
2710
3106
|
*/
|
|
2711
3107
|
select(name, value) {
|
|
2712
3108
|
if (this.selected[name] === value) return;
|
|
@@ -2732,29 +3128,26 @@ ${fragment}`, {
|
|
|
2732
3128
|
this.selected = next;
|
|
2733
3129
|
this.renderVariant();
|
|
2734
3130
|
}
|
|
2735
|
-
/** @param
|
|
3131
|
+
/** @param id A variant gid or numeric id. */
|
|
2736
3132
|
selectVariant(id) {
|
|
2737
3133
|
const variant = this.product.variants.nodes.find((candidate) => candidate.id === GID.variant(id));
|
|
2738
3134
|
if (!variant) return;
|
|
2739
3135
|
this.selected = selectionOf(variant);
|
|
2740
3136
|
this.renderVariant();
|
|
2741
3137
|
}
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
if (next.kind === this.purchase) return;
|
|
2747
|
-
this.purchaseChoice = next;
|
|
3138
|
+
setPurchase(kind) {
|
|
3139
|
+
const next = normalisePurchase(this.product, this.variant, kind, purchasePlanId(this.purchase));
|
|
3140
|
+
if (next.kind === this.purchase.kind) return;
|
|
3141
|
+
this.purchase = next;
|
|
2748
3142
|
this.renderVariant();
|
|
2749
3143
|
}
|
|
2750
|
-
/** @param
|
|
3144
|
+
/** @param ref A plan id, numeric id, name or option value. */
|
|
2751
3145
|
selectPlan(ref) {
|
|
2752
3146
|
const plan = findPlan(this.product, ref);
|
|
2753
3147
|
if (!plan) return;
|
|
2754
|
-
this.
|
|
3148
|
+
this.purchase = { kind: "subscription", planId: plan.id };
|
|
2755
3149
|
this.renderVariant();
|
|
2756
3150
|
}
|
|
2757
|
-
/** @param {number | string} quantity */
|
|
2758
3151
|
setQuantity(quantity) {
|
|
2759
3152
|
const minimum = has(this.root, "quantity") ? 0 : 1;
|
|
2760
3153
|
this.quantity = Math.max(minimum, parseInt(String(quantity), 10) || 0);
|
|
@@ -2763,7 +3156,7 @@ ${fragment}`, {
|
|
|
2763
3156
|
});
|
|
2764
3157
|
this.renderVariant();
|
|
2765
3158
|
}
|
|
2766
|
-
/** @returns
|
|
3159
|
+
/** @returns The cart after adding; `null` when the variant can't be added. */
|
|
2767
3160
|
async addToCart() {
|
|
2768
3161
|
if (!this.variant?.availableForSale) return null;
|
|
2769
3162
|
const line = this.getLine();
|
|
@@ -2789,7 +3182,7 @@ ${fragment}`, {
|
|
|
2789
3182
|
renderVariant() {
|
|
2790
3183
|
const view = productView(
|
|
2791
3184
|
this.product,
|
|
2792
|
-
{ selected: this.selected, purchase: this.
|
|
3185
|
+
{ selected: this.selected, purchase: this.purchase, quantity: this.quantity },
|
|
2793
3186
|
cfg.lowStock
|
|
2794
3187
|
);
|
|
2795
3188
|
if (!view) {
|
|
@@ -2798,7 +3191,7 @@ ${fragment}`, {
|
|
|
2798
3191
|
}
|
|
2799
3192
|
this.variant = view.variant;
|
|
2800
3193
|
this.selected = view.selected;
|
|
2801
|
-
this.
|
|
3194
|
+
this.purchase = view.purchase;
|
|
2802
3195
|
writeVariant(this, view);
|
|
2803
3196
|
emit(
|
|
2804
3197
|
"variant-changed",
|
|
@@ -2807,32 +3200,28 @@ ${fragment}`, {
|
|
|
2807
3200
|
variant: view.variant,
|
|
2808
3201
|
selected: { ...view.selected },
|
|
2809
3202
|
purchase: view.purchase.kind,
|
|
2810
|
-
sellingPlanId: view.
|
|
3203
|
+
sellingPlanId: view.line.kind === "subscription" ? view.line.planId : null
|
|
2811
3204
|
},
|
|
2812
3205
|
this.root
|
|
2813
3206
|
);
|
|
2814
3207
|
}
|
|
2815
3208
|
};
|
|
2816
3209
|
|
|
2817
|
-
// src/collection.
|
|
2818
|
-
var collection_exports = {};
|
|
2819
|
-
__export(collection_exports, {
|
|
2820
|
-
ListScope: () => ListScope,
|
|
2821
|
-
listSelector: () => listSelector
|
|
2822
|
-
});
|
|
3210
|
+
// src/collection.ts
|
|
2823
3211
|
var URL_QUERY_SOURCE = "url:";
|
|
2824
3212
|
var listSelector = () => `${sel("collection")},${sel("products")},${sel("recommendations")}`;
|
|
2825
3213
|
var isElement2 = (target) => target instanceof window.Element;
|
|
2826
3214
|
var toLimit = (raw) => parseInt(raw ?? "", 10) || void 0;
|
|
2827
3215
|
var toIntent = (raw) => raw === "COMPLEMENTARY" ? "COMPLEMENTARY" : "RELATED";
|
|
2828
3216
|
var ListScope = class {
|
|
2829
|
-
/** @param {HTMLElement} root */
|
|
2830
3217
|
constructor(root) {
|
|
3218
|
+
__publicField(this, "root");
|
|
3219
|
+
__publicField(this, "products", []);
|
|
3220
|
+
__publicField(this, "pageInfo", null);
|
|
3221
|
+
__publicField(this, "collection", null);
|
|
3222
|
+
/** Counts loads, so only the latest one renders. */
|
|
3223
|
+
__publicField(this, "loadToken", 0);
|
|
2831
3224
|
this.root = root;
|
|
2832
|
-
this.products = [];
|
|
2833
|
-
this.pageInfo = null;
|
|
2834
|
-
this.collection = null;
|
|
2835
|
-
this.loadToken = 0;
|
|
2836
3225
|
setScopeStatus(root, "loading");
|
|
2837
3226
|
root.addEventListener("click", (event) => {
|
|
2838
3227
|
if (!isElement2(event.target)) return;
|
|
@@ -2842,31 +3231,29 @@ ${fragment}`, {
|
|
|
2842
3231
|
void busy(more, () => this.load(true));
|
|
2843
3232
|
});
|
|
2844
3233
|
}
|
|
2845
|
-
/** @param {string} selector */
|
|
2846
3234
|
$(selector) {
|
|
2847
3235
|
return scoped(this.root, selector, listSelector());
|
|
2848
3236
|
}
|
|
2849
|
-
/**
|
|
3237
|
+
/** The collection or search query of the list's attributes. */
|
|
2850
3238
|
options() {
|
|
2851
3239
|
const { root } = this;
|
|
2852
|
-
const
|
|
3240
|
+
const query2 = { reverse: get(root, "reverse") === "true" };
|
|
2853
3241
|
const first = toLimit(get(root, "limit"));
|
|
2854
|
-
if (first)
|
|
3242
|
+
if (first) query2.first = first;
|
|
2855
3243
|
const sortKey = get(root, "sort");
|
|
2856
|
-
if (sortKey)
|
|
3244
|
+
if (sortKey) query2.sortKey = sortKey;
|
|
2857
3245
|
if (has(root, "collection")) {
|
|
2858
|
-
|
|
2859
|
-
return
|
|
3246
|
+
query2.collection = get(root, "collection") ?? "";
|
|
3247
|
+
return query2;
|
|
2860
3248
|
}
|
|
2861
3249
|
const raw = get(root, "products") ?? "";
|
|
2862
|
-
|
|
2863
|
-
return
|
|
3250
|
+
query2.query = raw.startsWith(URL_QUERY_SOURCE) ? new URL(window.location.href).searchParams.get(raw.slice(URL_QUERY_SOURCE.length)) || "" : raw;
|
|
3251
|
+
return query2;
|
|
2864
3252
|
}
|
|
2865
3253
|
/**
|
|
2866
3254
|
* The product of a recommendations list: a handle, `url:…` (one dynamic product page), or empty inside a product
|
|
2867
3255
|
* scope for that product.
|
|
2868
3256
|
*
|
|
2869
|
-
* @returns {Promise<Product[]>}
|
|
2870
3257
|
* @throws {TetherError} `NOT_FOUND` when there is no product to recommend for.
|
|
2871
3258
|
*/
|
|
2872
3259
|
async loadRecommendations() {
|
|
@@ -2876,7 +3263,7 @@ ${fragment}`, {
|
|
|
2876
3263
|
if (!key) throw new TetherError("No product for recommendations", { code: "NOT_FOUND" });
|
|
2877
3264
|
return getRecommendations(key, toIntent(get(this.root, "intent")));
|
|
2878
3265
|
}
|
|
2879
|
-
/** @param
|
|
3266
|
+
/** @param more Appends the next page instead of replacing the products. */
|
|
2880
3267
|
async load(more = false) {
|
|
2881
3268
|
this.loadToken += 1;
|
|
2882
3269
|
const token = this.loadToken;
|
|
@@ -2942,21 +3329,7 @@ ${fragment}`, {
|
|
|
2942
3329
|
}
|
|
2943
3330
|
};
|
|
2944
3331
|
|
|
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
|
-
});
|
|
3332
|
+
// src/mounting.ts
|
|
2960
3333
|
var HYDRATE_SELECTOR = 'script[type="application/json"][data-tether-hydrate]';
|
|
2961
3334
|
var mountedProducts = /* @__PURE__ */ new WeakSet();
|
|
2962
3335
|
var mountedLists = /* @__PURE__ */ new WeakSet();
|
|
@@ -2972,10 +3345,7 @@ ${fragment}`, {
|
|
|
2972
3345
|
});
|
|
2973
3346
|
}
|
|
2974
3347
|
function within(root, selector) {
|
|
2975
|
-
const self = root.matches?.(selector) ? [
|
|
2976
|
-
/** @type {HTMLElement} */
|
|
2977
|
-
root
|
|
2978
|
-
] : [];
|
|
3348
|
+
const self = root.matches?.(selector) ? [root] : [];
|
|
2979
3349
|
return [...self, ...qsa(root, selector)];
|
|
2980
3350
|
}
|
|
2981
3351
|
function applyPageTitle(el, product) {
|
|
@@ -3056,12 +3426,8 @@ ${fragment}`, {
|
|
|
3056
3426
|
function flushRefresh() {
|
|
3057
3427
|
const keys = [...refreshQueue];
|
|
3058
3428
|
refreshQueue = /* @__PURE__ */ new Set();
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
/** @type {string} */
|
|
3062
|
-
keys[index]
|
|
3063
|
-
);
|
|
3064
|
-
promise.then((fresh) => {
|
|
3429
|
+
keys.forEach((key) => {
|
|
3430
|
+
refreshProduct(key).then((fresh) => {
|
|
3065
3431
|
applyRefresh(key, fresh);
|
|
3066
3432
|
}).catch((error) => {
|
|
3067
3433
|
reportError(error, `refresh ${key}`);
|
|
@@ -3086,11 +3452,19 @@ ${fragment}`, {
|
|
|
3086
3452
|
document.documentElement.classList.remove(`${cfg.prefix}-market-pending`);
|
|
3087
3453
|
});
|
|
3088
3454
|
}
|
|
3089
|
-
|
|
3090
|
-
|
|
3455
|
+
function toHydratePayload(json) {
|
|
3456
|
+
if (!isRecord(json)) return { country: void 0, language: void 0, products: [] };
|
|
3457
|
+
const { products } = json;
|
|
3458
|
+
const entries = Array.isArray(products) ? products : [];
|
|
3459
|
+
return {
|
|
3460
|
+
country: toCountry(json["country"]) ?? void 0,
|
|
3461
|
+
language: toLanguage(json["language"]) ?? void 0,
|
|
3462
|
+
products: entries.filter(isProductShape)
|
|
3463
|
+
};
|
|
3464
|
+
}
|
|
3091
3465
|
var readScripts = /* @__PURE__ */ new WeakSet();
|
|
3092
3466
|
function hydrateFromPage(root = document) {
|
|
3093
|
-
|
|
3467
|
+
within(root, HYDRATE_SELECTOR).forEach((script) => {
|
|
3094
3468
|
if (readScripts.has(script)) return;
|
|
3095
3469
|
readScripts.add(script);
|
|
3096
3470
|
let json;
|
|
@@ -3100,14 +3474,10 @@ ${fragment}`, {
|
|
|
3100
3474
|
warn("bad data-tether-hydrate", error);
|
|
3101
3475
|
return;
|
|
3102
3476
|
}
|
|
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);
|
|
3477
|
+
const { country, language, products } = toHydratePayload(json);
|
|
3478
|
+
rememberOptionNames(products);
|
|
3479
|
+
if (country === cfg.country && language === cfg.language) {
|
|
3480
|
+
hydrateProducts(products);
|
|
3111
3481
|
return;
|
|
3112
3482
|
}
|
|
3113
3483
|
log("hydration skipped: other market");
|
|
@@ -3118,93 +3488,12 @@ ${fragment}`, {
|
|
|
3118
3488
|
});
|
|
3119
3489
|
}
|
|
3120
3490
|
|
|
3121
|
-
// src/
|
|
3122
|
-
var { cfg: cfg2 } = config_exports;
|
|
3123
|
-
var coreModules = {
|
|
3124
|
-
"config.js": config_exports,
|
|
3125
|
-
"client.js": client_exports,
|
|
3126
|
-
"dom.js": dom_exports,
|
|
3127
|
-
"fragments.js": fragments_exports,
|
|
3128
|
-
"loader.js": loader_exports,
|
|
3129
|
-
"selling-plans.js": selling_plans_exports,
|
|
3130
|
-
"status.js": status_exports,
|
|
3131
|
-
"cart.js": cart_exports,
|
|
3132
|
-
"product.js": product_exports,
|
|
3133
|
-
"collection.js": collection_exports,
|
|
3134
|
-
"mounting.js": mounting_exports,
|
|
3135
|
-
"market-codes.js": market_codes_exports,
|
|
3136
|
-
"shopify-values.js": shopify_values_exports,
|
|
3137
|
-
"store-html.js": store_html_exports
|
|
3138
|
-
};
|
|
3139
|
-
var MINIFIED_SCRIPT = /\.min\.js(\?|$)/;
|
|
3140
|
-
var coreScript = typeof document === "undefined" ? "" : document.currentScript?.getAttribute("src") ?? "";
|
|
3141
|
-
var coreScriptUrl = coreScript ? new URL(coreScript, document.baseURI).href : "";
|
|
3142
|
-
var parts = /* @__PURE__ */ new Map();
|
|
3143
|
-
function partsFolder() {
|
|
3144
|
-
if (!cfg2.partsUrl) return coreScriptUrl.replace(/[^/]*$/, "");
|
|
3145
|
-
const folder = new URL(cfg2.partsUrl, document.baseURI);
|
|
3146
|
-
if (folder.protocol !== "https:" && folder.origin !== window.location.origin) {
|
|
3147
|
-
throw new Error(`partsUrl must be https or on this site: ${folder.href}`);
|
|
3148
|
-
}
|
|
3149
|
-
return folder.href;
|
|
3150
|
-
}
|
|
3151
|
-
function loadScript(name, url) {
|
|
3152
|
-
return new Promise((resolve, reject) => {
|
|
3153
|
-
const script = document.createElement("script");
|
|
3154
|
-
script.src = url;
|
|
3155
|
-
script.async = true;
|
|
3156
|
-
script.onload = () => {
|
|
3157
|
-
const { Tether } = (
|
|
3158
|
-
/** @type {Window & { Tether?: { __parts?: Record<string, unknown> } }} */
|
|
3159
|
-
window
|
|
3160
|
-
);
|
|
3161
|
-
const part = Tether?.__parts?.[name];
|
|
3162
|
-
if (part) resolve(part);
|
|
3163
|
-
else reject(new Error(`${url} did not register`));
|
|
3164
|
-
};
|
|
3165
|
-
script.onerror = () => {
|
|
3166
|
-
parts.delete(name);
|
|
3167
|
-
reject(new Error(`Could not load ${url}. Host it next to the Tether script, or set partsUrl.`));
|
|
3168
|
-
};
|
|
3169
|
-
document.head.appendChild(script);
|
|
3170
|
-
});
|
|
3171
|
-
}
|
|
3172
|
-
function loadPart(name) {
|
|
3173
|
-
let loading = parts.get(name);
|
|
3174
|
-
if (!loading) {
|
|
3175
|
-
let folder;
|
|
3176
|
-
try {
|
|
3177
|
-
folder = partsFolder();
|
|
3178
|
-
} catch (error) {
|
|
3179
|
-
return Promise.reject(error instanceof Error ? error : new Error(String(error)));
|
|
3180
|
-
}
|
|
3181
|
-
const suffix = MINIFIED_SCRIPT.test(coreScriptUrl) || !coreScriptUrl ? ".min" : "";
|
|
3182
|
-
loading = loadScript(name, `${folder.replace(/\/?$/, "/")}tether.${name}${suffix}.js`);
|
|
3183
|
-
parts.set(name, loading);
|
|
3184
|
-
}
|
|
3185
|
-
return (
|
|
3186
|
-
/** @type {Promise<Parts[Name]>} */
|
|
3187
|
-
loading
|
|
3188
|
-
);
|
|
3189
|
-
}
|
|
3190
|
-
coreModules["lazy.js"] = { loadPart, coreModules };
|
|
3191
|
-
|
|
3192
|
-
// src/markets.js
|
|
3491
|
+
// src/markets.ts
|
|
3193
3492
|
var storageKey = () => `${cfg.prefix}:market:${cfg.domain}`;
|
|
3194
|
-
|
|
3195
|
-
|
|
3493
|
+
var pageStorage = { getItem: (key) => getStored(key) };
|
|
3494
|
+
function parseJsonOrNull(text2) {
|
|
3196
3495
|
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
|
-
);
|
|
3496
|
+
return JSON.parse(text2);
|
|
3208
3497
|
} catch (error) {
|
|
3209
3498
|
if (error instanceof SyntaxError) return null;
|
|
3210
3499
|
throw error;
|
|
@@ -3214,22 +3503,19 @@ ${fragment}`, {
|
|
|
3214
3503
|
if (typeof value !== "object" || value === null) return null;
|
|
3215
3504
|
const country = toCountry(Reflect.get(value, "country"));
|
|
3216
3505
|
if (!country) return null;
|
|
3217
|
-
const locale = (
|
|
3218
|
-
/** @type {unknown} */
|
|
3219
|
-
Reflect.get(value, "locale")
|
|
3220
|
-
);
|
|
3506
|
+
const locale = Reflect.get(value, "locale");
|
|
3221
3507
|
return {
|
|
3222
3508
|
country,
|
|
3223
3509
|
language: toLanguage(Reflect.get(value, "language")) ?? void 0,
|
|
3224
3510
|
locale: typeof locale === "string" && locale ? locale : void 0
|
|
3225
3511
|
};
|
|
3226
3512
|
}
|
|
3227
|
-
function readSavedMarket(storage =
|
|
3228
|
-
const
|
|
3229
|
-
return
|
|
3513
|
+
function readSavedMarket(storage = pageStorage) {
|
|
3514
|
+
const text2 = storage.getItem(storageKey());
|
|
3515
|
+
return text2 ? toMarket(parseJsonOrNull(text2)) : null;
|
|
3230
3516
|
}
|
|
3231
3517
|
|
|
3232
|
-
// src/page-market.
|
|
3518
|
+
// src/page-market.ts
|
|
3233
3519
|
var configuredMarket = { country: void 0, language: void 0, locale: void 0 };
|
|
3234
3520
|
var lastPageHadOwnMarket = false;
|
|
3235
3521
|
function pageMarket() {
|
|
@@ -3266,21 +3552,169 @@ ${fragment}`, {
|
|
|
3266
3552
|
if (market.locale && !overrides.locale) cfg.locale = market.locale;
|
|
3267
3553
|
log("market", market);
|
|
3268
3554
|
}
|
|
3269
|
-
var
|
|
3555
|
+
var sameCode = (a, b) => (a ?? "").toUpperCase() === (b ?? "").toUpperCase();
|
|
3270
3556
|
function marketForArrival() {
|
|
3271
3557
|
const page = pageMarket();
|
|
3272
3558
|
const leftOwnMarket = page === null && lastPageHadOwnMarket;
|
|
3273
3559
|
lastPageHadOwnMarket = page !== null;
|
|
3274
3560
|
const target = page ?? (leftOwnMarket ? readSavedMarket() ?? configuredMarket : null);
|
|
3275
3561
|
if (!target?.country) return null;
|
|
3276
|
-
if (
|
|
3562
|
+
if (sameCode(target.country, cfg.country) && sameCode(target.language, cfg.language)) return null;
|
|
3277
3563
|
return { country: target.country, language: target.language, locale: target.locale };
|
|
3278
3564
|
}
|
|
3279
3565
|
function setCountry(code, options) {
|
|
3280
3566
|
return loadPart("markets").then((part) => part.setCountry(code, options));
|
|
3281
3567
|
}
|
|
3282
3568
|
|
|
3283
|
-
// src/
|
|
3569
|
+
// src/part-integrity.ts
|
|
3570
|
+
var PART_INTEGRITY = { "tether.diagnose.js": "sha384-My35ltXsPvspuBHm0xCTu7wl6C4ORwigmKQMCaTKI6VP9ELMCCLYCETpAYW3Fus+", "tether.markets.js": "sha384-m9u+yVeYzqljJYufGt6C6E2xCgqgBSJstvmjU5a2lXZLK3q2saH19EYgP/EC5ox3", "tether.search.js": "sha384-ZrByfRsWNOHbbWOocY4WOD2RWe4QI2qQRZ4Eb+fM5v/UHIQ8mlWxbSwrxJt8yLbc", "tether.extras.js": "sha384-sE3c2fclN/3LKtuYUtC4CjVk76Zu0Mv/jR3p8YYLisAvyDvqJxpx4nj8PbJ3KzXH", "tether.account.js": "sha384-UF36poTg1vnXE7Pg+FYFGMJ4qjKhx9mbOug9bqTYhzgqRppOKS9M/1uY5D/cz9KF", "tether.account-pages.js": "sha384-jJYvZswrRw+pGDYrk0FswWcYqGpZkbR6ooxJGFu0lTYkUlVOcsCXuLLLU6M5kO54", "tether.analytics.js": "sha384-10v5pBMzgfF7BuLGQBg3cdbgMk701Uto5rGjhByiRD2jI9+/WF8DFcSWplIcdRNg" };
|
|
3571
|
+
|
|
3572
|
+
// src/part-registry.ts
|
|
3573
|
+
function isTetherInternals(value) {
|
|
3574
|
+
return isRecord(value) && isRecord(value["__core"]) && isRecord(value["__parts"]);
|
|
3575
|
+
}
|
|
3576
|
+
|
|
3577
|
+
// src/lazy-iife.ts
|
|
3578
|
+
var coreModules = {
|
|
3579
|
+
get "config.ts"() {
|
|
3580
|
+
return [cfg, isConfigured, log, MARKET_SOURCES, parseMarkets, reportError, warn];
|
|
3581
|
+
},
|
|
3582
|
+
get "client.ts"() {
|
|
3583
|
+
return [endpoint, gql, orNull, shaped, TetherError];
|
|
3584
|
+
},
|
|
3585
|
+
get "dom.ts"() {
|
|
3586
|
+
return [
|
|
3587
|
+
ANALYTICS_EVENTS,
|
|
3588
|
+
attr,
|
|
3589
|
+
attributesFromJson,
|
|
3590
|
+
emit,
|
|
3591
|
+
formatDate,
|
|
3592
|
+
formatMoney2,
|
|
3593
|
+
get,
|
|
3594
|
+
has,
|
|
3595
|
+
hasTemplate,
|
|
3596
|
+
isOwnEvent,
|
|
3597
|
+
parseJSONAttr,
|
|
3598
|
+
productUrl2,
|
|
3599
|
+
qsa,
|
|
3600
|
+
repeat,
|
|
3601
|
+
sel,
|
|
3602
|
+
setImage,
|
|
3603
|
+
setText,
|
|
3604
|
+
show
|
|
3605
|
+
];
|
|
3606
|
+
},
|
|
3607
|
+
get "loader.ts"() {
|
|
3608
|
+
return [clearProductCache, getProduct];
|
|
3609
|
+
},
|
|
3610
|
+
get "selling-plans.ts"() {
|
|
3611
|
+
return [allPlans];
|
|
3612
|
+
},
|
|
3613
|
+
get "status.ts"() {
|
|
3614
|
+
return [boundary, resolveProductKey];
|
|
3615
|
+
},
|
|
3616
|
+
get "cart.ts"() {
|
|
3617
|
+
return [Cart];
|
|
3618
|
+
},
|
|
3619
|
+
get "product.ts"() {
|
|
3620
|
+
return [scopes];
|
|
3621
|
+
},
|
|
3622
|
+
get "collection.ts"() {
|
|
3623
|
+
return [listSelector];
|
|
3624
|
+
},
|
|
3625
|
+
get "mounting.ts"() {
|
|
3626
|
+
return [liveLists, liveProducts, mountedProducts, mountProduct, pruneLive, within];
|
|
3627
|
+
},
|
|
3628
|
+
get "market-codes.ts"() {
|
|
3629
|
+
return [toCountry, toLanguage];
|
|
3630
|
+
},
|
|
3631
|
+
get "page-market.ts"() {
|
|
3632
|
+
return [setCountry];
|
|
3633
|
+
},
|
|
3634
|
+
get "shopify-values.ts"() {
|
|
3635
|
+
return [isHttpsUrl];
|
|
3636
|
+
},
|
|
3637
|
+
// Parts that load other parts (account → account-pages) use this same loader.
|
|
3638
|
+
get "lazy.ts"() {
|
|
3639
|
+
return [loadPart];
|
|
3640
|
+
}
|
|
3641
|
+
};
|
|
3642
|
+
var MINIFIED_SCRIPT = /\.min\.js(\?|$)/;
|
|
3643
|
+
var coreScript = typeof document === "undefined" ? "" : document.currentScript?.getAttribute("src") ?? "";
|
|
3644
|
+
var coreScriptUrl = coreScript ? new URL(coreScript, document.baseURI).href : "";
|
|
3645
|
+
var parts = /* @__PURE__ */ new Map();
|
|
3646
|
+
function partsFolder() {
|
|
3647
|
+
if (cfg.partsUrl) return new URL(cfg.partsUrl, document.baseURI).href;
|
|
3648
|
+
if (!coreScriptUrl) return null;
|
|
3649
|
+
return coreScriptUrl.replace(/[^/]*$/, "");
|
|
3650
|
+
}
|
|
3651
|
+
function applyIntegrity(script, integrity) {
|
|
3652
|
+
script.integrity = integrity;
|
|
3653
|
+
if (new URL(script.src).origin !== window.location.origin) script.crossOrigin = "anonymous";
|
|
3654
|
+
}
|
|
3655
|
+
function loadScript(name, folder, file, integrity) {
|
|
3656
|
+
return new Promise((resolve, reject) => {
|
|
3657
|
+
const url2 = `${folder.replace(/\/?$/, "/")}${file}`;
|
|
3658
|
+
const script = document.createElement("script");
|
|
3659
|
+
script.src = url2;
|
|
3660
|
+
script.async = true;
|
|
3661
|
+
applyIntegrity(script, integrity);
|
|
3662
|
+
script.onload = () => {
|
|
3663
|
+
const tether = Reflect.get(window, "Tether");
|
|
3664
|
+
const part = isTetherInternals(tether) ? tether.__parts[name] : void 0;
|
|
3665
|
+
if (part) {
|
|
3666
|
+
resolve(part);
|
|
3667
|
+
return;
|
|
3668
|
+
}
|
|
3669
|
+
parts.delete(name);
|
|
3670
|
+
const problem = isTetherInternals(tether) ? "did not register" : "can't register: window.Tether is no longer this Tether script's, another script replaced it";
|
|
3671
|
+
reject(new Error(`${url2} ${problem}`));
|
|
3672
|
+
};
|
|
3673
|
+
script.onerror = () => {
|
|
3674
|
+
parts.delete(name);
|
|
3675
|
+
reject(
|
|
3676
|
+
new Error(
|
|
3677
|
+
`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.`
|
|
3678
|
+
)
|
|
3679
|
+
);
|
|
3680
|
+
};
|
|
3681
|
+
document.head.appendChild(script);
|
|
3682
|
+
});
|
|
3683
|
+
}
|
|
3684
|
+
function loadPart(name) {
|
|
3685
|
+
let loading = parts.get(name);
|
|
3686
|
+
if (!loading) {
|
|
3687
|
+
const folder = partsFolder();
|
|
3688
|
+
if (folder === null) {
|
|
3689
|
+
return Promise.reject(
|
|
3690
|
+
new Error(`The Tether script's own location is unknown, so its ${name} part can't be found: set partsUrl.`)
|
|
3691
|
+
);
|
|
3692
|
+
}
|
|
3693
|
+
const suffix = MINIFIED_SCRIPT.test(coreScriptUrl) || !coreScriptUrl ? ".min" : "";
|
|
3694
|
+
const file = `tether.${name}${suffix}.js`;
|
|
3695
|
+
const integrity = PART_INTEGRITY[file];
|
|
3696
|
+
if (integrity === void 0) {
|
|
3697
|
+
return Promise.reject(new Error(`This Tether script was built without ${file}, so it can't load that part.`));
|
|
3698
|
+
}
|
|
3699
|
+
loading = loadScript(name, folder, file, integrity);
|
|
3700
|
+
parts.set(name, loading);
|
|
3701
|
+
}
|
|
3702
|
+
return loading;
|
|
3703
|
+
}
|
|
3704
|
+
|
|
3705
|
+
// src/account-marks.ts
|
|
3706
|
+
var hintKey = () => `${cfg.prefix}:account-hint:${cfg.domain}`;
|
|
3707
|
+
function removeFromBothAreas(key) {
|
|
3708
|
+
removeStored(key, "sessionStorage");
|
|
3709
|
+
removeStored(key, "localStorage");
|
|
3710
|
+
}
|
|
3711
|
+
function forgetBrowserModeTokens() {
|
|
3712
|
+
removeFromBothAreas(`${cfg.prefix}:account:${cfg.domain}`);
|
|
3713
|
+
removeFromBothAreas(`${cfg.prefix}:account-login:${cfg.domain}`);
|
|
3714
|
+
}
|
|
3715
|
+
var isLoginOutcome = (value) => value === "in" || value === "expired" || value === "error";
|
|
3716
|
+
|
|
3717
|
+
// src/account-api.ts
|
|
3284
3718
|
var ACCOUNT_ATTRIBUTES = [
|
|
3285
3719
|
"account-login",
|
|
3286
3720
|
"account-bind",
|
|
@@ -3293,20 +3727,12 @@ ${fragment}`, {
|
|
|
3293
3727
|
"address-form"
|
|
3294
3728
|
];
|
|
3295
3729
|
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
|
-
}
|
|
3730
|
+
function hasLoginHint() {
|
|
3731
|
+
return Boolean(getStored(hintKey()));
|
|
3306
3732
|
}
|
|
3307
3733
|
function mountAccountIfUsed(root) {
|
|
3308
|
-
if (!cfg.account
|
|
3309
|
-
const needed = within(root, accountSelector()).length > 0 ||
|
|
3734
|
+
if (!cfg.account) return;
|
|
3735
|
+
const needed = within(root, accountSelector()).length > 0 || isLoginOutcome(new URLSearchParams(window.location.search).get("account")) || hasLoginHint();
|
|
3310
3736
|
if (!needed) return;
|
|
3311
3737
|
loadPart("account").then((part) => part.mountAccount(root)).catch((error) => {
|
|
3312
3738
|
reportError(error, "account");
|
|
@@ -3314,22 +3740,14 @@ ${fragment}`, {
|
|
|
3314
3740
|
}
|
|
3315
3741
|
function fromPart(name) {
|
|
3316
3742
|
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
|
-
);
|
|
3743
|
+
const call = part[name];
|
|
3323
3744
|
return call(...args);
|
|
3324
3745
|
});
|
|
3325
3746
|
}
|
|
3326
3747
|
var accountApi = {
|
|
3327
3748
|
login: fromPart("login"),
|
|
3328
3749
|
logout: fromPart("logout"),
|
|
3329
|
-
handleCallback: fromPart("handleCallback"),
|
|
3330
|
-
getToken: fromPart("getToken"),
|
|
3331
3750
|
isLoggedIn: fromPart("isLoggedIn"),
|
|
3332
|
-
query: fromPart("query"),
|
|
3333
3751
|
customer: fromPart("customer"),
|
|
3334
3752
|
orders: fromPart("orders"),
|
|
3335
3753
|
order: fromPart("order"),
|
|
@@ -3344,7 +3762,7 @@ ${fragment}`, {
|
|
|
3344
3762
|
mountAccount: fromPart("mountAccount")
|
|
3345
3763
|
};
|
|
3346
3764
|
|
|
3347
|
-
// src/controls/cart-controls.
|
|
3765
|
+
// src/controls/cart-controls.ts
|
|
3348
3766
|
var lineKeyAttribute = () => `data-${cfg.prefix}-key`;
|
|
3349
3767
|
var isElement3 = (target) => target instanceof window.Element;
|
|
3350
3768
|
function lineOf(target) {
|
|
@@ -3441,7 +3859,7 @@ ${fragment}`, {
|
|
|
3441
3859
|
document.addEventListener("keydown", onKeydown);
|
|
3442
3860
|
}
|
|
3443
3861
|
|
|
3444
|
-
// src/controls/discount.
|
|
3862
|
+
// src/controls/discount.ts
|
|
3445
3863
|
var isElement4 = (target) => target instanceof window.Element;
|
|
3446
3864
|
function codeInputOf(form) {
|
|
3447
3865
|
const input = form.querySelector(sel("discount-input")) ?? form.querySelector("input");
|
|
@@ -3499,7 +3917,7 @@ ${fragment}`, {
|
|
|
3499
3917
|
document.addEventListener("click", onClick3);
|
|
3500
3918
|
}
|
|
3501
3919
|
|
|
3502
|
-
// src/controls/extras.
|
|
3920
|
+
// src/controls/extras.ts
|
|
3503
3921
|
var addGroup = (button) => loadPart("extras").then((part) => part.addGroup(button));
|
|
3504
3922
|
var reportExtrasError = (error) => {
|
|
3505
3923
|
reportError(error, "extras");
|
|
@@ -3518,6 +3936,7 @@ ${fragment}`, {
|
|
|
3518
3936
|
}).catch(reportExtrasError);
|
|
3519
3937
|
}
|
|
3520
3938
|
function onVariantChanged(event) {
|
|
3939
|
+
if (!isOwnEvent(event, "variant-changed")) return;
|
|
3521
3940
|
const { target } = event;
|
|
3522
3941
|
if (!(target instanceof window.Element)) return;
|
|
3523
3942
|
if (!cfg.variantUrl && !target.hasAttribute(attr("variant-url"))) return;
|
|
@@ -3528,14 +3947,10 @@ ${fragment}`, {
|
|
|
3528
3947
|
function bindExtrasControls() {
|
|
3529
3948
|
document.addEventListener("click", onClick4);
|
|
3530
3949
|
window.addEventListener("storage", onStorage);
|
|
3531
|
-
document.addEventListener(
|
|
3532
|
-
`${cfg.prefix}:variant-changed`,
|
|
3533
|
-
/** @type {EventListener} */
|
|
3534
|
-
onVariantChanged
|
|
3535
|
-
);
|
|
3950
|
+
document.addEventListener(`${cfg.prefix}:variant-changed`, onVariantChanged);
|
|
3536
3951
|
}
|
|
3537
3952
|
|
|
3538
|
-
// src/controls/navigation.
|
|
3953
|
+
// src/controls/navigation.ts
|
|
3539
3954
|
var mountedBody = null;
|
|
3540
3955
|
function rememberDocumentMount() {
|
|
3541
3956
|
mountedBody = document.body;
|
|
@@ -3556,7 +3971,9 @@ ${fragment}`, {
|
|
|
3556
3971
|
}
|
|
3557
3972
|
function bindAstroRouter(hooks) {
|
|
3558
3973
|
document.addEventListener("astro:page-load", () => {
|
|
3559
|
-
|
|
3974
|
+
onPageLoad(hooks).catch((error) => {
|
|
3975
|
+
reportError(error, "page mount");
|
|
3976
|
+
});
|
|
3560
3977
|
});
|
|
3561
3978
|
document.addEventListener("astro:before-swap", () => {
|
|
3562
3979
|
mountedBody = null;
|
|
@@ -3583,11 +4000,13 @@ ${fragment}`, {
|
|
|
3583
4000
|
window.addEventListener("online", onOnline);
|
|
3584
4001
|
}
|
|
3585
4002
|
|
|
3586
|
-
// src/index.
|
|
3587
|
-
var VERSION = "0.
|
|
3588
|
-
var ANALYTICS_EVENTS = ["product-ready", "list-ready", "cart-updated", "cart-toggled", "checkout", "search"];
|
|
4003
|
+
// src/index.ts
|
|
4004
|
+
var VERSION = "0.7.1";
|
|
3589
4005
|
var OBSERVE_DEBOUNCE_MS = 50;
|
|
3590
|
-
|
|
4006
|
+
function pageConfig() {
|
|
4007
|
+
const config = pageGlobal("TETHER_CONFIG");
|
|
4008
|
+
return typeof config === "object" && config !== null ? Object.fromEntries(Object.entries(config)) : {};
|
|
4009
|
+
}
|
|
3591
4010
|
var initPromise = null;
|
|
3592
4011
|
var observer = null;
|
|
3593
4012
|
var analyticsQueue = null;
|
|
@@ -3601,10 +4020,7 @@ ${fragment}`, {
|
|
|
3601
4020
|
const list = new ListScope(el);
|
|
3602
4021
|
liveLists.add(list);
|
|
3603
4022
|
el.addEventListener(`${cfg.prefix}:list-rendered`, (event) => {
|
|
3604
|
-
mount(
|
|
3605
|
-
/** @type {CustomEvent<{ container: ParentNode }>} */
|
|
3606
|
-
event.detail.container
|
|
3607
|
-
);
|
|
4023
|
+
if (isOwnEvent(event, "list-rendered")) mount(event.detail.container);
|
|
3608
4024
|
});
|
|
3609
4025
|
void trackMarket(list.load());
|
|
3610
4026
|
});
|
|
@@ -3625,21 +4041,39 @@ ${fragment}`, {
|
|
|
3625
4041
|
});
|
|
3626
4042
|
}
|
|
3627
4043
|
}
|
|
3628
|
-
function
|
|
3629
|
-
if (!isConfigured()) return 0;
|
|
3630
|
-
if (root === document) rememberDocumentMount();
|
|
3631
|
-
pruneLive();
|
|
3632
|
-
hydrateFromPage(root.ownerDocument ?? document);
|
|
4044
|
+
function mountWithin(root) {
|
|
3633
4045
|
let count = mountLists(root);
|
|
3634
4046
|
within(root, sel("product")).forEach((el) => {
|
|
3635
4047
|
if (mountProduct(el)) count++;
|
|
3636
4048
|
});
|
|
3637
4049
|
mountParts(root);
|
|
4050
|
+
return count;
|
|
4051
|
+
}
|
|
4052
|
+
function mount(root = document) {
|
|
4053
|
+
if (!isConfigured()) return 0;
|
|
4054
|
+
if (root === document) rememberDocumentMount();
|
|
4055
|
+
pruneLive();
|
|
4056
|
+
hydrateFromPage(root.ownerDocument ?? document);
|
|
4057
|
+
const count = mountWithin(root);
|
|
3638
4058
|
settleMarket();
|
|
3639
4059
|
Cart.render();
|
|
3640
4060
|
log(`mounted ${count} scope(s)`, root);
|
|
3641
4061
|
return count;
|
|
3642
4062
|
}
|
|
4063
|
+
function mountAdded(added) {
|
|
4064
|
+
const roots = added.filter(
|
|
4065
|
+
(root) => root.isConnected && !added.some((other) => other !== root && other.contains(root))
|
|
4066
|
+
);
|
|
4067
|
+
if (!roots.length || !isConfigured()) return;
|
|
4068
|
+
pruneLive();
|
|
4069
|
+
roots.forEach((root) => {
|
|
4070
|
+
hydrateFromPage(root);
|
|
4071
|
+
});
|
|
4072
|
+
const count = roots.reduce((sum, root) => sum + mountWithin(root), 0);
|
|
4073
|
+
settleMarket();
|
|
4074
|
+
Cart.render();
|
|
4075
|
+
log(`mounted ${count} scope(s) in ${roots.length} added element(s)`);
|
|
4076
|
+
}
|
|
3643
4077
|
function unmount() {
|
|
3644
4078
|
Cart.close();
|
|
3645
4079
|
}
|
|
@@ -3661,13 +4095,13 @@ ${fragment}`, {
|
|
|
3661
4095
|
ANALYTICS_EVENTS.forEach((name) => {
|
|
3662
4096
|
document.addEventListener(`${cfg.prefix}:${name}`, (event) => queue.push(event));
|
|
3663
4097
|
});
|
|
3664
|
-
document.addEventListener("astro:page-load", () => queue.push(
|
|
4098
|
+
document.addEventListener("astro:page-load", (event) => queue.push(event));
|
|
3665
4099
|
loadPart("analytics").then((part) => part.startAnalytics(queue)).catch((error) => {
|
|
3666
4100
|
reportError(error, "analytics");
|
|
3667
4101
|
});
|
|
3668
4102
|
}
|
|
3669
4103
|
async function cartInPageMarket(cart) {
|
|
3670
|
-
const country = cfg.country
|
|
4104
|
+
const country = cfg.country;
|
|
3671
4105
|
const cartCountry = cart?.buyerIdentity.countryCode;
|
|
3672
4106
|
if (!cart || !country || !cartCountry || cartCountry === country) return cart;
|
|
3673
4107
|
try {
|
|
@@ -3679,12 +4113,13 @@ ${fragment}`, {
|
|
|
3679
4113
|
}
|
|
3680
4114
|
function init2(overrides = {}) {
|
|
3681
4115
|
if (initPromise && !Object.keys(overrides).length) return initPromise;
|
|
3682
|
-
configure({ ...
|
|
4116
|
+
configure({ ...pageConfig(), ...overrides });
|
|
3683
4117
|
if (!isConfigured()) {
|
|
3684
4118
|
warn("TETHER_CONFIG is missing domain or token. Tether is idle.");
|
|
3685
4119
|
initPromise = Promise.resolve(null);
|
|
3686
4120
|
return initPromise;
|
|
3687
4121
|
}
|
|
4122
|
+
forgetBrowserModeTokens();
|
|
3688
4123
|
bindControls();
|
|
3689
4124
|
if (cfg.observe) startObserver();
|
|
3690
4125
|
if (cfg.analytics && !analyticsQueue) startAnalyticsQueue();
|
|
@@ -3706,10 +4141,9 @@ ${fragment}`, {
|
|
|
3706
4141
|
});
|
|
3707
4142
|
clearTimeout(timer);
|
|
3708
4143
|
timer = setTimeout(() => {
|
|
3709
|
-
|
|
3710
|
-
if (node.isConnected) mount(node);
|
|
3711
|
-
});
|
|
4144
|
+
const added = [...queued];
|
|
3712
4145
|
queued.clear();
|
|
4146
|
+
mountAdded(added);
|
|
3713
4147
|
}, OBSERVE_DEBOUNCE_MS);
|
|
3714
4148
|
});
|
|
3715
4149
|
observer.observe(document.body, { childList: true, subtree: true });
|
|
@@ -3718,32 +4152,28 @@ ${fragment}`, {
|
|
|
3718
4152
|
var getLocalization = () => loadPart("markets").then((part) => part.getLocalization());
|
|
3719
4153
|
var diagnose = (options) => loadPart("diagnose").then((part) => part.diagnose({ ...options, version: VERSION }));
|
|
3720
4154
|
function on(name, listener) {
|
|
4155
|
+
const type = `${cfg.prefix}:${name}`;
|
|
3721
4156
|
const handler = (event) => {
|
|
3722
|
-
|
|
3723
|
-
/** @type {CustomEvent} Tether's events are CustomEvents. */
|
|
3724
|
-
event
|
|
3725
|
-
);
|
|
3726
|
-
listener(custom.detail, custom);
|
|
4157
|
+
if (isOwnEvent(event, name)) listener(event.detail, event);
|
|
3727
4158
|
};
|
|
3728
|
-
document.addEventListener(
|
|
4159
|
+
document.addEventListener(type, handler);
|
|
3729
4160
|
return () => {
|
|
3730
|
-
document.removeEventListener(
|
|
4161
|
+
document.removeEventListener(type, handler);
|
|
3731
4162
|
};
|
|
3732
4163
|
}
|
|
3733
|
-
var
|
|
4164
|
+
var publicApi = {
|
|
3734
4165
|
VERSION,
|
|
3735
4166
|
init: init2,
|
|
3736
4167
|
mount,
|
|
3737
4168
|
unmount,
|
|
3738
|
-
/**
|
|
4169
|
+
/** The live config, read-only. */
|
|
3739
4170
|
get config() {
|
|
3740
|
-
return
|
|
4171
|
+
return configView;
|
|
3741
4172
|
},
|
|
3742
|
-
/** @returns {Promise<TetherApi | null>} */
|
|
3743
4173
|
get ready() {
|
|
3744
4174
|
return initPromise ?? Promise.resolve(null);
|
|
3745
4175
|
},
|
|
3746
|
-
query
|
|
4176
|
+
query,
|
|
3747
4177
|
withContext,
|
|
3748
4178
|
cart: Cart,
|
|
3749
4179
|
getProduct,
|
|
@@ -3764,19 +4194,15 @@ ${fragment}`, {
|
|
|
3764
4194
|
detectMarket,
|
|
3765
4195
|
getLocalization,
|
|
3766
4196
|
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
4197
|
on
|
|
3774
4198
|
};
|
|
4199
|
+
var internals = { __core: coreModules, __parts: {} };
|
|
4200
|
+
var api = Object.assign(publicApi, internals);
|
|
3775
4201
|
var index_default = api;
|
|
3776
4202
|
function autoStart() {
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
if (!
|
|
4203
|
+
Reflect.set(window, "Tether", api);
|
|
4204
|
+
const config = pageGlobal("TETHER_CONFIG");
|
|
4205
|
+
if (!config || field(config, "autoMount") === false || pageGlobal("TETHER_INTEGRATION")) return;
|
|
3780
4206
|
const start = () => {
|
|
3781
4207
|
init2().then(() => mount(document)).catch((error) => {
|
|
3782
4208
|
reportError(error, "init");
|