@atomm-developer/generator-material-purchase 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1925 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
+ const DEFAULT_API_BASE_URL = "https://xcs-api.xtool.com";
5
+ const ICON_BASE = "https://storage.atomm.com/xtool/aimake-static/static/xtool-shopify";
6
+ const ShopifyStoreName = {
7
+ MAKEBLOCK_XTOOL: "makeblock-xtool",
8
+ CA_XTOOL: "ca-xtool",
9
+ XTOOL_AUSTRALIA_STORE: "xtool-australia-store",
10
+ JP_XTOOL: "jp-xtool",
11
+ EU_XTOOL: "eu-xtool",
12
+ DE_XTOOL: "de-xtool",
13
+ FR_XTOOL: "fr-xtool",
14
+ UK_XTOOL: "uk-xtool"
15
+ };
16
+ const PROD_STORE_OPTIONS = [
17
+ {
18
+ label: "US",
19
+ value: ShopifyStoreName.MAKEBLOCK_XTOOL,
20
+ icon: `${ICON_BASE}/at_country_us.svg`,
21
+ shopDomain: "makeblock-xtool.myshopify.com",
22
+ storeFrontAccessToken: "7bb184c4d18313c58d0ccd6bfa0f73a3"
23
+ },
24
+ {
25
+ label: "CA",
26
+ value: ShopifyStoreName.CA_XTOOL,
27
+ icon: `${ICON_BASE}/at_country_ca.svg`,
28
+ shopDomain: "ca-xtool.myshopify.com",
29
+ storeFrontAccessToken: "7912e607574a3a6eaffaf501c75408e6"
30
+ },
31
+ {
32
+ label: "EU",
33
+ value: ShopifyStoreName.EU_XTOOL,
34
+ icon: `${ICON_BASE}/at_country_eu.svg`,
35
+ shopDomain: "eu-xtool.myshopify.com",
36
+ storeFrontAccessToken: "0886b237162c21d11ba1ca30f6220c6f"
37
+ },
38
+ {
39
+ label: "UK",
40
+ value: ShopifyStoreName.UK_XTOOL,
41
+ icon: `${ICON_BASE}/at_country_uk.svg`,
42
+ shopDomain: "uk-xtool.myshopify.com",
43
+ storeFrontAccessToken: "adf825795f70225d0351ad387d538757"
44
+ },
45
+ {
46
+ label: "FR",
47
+ value: ShopifyStoreName.FR_XTOOL,
48
+ icon: `${ICON_BASE}/at_country_fr.svg`,
49
+ shopDomain: "fr-xtool.myshopify.com",
50
+ storeFrontAccessToken: "e397fc547281c76d1086776fa347d804"
51
+ },
52
+ {
53
+ label: "DE",
54
+ value: ShopifyStoreName.DE_XTOOL,
55
+ icon: `${ICON_BASE}/at_country_de.svg`,
56
+ shopDomain: "de-xtool.myshopify.com",
57
+ storeFrontAccessToken: "87e1adaeba390a0b21e5ef6937792d2f"
58
+ },
59
+ {
60
+ label: "JP",
61
+ value: ShopifyStoreName.JP_XTOOL,
62
+ icon: `${ICON_BASE}/at_country_jp.svg`,
63
+ shopDomain: "jp-xtool.myshopify.com",
64
+ storeFrontAccessToken: "c9738498306280b6ae87bb999811d952"
65
+ },
66
+ {
67
+ label: "AU",
68
+ value: ShopifyStoreName.XTOOL_AUSTRALIA_STORE,
69
+ icon: `${ICON_BASE}/at_country_au.svg`,
70
+ shopDomain: "xtool-australia-store.myshopify.com",
71
+ storeFrontAccessToken: "fabb70f52384af557b4ac0392f86c2ed"
72
+ }
73
+ ];
74
+ const STORE_LABEL_KEY_MAP = {
75
+ [ShopifyStoreName.MAKEBLOCK_XTOOL]: "store_us",
76
+ [ShopifyStoreName.CA_XTOOL]: "store_ca",
77
+ [ShopifyStoreName.XTOOL_AUSTRALIA_STORE]: "store_au",
78
+ [ShopifyStoreName.EU_XTOOL]: "store_eu",
79
+ [ShopifyStoreName.DE_XTOOL]: "store_de",
80
+ [ShopifyStoreName.FR_XTOOL]: "store_fr",
81
+ [ShopifyStoreName.UK_XTOOL]: "store_uk",
82
+ [ShopifyStoreName.JP_XTOOL]: "store_jp"
83
+ };
84
+ const COUNTRY_NAME_KEY_MAP = {
85
+ [ShopifyStoreName.MAKEBLOCK_XTOOL]: "country_usa",
86
+ [ShopifyStoreName.CA_XTOOL]: "country_canada",
87
+ [ShopifyStoreName.XTOOL_AUSTRALIA_STORE]: "country_australia",
88
+ [ShopifyStoreName.EU_XTOOL]: "country_eu",
89
+ [ShopifyStoreName.DE_XTOOL]: "country_germany",
90
+ [ShopifyStoreName.FR_XTOOL]: "country_france",
91
+ [ShopifyStoreName.UK_XTOOL]: "country_uk",
92
+ [ShopifyStoreName.JP_XTOOL]: "country_japan"
93
+ };
94
+ function countryToSite(countryInfo) {
95
+ if (!countryInfo) return ShopifyStoreName.MAKEBLOCK_XTOOL;
96
+ switch (countryInfo.continent) {
97
+ case "EU":
98
+ switch (countryInfo.country) {
99
+ case "GB":
100
+ case "GG":
101
+ case "IM":
102
+ case "JE":
103
+ return ShopifyStoreName.UK_XTOOL;
104
+ case "DE":
105
+ case "AT":
106
+ return ShopifyStoreName.DE_XTOOL;
107
+ case "FR":
108
+ case "LU":
109
+ case "MC":
110
+ return ShopifyStoreName.FR_XTOOL;
111
+ default:
112
+ return ShopifyStoreName.EU_XTOOL;
113
+ }
114
+ case "AS":
115
+ return countryInfo.country === "JP" ? ShopifyStoreName.JP_XTOOL : ShopifyStoreName.MAKEBLOCK_XTOOL;
116
+ case "NA":
117
+ return countryInfo.country === "CA" ? ShopifyStoreName.CA_XTOOL : ShopifyStoreName.MAKEBLOCK_XTOOL;
118
+ case "OC":
119
+ return countryInfo.country === "AU" ? ShopifyStoreName.XTOOL_AUSTRALIA_STORE : ShopifyStoreName.MAKEBLOCK_XTOOL;
120
+ default:
121
+ return ShopifyStoreName.MAKEBLOCK_XTOOL;
122
+ }
123
+ }
124
+ const STORAGE_KEYS = {
125
+ CURRENT_SHOP: "xtool_current_shop_name",
126
+ SHOP_INFO: "xtool_shop_info",
127
+ U_TOKEN: "utoken",
128
+ LANG: "LANG_KEY"
129
+ };
130
+ function getCookie(name) {
131
+ if (typeof document === "undefined") return "";
132
+ const reg = new RegExp(`(?:^|;\\s*)${name}=([^;]*)`);
133
+ const match = document.cookie.match(reg);
134
+ return match ? decodeURIComponent(match[1]) : "";
135
+ }
136
+ function getUToken() {
137
+ return getCookie(STORAGE_KEYS.U_TOKEN) || localStorage.getItem(STORAGE_KEYS.U_TOKEN) || "";
138
+ }
139
+ function getLang() {
140
+ return localStorage.getItem(STORAGE_KEYS.LANG) || "en";
141
+ }
142
+ async function atommRequest({ baseURL, url, method, params, data }) {
143
+ let finalUrl = baseURL.replace(/\/$/, "") + url;
144
+ if (params && method === "GET") {
145
+ const search = new URLSearchParams();
146
+ Object.entries(params).forEach(([k, v]) => {
147
+ if (v === void 0 || v === null) return;
148
+ search.append(k, String(v));
149
+ });
150
+ const qs = search.toString();
151
+ if (qs) finalUrl += (finalUrl.includes("?") ? "&" : "?") + qs;
152
+ }
153
+ const res = await fetch(finalUrl, {
154
+ method,
155
+ headers: {
156
+ "content-type": "application/json;charset=UTF-8",
157
+ uToken: getUToken(),
158
+ lang: getLang()
159
+ },
160
+ body: method === "POST" ? JSON.stringify(data ?? {}) : void 0
161
+ });
162
+ if (!res.ok) {
163
+ throw new Error(`HTTP ${res.status} ${res.statusText}`);
164
+ }
165
+ const body = await res.json();
166
+ if (body.code === 0) return body.data;
167
+ throw new Error(body.msg || body.message || `Request failed with code ${body.code}`);
168
+ }
169
+ function fetchAccessoryPack(baseURL, params) {
170
+ return atommRequest({
171
+ baseURL,
172
+ url: "/community/v1/web/generator-accessory-pack",
173
+ method: "GET",
174
+ params
175
+ });
176
+ }
177
+ function fetchIpLocation(baseURL) {
178
+ return atommRequest({
179
+ baseURL,
180
+ url: "/community/v2/web/ip/location",
181
+ method: "GET"
182
+ });
183
+ }
184
+ function fetchDistributionTrackId(baseURL, productContent) {
185
+ return atommRequest({
186
+ baseURL,
187
+ url: "/community/v1/web/distribution/order",
188
+ method: "POST",
189
+ data: { productContent }
190
+ });
191
+ }
192
+ class f {
193
+ constructor(t) {
194
+ if (t.shopDomain && !t.shopDomain.match(/^[a-zA-Z0-9_-]+\.myshopify\.com$/))
195
+ throw new Error("shopDomain 必须为 xxxx.myshopify.com 格式");
196
+ if (!t.storeFrontAccessToken)
197
+ throw new Error("storeFrontAccessToken 必填");
198
+ this.config = {
199
+ shopDomain: t.shopDomain,
200
+ storeFrontAccessToken: t.storeFrontAccessToken,
201
+ apiVersion: t.apiVersion || "2025-07",
202
+ timeout: t.timeout || 3e4,
203
+ cartStorageMode: t.cartStorageMode || "memory",
204
+ cartStorageKey: t.cartStorageKey || "xtool_shop_sdk_shopify_cart_id"
205
+ }, this.baseUrl = `https://${this.config.shopDomain}/api/${this.config.apiVersion}`;
206
+ }
207
+ /**
208
+ * 发送 HTTP 请求
209
+ */
210
+ async request(t, e = {}) {
211
+ const {
212
+ method: o = "GET",
213
+ headers: a = {},
214
+ body: i,
215
+ params: s
216
+ } = e;
217
+ let n = `${this.baseUrl}${t}`;
218
+ if (s) {
219
+ const r = new URLSearchParams(s).toString();
220
+ n += `?${r}`;
221
+ }
222
+ const h = {
223
+ "Content-Type": "application/json",
224
+ "X-Shopify-StoreFront-Access-Token": this.config.storeFrontAccessToken,
225
+ ...a
226
+ }, c = {
227
+ method: o,
228
+ headers: h
229
+ };
230
+ i && (c.body = JSON.stringify(i));
231
+ const d = new AbortController(), u = setTimeout(() => d.abort(), this.config.timeout);
232
+ try {
233
+ const r = await fetch(n, {
234
+ ...c,
235
+ signal: d.signal
236
+ });
237
+ clearTimeout(u);
238
+ const p = {
239
+ data: await r.json(),
240
+ status: r.status,
241
+ statusText: r.statusText,
242
+ headers: this.parseHeaders(r.headers)
243
+ };
244
+ if (!r.ok)
245
+ throw new Error(`API 请求失败: ${r.status} ${r.statusText}`);
246
+ return p;
247
+ } catch (r) {
248
+ throw r instanceof Error ? r.name === "AbortError" ? new Error("请求超时") : r : new Error("未知错误");
249
+ }
250
+ }
251
+ /**
252
+ * 解析响应头
253
+ */
254
+ parseHeaders(t) {
255
+ const e = {};
256
+ return t.forEach((o, a) => {
257
+ e[a] = o;
258
+ }), e;
259
+ }
260
+ graphQlRequest(t, e = {}) {
261
+ return this.request("/graphql.json", {
262
+ method: "POST",
263
+ body: { query: t, variables: e }
264
+ });
265
+ }
266
+ /**
267
+ * 创建购物车
268
+ * @param params 购物车参数
269
+ * @returns 购物车信息,包含购物车 ID
270
+ */
271
+ async createCart(t) {
272
+ const e = t.lines.map((s) => s.merchandiseId.startsWith("gid://shopify/ProductVariant/") ? s : {
273
+ ...s,
274
+ merchandiseId: `gid://shopify/ProductVariant/${s.merchandiseId}`
275
+ });
276
+ return t.lines = e, (await this.graphQlRequest(`
277
+ mutation cartCreate($input: CartInput) {
278
+ cartCreate(input: $input) {
279
+ cart {
280
+ id
281
+ checkoutUrl
282
+ }
283
+ userErrors {
284
+ field
285
+ message
286
+ }
287
+ warnings {
288
+ code
289
+ message
290
+ target
291
+ }
292
+ }
293
+ }
294
+ `, { input: t })).data.data.cartCreate;
295
+ }
296
+ /**
297
+ * 获取商品信息与Metafields
298
+ * @param params 商品信息与Metafields参数
299
+ * @returns 商品信息与Metafields
300
+ */
301
+ async getProductInfoWithMetafields(t) {
302
+ const { productId: e, metafieldsIdentifiers: o } = t, a = `
303
+ query Products($id: ID, $metafields_identifiers: [HasMetafieldsIdentifier!]!) {
304
+ product(id: $id ) {
305
+ title
306
+ id
307
+ handle
308
+ tags
309
+ productType
310
+ totalInventory
311
+ onlineStoreUrl
312
+ handle
313
+ isGiftCard
314
+ description
315
+ descriptionHtml
316
+ selectedOrFirstAvailableVariant {
317
+ id
318
+ }
319
+ featuredImage {
320
+ url
321
+ }
322
+ metafields(identifiers: $metafields_identifiers){
323
+ id
324
+ namespace
325
+ key
326
+ value
327
+ }
328
+ variants(first: 20){
329
+ nodes {
330
+ id
331
+ title
332
+ }
333
+ }
334
+ }
335
+ }
336
+ `, i = {
337
+ id: String(e).startsWith("gid://shopify/Product/") ? e : `gid://shopify/Product/${e}`,
338
+ metafields_identifiers: o
339
+ };
340
+ return (await this.graphQlRequest(a, i)).data.data.product;
341
+ }
342
+ /**
343
+ * 获取店铺信息
344
+ * @returns 店铺信息
345
+ */
346
+ async getShopInfo() {
347
+ return (await this.graphQlRequest(`
348
+ query Shop {
349
+ shop {
350
+ id
351
+ name
352
+ moneyFormat
353
+ }
354
+ }
355
+ `)).data.data.shop;
356
+ }
357
+ /**
358
+ * 自定义请求方法
359
+ * 提供灵活的 HTTP 请求能力,可用于调用其他 Shopify API
360
+ */
361
+ async customRequest(t, e) {
362
+ return this.request(t, e);
363
+ }
364
+ }
365
+ class ShopifyClient {
366
+ constructor() {
367
+ __publicField(this, "currentShopName", "");
368
+ __publicField(this, "client", null);
369
+ __publicField(this, "initialized", false);
370
+ __publicField(this, "initializing", false);
371
+ }
372
+ get currentStore() {
373
+ return PROD_STORE_OPTIONS.find((s) => s.value === this.currentShopName);
374
+ }
375
+ initClientFor(shopName) {
376
+ const store = PROD_STORE_OPTIONS.find((s) => s.value === shopName);
377
+ if (!store) {
378
+ console.warn(`[ShopifyClient] missing config for store: ${shopName}`);
379
+ return;
380
+ }
381
+ this.client = new f({
382
+ shopDomain: store.shopDomain,
383
+ storeFrontAccessToken: store.storeFrontAccessToken
384
+ });
385
+ }
386
+ async init(initialShopName) {
387
+ if (this.initialized || this.initializing) return;
388
+ this.initializing = true;
389
+ try {
390
+ this.currentShopName = initialShopName;
391
+ localStorage.setItem(STORAGE_KEYS.CURRENT_SHOP, this.currentShopName);
392
+ this.initClientFor(this.currentShopName);
393
+ await this.fetchAndSaveShopInfo();
394
+ this.initialized = true;
395
+ } catch (e) {
396
+ console.error("[ShopifyClient] init failed:", e);
397
+ this.initialized = true;
398
+ } finally {
399
+ this.initializing = false;
400
+ }
401
+ }
402
+ async fetchAndSaveShopInfo() {
403
+ if (!this.client) return;
404
+ try {
405
+ const info = await this.client.getShopInfo();
406
+ localStorage.setItem(
407
+ STORAGE_KEYS.SHOP_INFO,
408
+ JSON.stringify({
409
+ money_with_currency_format: info.moneyFormat,
410
+ moneyFormat: info.moneyFormat
411
+ })
412
+ );
413
+ } catch (e) {
414
+ console.warn("[ShopifyClient] getShopInfo failed:", e);
415
+ }
416
+ }
417
+ async changeShop(shopName) {
418
+ this.currentShopName = shopName;
419
+ localStorage.setItem(STORAGE_KEYS.CURRENT_SHOP, shopName);
420
+ this.initClientFor(shopName);
421
+ await this.fetchAndSaveShopInfo();
422
+ }
423
+ async createCheckout(lines, options) {
424
+ var _a, _b;
425
+ if (!this.client) throw new Error("Shopify client not initialized");
426
+ const attributes = [
427
+ { key: "xtool_return_url", value: window.location.href }
428
+ ];
429
+ if (options == null ? void 0 : options.trackId) {
430
+ attributes.push({ key: "atomm_track_id", value: options.trackId });
431
+ }
432
+ const result = await this.client.createCart({ lines, attributes });
433
+ if (result.cart) return result.cart;
434
+ const errMsg = ((_b = (_a = result.userErrors) == null ? void 0 : _a[0]) == null ? void 0 : _b.message) || "Failed to create cart";
435
+ throw new Error(errMsg);
436
+ }
437
+ }
438
+ function formatMoney(money) {
439
+ const moneyNumber = typeof money === "string" ? parseFloat(money) : money;
440
+ if (isNaN(moneyNumber) || typeof moneyNumber !== "number") return "$0.00";
441
+ let shopInfo = {};
442
+ try {
443
+ const stored = localStorage.getItem(STORAGE_KEYS.SHOP_INFO);
444
+ if (stored) shopInfo = JSON.parse(stored);
445
+ } catch {
446
+ }
447
+ const formatString = shopInfo.money_with_currency_format || shopInfo.moneyFormat || "${{amount}}";
448
+ const placeholderRegex = /\{\{\s*(\w+)\s*\}\}/;
449
+ function formatWithDelimiters(n, precision, thousands = ",", decimal = ".") {
450
+ var _a;
451
+ const parts = n.toFixed(precision).split(".");
452
+ const dollars = ((_a = parts[0]) == null ? void 0 : _a.replace(/(\d)(?=(\d{3})+(?!\d))/g, "$1" + thousands)) || "";
453
+ const cents = parts[1] ? decimal + parts[1] : "";
454
+ return dollars + cents;
455
+ }
456
+ const match = formatString.match(placeholderRegex);
457
+ let value = "";
458
+ if (match == null ? void 0 : match[1]) {
459
+ switch (match[1]) {
460
+ case "amount":
461
+ value = formatWithDelimiters(moneyNumber, 2);
462
+ break;
463
+ case "amount_no_decimals":
464
+ value = formatWithDelimiters(moneyNumber, 0);
465
+ break;
466
+ case "amount_with_comma_separator":
467
+ value = formatWithDelimiters(moneyNumber, 2, ".", ",");
468
+ break;
469
+ case "amount_no_decimals_with_comma_separator":
470
+ value = formatWithDelimiters(moneyNumber, 0, ".", ",");
471
+ break;
472
+ case "amount_with_space_separator":
473
+ value = formatWithDelimiters(moneyNumber, 2, " ", ",");
474
+ break;
475
+ case "amount_with_apostrophe_separator":
476
+ value = formatWithDelimiters(moneyNumber, 2, "'");
477
+ break;
478
+ default:
479
+ value = formatWithDelimiters(moneyNumber, 2);
480
+ break;
481
+ }
482
+ } else {
483
+ value = formatWithDelimiters(moneyNumber, 2);
484
+ }
485
+ return formatString.replace(placeholderRegex, value);
486
+ }
487
+ function assignUid(item, index) {
488
+ var _a, _b;
489
+ const dvId = item.defaultVariant;
490
+ if (dvId != null) return `${item.id}_${String(dvId)}_${index}`;
491
+ const vid = (_b = (_a = item.variants) == null ? void 0 : _a[0]) == null ? void 0 : _b.id;
492
+ if (vid != null) return `${item.id}_${vid}_${index}`;
493
+ return `${item.id}_${index}`;
494
+ }
495
+ function isVariantPurchasable(v) {
496
+ if (!v) return false;
497
+ return v.availableForSale === true && v.outOfStock !== true;
498
+ }
499
+ function refreshProductPrices(list) {
500
+ list.forEach((item) => {
501
+ var _a;
502
+ (_a = item.variants) == null ? void 0 : _a.forEach((v) => {
503
+ v.displayPrice = formatMoney(v.price || "0.00");
504
+ v.displayCompareAtPrice = v.compareAtPrice ? formatMoney(v.compareAtPrice) : "";
505
+ });
506
+ });
507
+ }
508
+ function findVariantByOptions(product, options) {
509
+ var _a;
510
+ if (!((_a = product.variants) == null ? void 0 : _a.length)) return null;
511
+ if (!options || Object.keys(options).length === 0) {
512
+ if (product.defaultVariant) {
513
+ const dv = product.variants.find((v) => v.id === product.defaultVariant);
514
+ if (dv) return dv;
515
+ }
516
+ return product.variants.find((v) => v.isBind) || product.variants[0] || null;
517
+ }
518
+ const matched = product.variants.find((v) => {
519
+ if (!v.option) return false;
520
+ for (const [k, val] of Object.entries(options)) {
521
+ if (k === "uid") continue;
522
+ if (val && val !== "" && v.option[k] !== val) return false;
523
+ }
524
+ return true;
525
+ });
526
+ if (matched) return matched;
527
+ if (product.defaultVariant) {
528
+ const dv = product.variants.find((v) => v.id === product.defaultVariant);
529
+ if (dv) return dv;
530
+ }
531
+ return product.variants.find((v) => v.isBind) || product.variants[0] || null;
532
+ }
533
+ function getOptionKeys(product) {
534
+ var _a, _b;
535
+ const variants = product.variants;
536
+ if (!(variants == null ? void 0 : variants.length)) return [];
537
+ const isNonEmpty = (v) => !!v && v.trim() !== "";
538
+ let keys = [];
539
+ if ((_a = product.options) == null ? void 0 : _a.length) {
540
+ keys = product.options.map((o) => o.key).filter(Boolean);
541
+ } else if ((_b = variants[0]) == null ? void 0 : _b.option) {
542
+ keys = Object.keys(variants[0].option);
543
+ }
544
+ return keys.filter((k) => variants.some((v) => {
545
+ var _a2;
546
+ return isNonEmpty((_a2 = v.option) == null ? void 0 : _a2[k]);
547
+ }));
548
+ }
549
+ function getOptionValues(product, keys, index, selected) {
550
+ const isNonEmpty = (v) => !!v && v.trim() !== "";
551
+ const variants = product.variants || [];
552
+ const values = /* @__PURE__ */ new Set();
553
+ variants.forEach((v) => {
554
+ if (!v.option) return;
555
+ if (index > 0) {
556
+ for (let i = 0; i < index; i++) {
557
+ const prevKey = keys[i];
558
+ const prevVal = selected[prevKey];
559
+ if (isNonEmpty(prevVal) && v.option[prevKey] !== prevVal) return;
560
+ }
561
+ }
562
+ const val = v.option[keys[index]];
563
+ if (isNonEmpty(val)) values.add(val);
564
+ });
565
+ return Array.from(values).sort();
566
+ }
567
+ function getDefaultVariant(product) {
568
+ const variants = product.variants;
569
+ if (!(variants == null ? void 0 : variants.length)) return null;
570
+ if (product.defaultVariant) {
571
+ const found = variants.find((v) => v.id === product.defaultVariant);
572
+ if (found) return found;
573
+ }
574
+ return variants.find((v) => v.isBind) || variants[0] || null;
575
+ }
576
+ function initSelectedOptionsForProduct(product) {
577
+ const keys = getOptionKeys(product);
578
+ const selected = {};
579
+ if (!keys.length) return selected;
580
+ keys.forEach((k) => selected[k] = "");
581
+ const target = getDefaultVariant(product);
582
+ if (target == null ? void 0 : target.option) {
583
+ keys.forEach((k) => {
584
+ const v = target.option[k];
585
+ if (v && v.trim() !== "") selected[k] = v;
586
+ });
587
+ } else {
588
+ keys.forEach((k, i) => {
589
+ const values = getOptionValues(product, keys, i, selected);
590
+ if (values[0]) selected[k] = values[0];
591
+ });
592
+ }
593
+ return selected;
594
+ }
595
+ const en = {
596
+ shopify_supplies_kit: "Supplies Kit",
597
+ close: "Close",
598
+ current_country_notice: "Items will be shipped to {country}.",
599
+ loading: "Loading",
600
+ no_items_in_cart: "No items available.",
601
+ sold_out: "Sold out",
602
+ total: "Total",
603
+ discount_notice: "Discount codes can only be used in checkout.",
604
+ buy_now: "Buy now",
605
+ failed_load_product_list: "Failed to load product list.",
606
+ checkout_failed: "Failed to create checkout.",
607
+ selected_items_out_of_stock: "Selected items are sold out.",
608
+ no_valid_products_selected: "No valid products selected.",
609
+ store_us: "US",
610
+ store_ca: "CA",
611
+ store_au: "AU",
612
+ store_eu: "EU",
613
+ store_de: "DE",
614
+ store_fr: "FR",
615
+ store_uk: "UK",
616
+ store_jp: "JP",
617
+ store_test: "XT",
618
+ country_usa: "USA",
619
+ country_canada: "Canada",
620
+ country_australia: "Australia",
621
+ country_eu: "EU",
622
+ country_germany: "Germany",
623
+ country_france: "France",
624
+ country_uk: "UK",
625
+ country_japan: "Japan"
626
+ };
627
+ const zh = {
628
+ shopify_supplies_kit: "耗材包",
629
+ close: "关闭",
630
+ current_country_notice: "商品将发往{country}。",
631
+ loading: "加载中",
632
+ no_items_in_cart: "暂无可购买的商品。",
633
+ sold_out: "售罄",
634
+ total: "合计",
635
+ discount_notice: "优惠码仅可在结算页使用。",
636
+ buy_now: "立即购买",
637
+ failed_load_product_list: "加载商品列表失败。",
638
+ checkout_failed: "创建结算订单失败。",
639
+ selected_items_out_of_stock: "所选商品已售罄。",
640
+ no_valid_products_selected: "未选择有效商品。",
641
+ store_us: "美国",
642
+ store_ca: "加拿大",
643
+ store_au: "澳大利亚",
644
+ store_eu: "欧盟",
645
+ store_de: "德国",
646
+ store_fr: "法国",
647
+ store_uk: "英国",
648
+ store_jp: "日本",
649
+ store_test: "测试",
650
+ country_usa: "美国",
651
+ country_canada: "加拿大",
652
+ country_australia: "澳大利亚",
653
+ country_eu: "欧洲",
654
+ country_germany: "德国",
655
+ country_france: "法国",
656
+ country_uk: "英国",
657
+ country_japan: "日本"
658
+ };
659
+ class I18n {
660
+ constructor() {
661
+ __publicField(this, "locale", "en");
662
+ __publicField(this, "dict", { en, zh });
663
+ }
664
+ setLocale(locale) {
665
+ this.locale = this.dict[locale] ? locale : "en";
666
+ }
667
+ extend(messages) {
668
+ if (!messages) return;
669
+ Object.entries(messages).forEach(([loc, entries]) => {
670
+ if (!entries) return;
671
+ this.dict[loc] = { ...this.dict[loc] || {}, ...entries };
672
+ });
673
+ }
674
+ t(key, params) {
675
+ var _a;
676
+ const table = this.dict[this.locale] || this.dict["en"] || {};
677
+ let text = table[key] ?? ((_a = this.dict["en"]) == null ? void 0 : _a[key]) ?? key;
678
+ if (params) {
679
+ Object.entries(params).forEach(([k, v]) => {
680
+ text = text.replace(new RegExp(`\\{\\s*${k}\\s*\\}`, "g"), String(v));
681
+ });
682
+ }
683
+ return text;
684
+ }
685
+ }
686
+ const wrap = (path, size = 16) => `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${path}</svg>`;
687
+ const iconX = (size = 16) => wrap('<path d="M18 6 6 18"/><path d="m6 6 12 12"/>', size);
688
+ const iconChevronDown = (size = 16) => wrap('<path d="m6 9 6 6 6-6"/>', size);
689
+ const iconChevronUp = (size = 16) => wrap('<path d="m18 15-6-6-6 6"/>', size);
690
+ const iconCheck = (size = 12) => wrap('<path d="M20 6 9 17l-5-5"/>', size);
691
+ const iconMinus = (size = 14) => wrap('<path d="M5 12h14"/>', size);
692
+ const iconPlus = (size = 14) => wrap('<path d="M5 12h14"/><path d="M12 5v14"/>', size);
693
+ const styles = `
694
+ :host, .xpm-host {
695
+ font-family: Inter, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
696
+ color: #2d3541;
697
+ }
698
+ *, *::before, *::after { box-sizing: border-box; }
699
+
700
+ .xpm-overlay {
701
+ position: fixed;
702
+ inset: 0;
703
+ background: rgba(0, 0, 0, 0.4);
704
+ z-index: 0;
705
+ opacity: 0;
706
+ transition: opacity 0.2s ease;
707
+ }
708
+ .xpm-overlay--visible { opacity: 1; }
709
+
710
+ .xpm-panel {
711
+ position: fixed;
712
+ top: 0;
713
+ right: 0;
714
+ width: 320px;
715
+ height: 100vh;
716
+ background: #fff;
717
+ display: flex;
718
+ flex-direction: column;
719
+ box-shadow:
720
+ 0 1.187px 2.373px rgba(0, 0, 0, 0.1),
721
+ 0 23.733px 23.733px rgba(0, 0, 0, 0.1);
722
+ transform: translateX(100%);
723
+ transition: transform 0.25s ease;
724
+ }
725
+ .xpm-panel--visible { transform: translateX(0); }
726
+
727
+ .xpm-header {
728
+ height: 56px;
729
+ padding: 12px;
730
+ border-bottom: 1px solid #e7e9ed;
731
+ display: flex;
732
+ align-items: center;
733
+ justify-content: space-between;
734
+ flex-shrink: 0;
735
+ background: #fff;
736
+ }
737
+ .xpm-header__title {
738
+ color: #2d3541;
739
+ font-size: 14px;
740
+ font-weight: 600;
741
+ line-height: 20px;
742
+ }
743
+ .xpm-icon-btn {
744
+ display: inline-flex;
745
+ align-items: center;
746
+ justify-content: center;
747
+ width: 24px;
748
+ height: 24px;
749
+ padding: 0;
750
+ border: 0;
751
+ border-radius: 4px;
752
+ background: transparent;
753
+ color: #2d3541;
754
+ cursor: pointer;
755
+ }
756
+ .xpm-icon-btn:hover { background: #f0f0f5; }
757
+
758
+ .xpm-banner {
759
+ position: relative;
760
+ display: flex;
761
+ align-items: center;
762
+ justify-content: space-between;
763
+ height: 40px;
764
+ padding: 8px 12px 0;
765
+ flex-shrink: 0;
766
+ background: #fff;
767
+ }
768
+ .xpm-banner__text {
769
+ min-width: 0;
770
+ color: #ff7c23;
771
+ font-size: 10px;
772
+ font-weight: 500;
773
+ line-height: 14px;
774
+ white-space: nowrap;
775
+ overflow: hidden;
776
+ text-overflow: ellipsis;
777
+ padding-right: 80px;
778
+ }
779
+
780
+ .xpm-dropdown {
781
+ position: absolute;
782
+ top: 8px;
783
+ right: 12px;
784
+ z-index: 30;
785
+ display: flex;
786
+ flex-direction: column;
787
+ gap: 4px;
788
+ width: 73px;
789
+ flex-shrink: 0;
790
+ }
791
+ .xpm-dropdown__trigger {
792
+ display: flex;
793
+ align-items: center;
794
+ gap: 4px;
795
+ width: 73px;
796
+ height: 32px;
797
+ padding: 6px 4px 6px 8px;
798
+ border: 1px solid transparent;
799
+ background: #f0f0f5;
800
+ border-radius: 4px;
801
+ color: #070b10;
802
+ cursor: pointer;
803
+ transition: all 0.15s ease;
804
+ }
805
+ .xpm-dropdown--open .xpm-dropdown__trigger {
806
+ border: 1px solid #070b10;
807
+ background: #fff;
808
+ }
809
+ .xpm-dropdown__selected {
810
+ flex: 1;
811
+ min-width: 0;
812
+ display: inline-flex;
813
+ align-items: center;
814
+ gap: 4px;
815
+ color: #070b10;
816
+ font-size: 12px;
817
+ font-weight: 400;
818
+ line-height: 16px;
819
+ overflow: hidden;
820
+ }
821
+ .xpm-dropdown__selected span {
822
+ white-space: nowrap;
823
+ overflow: hidden;
824
+ text-overflow: ellipsis;
825
+ }
826
+ .xpm-dropdown__menu {
827
+ display: flex;
828
+ flex-direction: column;
829
+ align-items: stretch;
830
+ width: 73px;
831
+ padding: 4px;
832
+ overflow: hidden;
833
+ border: 1px solid #f0f0f5;
834
+ border-radius: 6px;
835
+ background: #fff;
836
+ box-shadow:
837
+ 0 2px 8px rgba(0, 0, 0, 0.1),
838
+ 0 16px 32px rgba(0, 0, 0, 0.1);
839
+ }
840
+ .xpm-dropdown__item {
841
+ display: flex;
842
+ align-items: center;
843
+ gap: 8px;
844
+ width: 100%;
845
+ height: 32px;
846
+ padding: 8px;
847
+ border: 0;
848
+ border-radius: 4px;
849
+ background: transparent;
850
+ color: #070b10;
851
+ font-size: 12px;
852
+ font-weight: 400;
853
+ line-height: 16px;
854
+ cursor: pointer;
855
+ }
856
+ .xpm-dropdown__item:hover { background: #f0f0f5; }
857
+ .xpm-dropdown__item-icon {
858
+ width: 16px;
859
+ height: 16px;
860
+ object-fit: contain;
861
+ flex-shrink: 0;
862
+ }
863
+ .xpm-flag {
864
+ width: 15px;
865
+ height: 11px;
866
+ object-fit: cover;
867
+ flex-shrink: 0;
868
+ }
869
+
870
+ .xpm-list {
871
+ flex: 1;
872
+ min-height: 0;
873
+ overflow-y: auto;
874
+ background: #fff;
875
+ }
876
+ .xpm-list::-webkit-scrollbar { width: 4px; }
877
+ .xpm-list::-webkit-scrollbar-thumb { background: #c5cad2; border-radius: 2px; }
878
+ .xpm-list::-webkit-scrollbar-track { background: transparent; }
879
+
880
+ .xpm-placeholder {
881
+ display: flex;
882
+ flex-direction: column;
883
+ align-items: center;
884
+ justify-content: center;
885
+ gap: 8px;
886
+ height: 100%;
887
+ color: #848b96;
888
+ font-size: 12px;
889
+ }
890
+ .xpm-spinner {
891
+ width: 20px;
892
+ height: 20px;
893
+ border-radius: 50%;
894
+ border: 2px solid #e7e9ed;
895
+ border-top-color: #6e757f;
896
+ animation: xpm-spin 0.8s linear infinite;
897
+ }
898
+ @keyframes xpm-spin { to { transform: rotate(1turn); } }
899
+
900
+ .xpm-row {
901
+ display: flex;
902
+ gap: 12px;
903
+ align-items: center;
904
+ min-height: 110px;
905
+ padding: 8px 12px;
906
+ border-bottom: 1px solid rgba(83, 91, 136, 0.1);
907
+ background: #fff;
908
+ }
909
+ .xpm-row--has-options {
910
+ align-items: flex-start;
911
+ min-height: 167px;
912
+ padding: 12px;
913
+ }
914
+ .xpm-row--has-options .xpm-row__info {
915
+ min-height: 142px;
916
+ justify-content: flex-start;
917
+ }
918
+ .xpm-row--has-options .xpm-row__meta { margin-top: 0; }
919
+
920
+ .xpm-row__media {
921
+ position: relative;
922
+ width: 88px;
923
+ height: 88px;
924
+ flex-shrink: 0;
925
+ }
926
+ .xpm-row__thumb {
927
+ width: 88px;
928
+ height: 88px;
929
+ overflow: hidden;
930
+ border: 1px solid rgba(0, 0, 0, 0.08);
931
+ border-radius: 8px;
932
+ background: #f6f6fa;
933
+ }
934
+ .xpm-row__thumb img {
935
+ width: 100%;
936
+ height: 100%;
937
+ display: block;
938
+ object-fit: cover;
939
+ }
940
+ .xpm-row__sold-out {
941
+ position: absolute;
942
+ left: 0;
943
+ right: 0;
944
+ bottom: 0;
945
+ height: 24px;
946
+ display: flex;
947
+ align-items: center;
948
+ justify-content: center;
949
+ border-radius: 0 0 8px 8px;
950
+ background: rgba(0, 0, 0, 0.8);
951
+ color: #fff;
952
+ font-size: 10px;
953
+ font-weight: 400;
954
+ line-height: 14px;
955
+ }
956
+
957
+ .xpm-checkbox {
958
+ position: absolute;
959
+ left: 4px;
960
+ top: 4px;
961
+ z-index: 2;
962
+ display: inline-flex;
963
+ align-items: center;
964
+ justify-content: center;
965
+ width: 16px;
966
+ height: 16px;
967
+ cursor: pointer;
968
+ }
969
+ .xpm-checkbox input {
970
+ position: absolute;
971
+ inset: 0;
972
+ width: 100%;
973
+ height: 100%;
974
+ margin: 0;
975
+ opacity: 0;
976
+ cursor: pointer;
977
+ }
978
+ .xpm-checkbox__box {
979
+ display: inline-flex;
980
+ align-items: center;
981
+ justify-content: center;
982
+ width: 14px;
983
+ height: 14px;
984
+ border: 1px solid #d8dadf;
985
+ border-radius: 2px;
986
+ background: #fff;
987
+ color: #fff;
988
+ }
989
+ .xpm-checkbox--checked .xpm-checkbox__box {
990
+ border-color: #070b10;
991
+ background: #070b10;
992
+ }
993
+ .xpm-checkbox--disabled,
994
+ .xpm-checkbox--disabled input { cursor: not-allowed; }
995
+ .xpm-checkbox--disabled .xpm-checkbox__box {
996
+ border-color: #d8dadf;
997
+ background: #d8dadf;
998
+ }
999
+
1000
+ .xpm-row__info {
1001
+ flex: 1;
1002
+ min-width: 0;
1003
+ min-height: 80px;
1004
+ display: flex;
1005
+ flex-direction: column;
1006
+ justify-content: flex-start;
1007
+ gap: 8px;
1008
+ }
1009
+ .xpm-row__name {
1010
+ min-width: 100%;
1011
+ color: #070b10;
1012
+ font-size: 12px;
1013
+ font-weight: 600;
1014
+ line-height: 16px;
1015
+ display: -webkit-box;
1016
+ -webkit-line-clamp: 2;
1017
+ -webkit-box-orient: vertical;
1018
+ overflow: hidden;
1019
+ word-break: break-word;
1020
+ }
1021
+
1022
+ .xpm-variants {
1023
+ display: flex;
1024
+ flex-direction: column;
1025
+ gap: 4px;
1026
+ width: 100%;
1027
+ }
1028
+
1029
+ .xpm-select {
1030
+ position: relative;
1031
+ width: 100%;
1032
+ }
1033
+ .xpm-select__trigger {
1034
+ display: flex;
1035
+ align-items: center;
1036
+ justify-content: space-between;
1037
+ width: 100%;
1038
+ height: 28px;
1039
+ padding: 0 6px;
1040
+ border: 1px solid rgba(0, 0, 0, 0.1);
1041
+ border-radius: 4px;
1042
+ background: #fff;
1043
+ color: #2d3541;
1044
+ font-size: 12px;
1045
+ font-weight: 400;
1046
+ line-height: 16px;
1047
+ cursor: pointer;
1048
+ }
1049
+ .xpm-select__trigger--placeholder { color: #848b96; }
1050
+ .xpm-select--open .xpm-select__trigger { border-color: #070b10; }
1051
+ .xpm-select__menu {
1052
+ position: absolute;
1053
+ left: 0;
1054
+ right: 0;
1055
+ top: calc(100% + 4px);
1056
+ z-index: 40;
1057
+ max-height: 200px;
1058
+ overflow-y: auto;
1059
+ padding: 4px;
1060
+ border: 1px solid #f0f0f5;
1061
+ border-radius: 6px;
1062
+ background: #fff;
1063
+ box-shadow:
1064
+ 0 2px 8px rgba(0, 0, 0, 0.1),
1065
+ 0 16px 32px rgba(0, 0, 0, 0.1);
1066
+ }
1067
+ .xpm-select__option {
1068
+ display: flex;
1069
+ align-items: center;
1070
+ width: 100%;
1071
+ height: 28px;
1072
+ padding: 4px 8px;
1073
+ border: 0;
1074
+ border-radius: 4px;
1075
+ background: transparent;
1076
+ color: #2d3541;
1077
+ font-size: 12px;
1078
+ font-weight: 400;
1079
+ line-height: 16px;
1080
+ cursor: pointer;
1081
+ text-align: left;
1082
+ }
1083
+ .xpm-select__option:hover { background: #f0f0f5; }
1084
+ .xpm-select__option--selected { background: #f0f0f5; color: #070b10; font-weight: 500; }
1085
+
1086
+ .xpm-row__meta {
1087
+ display: flex;
1088
+ flex-direction: column;
1089
+ gap: 8px;
1090
+ }
1091
+ .xpm-row__meta--with-options {
1092
+ flex-direction: row;
1093
+ align-items: center;
1094
+ justify-content: space-between;
1095
+ width: 100%;
1096
+ }
1097
+
1098
+ .xpm-price-group {
1099
+ min-width: 0;
1100
+ display: flex;
1101
+ align-items: center;
1102
+ gap: 4px;
1103
+ white-space: nowrap;
1104
+ }
1105
+ .xpm-price-group__discount {
1106
+ flex-shrink: 0;
1107
+ color: #fa2730;
1108
+ font-size: 12px;
1109
+ font-weight: 600;
1110
+ line-height: 16px;
1111
+ }
1112
+ .xpm-price-group__current {
1113
+ min-width: 0;
1114
+ overflow: hidden;
1115
+ color: #070b10;
1116
+ font-size: 12px;
1117
+ font-weight: 600;
1118
+ line-height: 16px;
1119
+ text-overflow: ellipsis;
1120
+ }
1121
+ .xpm-price-group__compare {
1122
+ min-width: 0;
1123
+ overflow: hidden;
1124
+ color: #b3b9c2;
1125
+ font-size: 10px;
1126
+ font-weight: 400;
1127
+ line-height: 14px;
1128
+ text-decoration: line-through;
1129
+ text-overflow: ellipsis;
1130
+ }
1131
+
1132
+ .xpm-row__actions {
1133
+ display: flex;
1134
+ align-items: center;
1135
+ justify-content: flex-end;
1136
+ min-height: 24px;
1137
+ }
1138
+ .xpm-qty {
1139
+ display: inline-flex;
1140
+ align-items: center;
1141
+ gap: 4px;
1142
+ flex-shrink: 0;
1143
+ }
1144
+ .xpm-qty__btn {
1145
+ display: inline-flex;
1146
+ align-items: center;
1147
+ justify-content: center;
1148
+ width: 20px;
1149
+ height: 20px;
1150
+ padding: 0;
1151
+ border: 1px solid #d8dadf;
1152
+ border-radius: 2px;
1153
+ background: #fff;
1154
+ color: #070b10;
1155
+ cursor: pointer;
1156
+ }
1157
+ .xpm-qty__btn:hover:not(:disabled) { background: #f6f6fa; }
1158
+ .xpm-qty__btn:disabled { opacity: 0.4; cursor: not-allowed; }
1159
+ .xpm-qty__value {
1160
+ display: inline-flex;
1161
+ align-items: center;
1162
+ justify-content: center;
1163
+ width: 24px;
1164
+ height: 24px;
1165
+ color: #070b10;
1166
+ font-size: 12px;
1167
+ font-weight: 400;
1168
+ line-height: 16px;
1169
+ }
1170
+
1171
+ .xpm-footer {
1172
+ flex-shrink: 0;
1173
+ height: 105px;
1174
+ padding: 8px 16px;
1175
+ border-top: 1px solid rgba(81, 97, 117, 0.1);
1176
+ background: #fff;
1177
+ }
1178
+ .xpm-footer__inner {
1179
+ display: flex;
1180
+ flex-direction: column;
1181
+ justify-content: center;
1182
+ gap: 8px;
1183
+ width: 100%;
1184
+ height: 100%;
1185
+ }
1186
+ .xpm-footer__total-block {
1187
+ display: flex;
1188
+ flex-direction: column;
1189
+ gap: 4px;
1190
+ align-items: flex-start;
1191
+ }
1192
+ .xpm-footer__total-row {
1193
+ display: flex;
1194
+ align-items: center;
1195
+ gap: 4px;
1196
+ color: #070b10;
1197
+ }
1198
+ .xpm-footer__total-label {
1199
+ font-size: 12px;
1200
+ font-weight: 600;
1201
+ line-height: 16px;
1202
+ }
1203
+ .xpm-footer__total-price {
1204
+ font-size: 16px;
1205
+ font-weight: 500;
1206
+ line-height: 22px;
1207
+ }
1208
+ .xpm-footer__warning {
1209
+ margin: 0;
1210
+ color: #ff7c23;
1211
+ font-size: 12px;
1212
+ font-weight: 500;
1213
+ line-height: 16px;
1214
+ }
1215
+ .xpm-buy-now {
1216
+ width: 100%;
1217
+ height: 32px;
1218
+ padding: 6px 12px;
1219
+ border: 0;
1220
+ border-radius: 6px;
1221
+ background: #ff0035;
1222
+ color: #fff;
1223
+ font-size: 12px;
1224
+ font-weight: 600;
1225
+ line-height: 16px;
1226
+ cursor: pointer;
1227
+ }
1228
+ .xpm-buy-now:hover:not(:disabled) { background: #e60030; }
1229
+ .xpm-buy-now:disabled { opacity: 0.6; cursor: not-allowed; }
1230
+
1231
+ .xpm-toast {
1232
+ position: fixed;
1233
+ top: 24px;
1234
+ left: 50%;
1235
+ transform: translateX(-50%);
1236
+ z-index: 1000;
1237
+ padding: 8px 16px;
1238
+ border-radius: 6px;
1239
+ background: rgba(20, 20, 24, 0.92);
1240
+ color: #fff;
1241
+ font-size: 13px;
1242
+ line-height: 20px;
1243
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);
1244
+ opacity: 0;
1245
+ transition: opacity 0.18s ease;
1246
+ pointer-events: none;
1247
+ }
1248
+ .xpm-toast--visible { opacity: 1; }
1249
+ `;
1250
+ class Renderer {
1251
+ constructor(state, i18n, callbacks, zIndex) {
1252
+ __publicField(this, "host", null);
1253
+ __publicField(this, "shadow", null);
1254
+ __publicField(this, "overlay", null);
1255
+ __publicField(this, "panel", null);
1256
+ __publicField(this, "toastEl", null);
1257
+ __publicField(this, "toastTimer", null);
1258
+ __publicField(this, "docClickHandler", (e) => this.handleDocClick(e));
1259
+ this.state = state;
1260
+ this.i18n = i18n;
1261
+ this.callbacks = callbacks;
1262
+ this.zIndex = zIndex;
1263
+ }
1264
+ mount() {
1265
+ if (this.host) return;
1266
+ this.host = document.createElement("div");
1267
+ this.host.className = "xpm-host";
1268
+ this.host.style.position = "relative";
1269
+ this.host.style.zIndex = String(this.zIndex);
1270
+ this.shadow = this.host.attachShadow({ mode: "open" });
1271
+ const styleEl = document.createElement("style");
1272
+ styleEl.textContent = styles;
1273
+ this.shadow.appendChild(styleEl);
1274
+ this.overlay = document.createElement("div");
1275
+ this.overlay.className = "xpm-overlay";
1276
+ this.overlay.addEventListener("click", () => this.callbacks.onClose());
1277
+ this.shadow.appendChild(this.overlay);
1278
+ this.panel = document.createElement("div");
1279
+ this.panel.className = "xpm-panel";
1280
+ this.shadow.appendChild(this.panel);
1281
+ document.body.appendChild(this.host);
1282
+ this.shadow.addEventListener("click", this.docClickHandler);
1283
+ this.renderAll();
1284
+ requestAnimationFrame(() => {
1285
+ var _a, _b;
1286
+ (_a = this.overlay) == null ? void 0 : _a.classList.add("xpm-overlay--visible");
1287
+ (_b = this.panel) == null ? void 0 : _b.classList.add("xpm-panel--visible");
1288
+ });
1289
+ }
1290
+ unmount() {
1291
+ var _a;
1292
+ if (!this.host) return;
1293
+ (_a = this.shadow) == null ? void 0 : _a.removeEventListener("click", this.docClickHandler);
1294
+ this.host.remove();
1295
+ this.host = null;
1296
+ this.shadow = null;
1297
+ this.overlay = null;
1298
+ this.panel = null;
1299
+ this.toastEl = null;
1300
+ if (this.toastTimer) {
1301
+ clearTimeout(this.toastTimer);
1302
+ this.toastTimer = null;
1303
+ }
1304
+ }
1305
+ /** 主动触发关闭动画,调用方负责在动画完成后 unmount */
1306
+ playClose() {
1307
+ return new Promise((resolve) => {
1308
+ var _a, _b;
1309
+ (_a = this.overlay) == null ? void 0 : _a.classList.remove("xpm-overlay--visible");
1310
+ (_b = this.panel) == null ? void 0 : _b.classList.remove("xpm-panel--visible");
1311
+ setTimeout(() => resolve(), 250);
1312
+ });
1313
+ }
1314
+ setState(patch) {
1315
+ Object.assign(this.state, patch);
1316
+ this.renderAll();
1317
+ }
1318
+ /** 不重渲,仅查 / 触发某些行为 */
1319
+ getState() {
1320
+ return this.state;
1321
+ }
1322
+ showToast(message) {
1323
+ if (!this.shadow) return;
1324
+ if (!this.toastEl) {
1325
+ this.toastEl = document.createElement("div");
1326
+ this.toastEl.className = "xpm-toast";
1327
+ this.shadow.appendChild(this.toastEl);
1328
+ }
1329
+ this.toastEl.textContent = message;
1330
+ this.toastEl.classList.add("xpm-toast--visible");
1331
+ if (this.toastTimer) clearTimeout(this.toastTimer);
1332
+ this.toastTimer = window.setTimeout(() => {
1333
+ var _a;
1334
+ (_a = this.toastEl) == null ? void 0 : _a.classList.remove("xpm-toast--visible");
1335
+ }, 2200);
1336
+ }
1337
+ renderAll() {
1338
+ if (!this.panel) return;
1339
+ this.panel.innerHTML = "";
1340
+ this.panel.appendChild(this.renderHeader());
1341
+ this.panel.appendChild(this.renderBanner());
1342
+ this.panel.appendChild(this.renderList());
1343
+ this.panel.appendChild(this.renderFooter());
1344
+ }
1345
+ renderHeader() {
1346
+ const header = document.createElement("header");
1347
+ header.className = "xpm-header";
1348
+ const title = document.createElement("span");
1349
+ title.className = "xpm-header__title";
1350
+ title.textContent = this.i18n.t("shopify_supplies_kit");
1351
+ const closeBtn = document.createElement("button");
1352
+ closeBtn.className = "xpm-icon-btn";
1353
+ closeBtn.setAttribute("aria-label", this.i18n.t("close"));
1354
+ closeBtn.innerHTML = iconX(16);
1355
+ closeBtn.addEventListener("click", () => this.callbacks.onClose());
1356
+ header.appendChild(title);
1357
+ header.appendChild(closeBtn);
1358
+ return header;
1359
+ }
1360
+ renderBanner() {
1361
+ const banner = document.createElement("div");
1362
+ banner.className = "xpm-banner";
1363
+ const currentStore = PROD_STORE_OPTIONS.find((s) => s.value === this.state.currentShopName) || PROD_STORE_OPTIONS[0];
1364
+ const countryKey = currentStore ? COUNTRY_NAME_KEY_MAP[currentStore.value] || "country_usa" : "country_usa";
1365
+ const countryName = this.i18n.t(countryKey);
1366
+ const bannerText = document.createElement("span");
1367
+ bannerText.className = "xpm-banner__text";
1368
+ bannerText.textContent = this.i18n.t("current_country_notice", { country: countryName });
1369
+ const dropdown = document.createElement("div");
1370
+ dropdown.className = "xpm-dropdown" + (this.state.shopMenuOpen ? " xpm-dropdown--open" : "");
1371
+ dropdown.dataset.xpmStop = "1";
1372
+ const trigger = document.createElement("button");
1373
+ trigger.type = "button";
1374
+ trigger.className = "xpm-dropdown__trigger";
1375
+ trigger.addEventListener("click", (e) => {
1376
+ e.stopPropagation();
1377
+ this.setState({ shopMenuOpen: !this.state.shopMenuOpen, openSelect: null });
1378
+ });
1379
+ const triggerSelected = document.createElement("span");
1380
+ triggerSelected.className = "xpm-dropdown__selected";
1381
+ if (currentStore == null ? void 0 : currentStore.icon) {
1382
+ const flag = document.createElement("img");
1383
+ flag.src = currentStore.icon;
1384
+ flag.alt = "";
1385
+ flag.className = "xpm-flag";
1386
+ triggerSelected.appendChild(flag);
1387
+ }
1388
+ const triggerLabel = document.createElement("span");
1389
+ const labelKey = currentStore ? STORE_LABEL_KEY_MAP[currentStore.value] : void 0;
1390
+ triggerLabel.textContent = labelKey ? this.i18n.t(labelKey) : (currentStore == null ? void 0 : currentStore.label) || "";
1391
+ triggerSelected.appendChild(triggerLabel);
1392
+ trigger.appendChild(triggerSelected);
1393
+ const chevWrap = document.createElement("span");
1394
+ chevWrap.innerHTML = this.state.shopMenuOpen ? iconChevronUp(16) : iconChevronDown(16);
1395
+ trigger.appendChild(chevWrap);
1396
+ dropdown.appendChild(trigger);
1397
+ if (this.state.shopMenuOpen) {
1398
+ const menu = document.createElement("div");
1399
+ menu.className = "xpm-dropdown__menu";
1400
+ PROD_STORE_OPTIONS.forEach((opt) => {
1401
+ const item = document.createElement("button");
1402
+ item.type = "button";
1403
+ item.className = "xpm-dropdown__item";
1404
+ if (opt.icon) {
1405
+ const ic = document.createElement("img");
1406
+ ic.src = opt.icon;
1407
+ ic.alt = "";
1408
+ ic.className = "xpm-dropdown__item-icon";
1409
+ item.appendChild(ic);
1410
+ }
1411
+ const labelText = document.createElement("span");
1412
+ const k = STORE_LABEL_KEY_MAP[opt.value];
1413
+ labelText.textContent = k ? this.i18n.t(k) : opt.label;
1414
+ item.appendChild(labelText);
1415
+ item.addEventListener("click", (e) => {
1416
+ e.stopPropagation();
1417
+ this.setState({ shopMenuOpen: false });
1418
+ this.callbacks.onShopChange(opt.value);
1419
+ });
1420
+ menu.appendChild(item);
1421
+ });
1422
+ dropdown.appendChild(menu);
1423
+ }
1424
+ banner.appendChild(bannerText);
1425
+ banner.appendChild(dropdown);
1426
+ return banner;
1427
+ }
1428
+ renderList() {
1429
+ const list = document.createElement("div");
1430
+ list.className = "xpm-list";
1431
+ if (this.state.loading) {
1432
+ const ph = document.createElement("div");
1433
+ ph.className = "xpm-placeholder";
1434
+ const sp = document.createElement("div");
1435
+ sp.className = "xpm-spinner";
1436
+ const txt = document.createElement("span");
1437
+ txt.textContent = this.i18n.t("loading");
1438
+ ph.appendChild(sp);
1439
+ ph.appendChild(txt);
1440
+ list.appendChild(ph);
1441
+ return list;
1442
+ }
1443
+ if (this.state.productList.length === 0) {
1444
+ const ph = document.createElement("div");
1445
+ ph.className = "xpm-placeholder";
1446
+ ph.textContent = this.i18n.t("no_items_in_cart");
1447
+ list.appendChild(ph);
1448
+ return list;
1449
+ }
1450
+ this.state.productList.forEach((p) => list.appendChild(this.renderCard(p)));
1451
+ return list;
1452
+ }
1453
+ renderCard(product) {
1454
+ const keys = getOptionKeys(product);
1455
+ const hasOptions = keys.length > 0;
1456
+ const selectedOpts = this.state.productSelectedOptions[product.uid] || initSelectedOptionsForProduct(product);
1457
+ if (!this.state.productSelectedOptions[product.uid]) {
1458
+ this.state.productSelectedOptions[product.uid] = selectedOpts;
1459
+ }
1460
+ const variant = findVariantByOptions(product, selectedOpts);
1461
+ const isSoldOut = (variant == null ? void 0 : variant.outOfStock) === true;
1462
+ const isPurchasable = isVariantPurchasable(variant);
1463
+ const isChecked = isSoldOut || this.state.checkedUids.includes(product.uid);
1464
+ const row = document.createElement("div");
1465
+ row.className = "xpm-row" + (hasOptions ? " xpm-row--has-options" : "");
1466
+ const media = document.createElement("div");
1467
+ media.className = "xpm-row__media";
1468
+ const thumb = document.createElement("div");
1469
+ thumb.className = "xpm-row__thumb";
1470
+ const img = document.createElement("img");
1471
+ const imgSrc = ((variant == null ? void 0 : variant.image) && variant.image.trim() !== "" ? variant.image : product.image) || "";
1472
+ if (imgSrc) img.src = imgSrc;
1473
+ img.alt = product.displayName || product.title || "";
1474
+ thumb.appendChild(img);
1475
+ if (isSoldOut) {
1476
+ const sold = document.createElement("div");
1477
+ sold.className = "xpm-row__sold-out";
1478
+ sold.textContent = this.i18n.t("sold_out");
1479
+ thumb.appendChild(sold);
1480
+ }
1481
+ media.appendChild(thumb);
1482
+ const checkbox = document.createElement("label");
1483
+ checkbox.className = "xpm-checkbox" + (isChecked ? " xpm-checkbox--checked" : "") + (isSoldOut ? " xpm-checkbox--disabled" : "");
1484
+ const input = document.createElement("input");
1485
+ input.type = "checkbox";
1486
+ input.checked = isChecked;
1487
+ input.disabled = isSoldOut;
1488
+ input.addEventListener("change", (e) => {
1489
+ if (!isPurchasable) return;
1490
+ const next = e.target.checked;
1491
+ const set = new Set(this.state.checkedUids);
1492
+ if (next) set.add(product.uid);
1493
+ else set.delete(product.uid);
1494
+ this.setState({ checkedUids: Array.from(set) });
1495
+ });
1496
+ const box = document.createElement("span");
1497
+ box.className = "xpm-checkbox__box";
1498
+ if (isChecked) box.innerHTML = iconCheck(12);
1499
+ checkbox.appendChild(input);
1500
+ checkbox.appendChild(box);
1501
+ media.appendChild(checkbox);
1502
+ row.appendChild(media);
1503
+ const info = document.createElement("div");
1504
+ info.className = "xpm-row__info";
1505
+ const name = document.createElement("div");
1506
+ name.className = "xpm-row__name";
1507
+ name.textContent = product.displayName || product.title || "";
1508
+ name.title = name.textContent;
1509
+ info.appendChild(name);
1510
+ if (hasOptions) {
1511
+ const variants = document.createElement("div");
1512
+ variants.className = "xpm-variants";
1513
+ keys.forEach((key, idx) => {
1514
+ variants.appendChild(this.renderSelect(product, key, idx, keys, selectedOpts));
1515
+ });
1516
+ info.appendChild(variants);
1517
+ }
1518
+ const meta = document.createElement("div");
1519
+ meta.className = "xpm-row__meta" + (hasOptions ? " xpm-row__meta--with-options" : "");
1520
+ const priceGroup = document.createElement("div");
1521
+ priceGroup.className = "xpm-price-group";
1522
+ const price = parseFloat(String((variant == null ? void 0 : variant.price) ?? "0")) || 0;
1523
+ const compareAt = parseFloat(String((variant == null ? void 0 : variant.compareAtPrice) ?? "0")) || 0;
1524
+ if (compareAt > price && compareAt > 0) {
1525
+ const pct = Math.round((compareAt - price) / compareAt * 100);
1526
+ const discount = document.createElement("span");
1527
+ discount.className = "xpm-price-group__discount";
1528
+ discount.textContent = `-${pct}%`;
1529
+ priceGroup.appendChild(discount);
1530
+ }
1531
+ const current = document.createElement("span");
1532
+ current.className = "xpm-price-group__current";
1533
+ current.textContent = (variant == null ? void 0 : variant.displayPrice) || formatMoney((variant == null ? void 0 : variant.price) || "0.00");
1534
+ priceGroup.appendChild(current);
1535
+ if (variant == null ? void 0 : variant.compareAtPrice) {
1536
+ const cmp = document.createElement("span");
1537
+ cmp.className = "xpm-price-group__compare";
1538
+ cmp.textContent = variant.displayCompareAtPrice || formatMoney(variant.compareAtPrice);
1539
+ priceGroup.appendChild(cmp);
1540
+ }
1541
+ meta.appendChild(priceGroup);
1542
+ const actions = document.createElement("div");
1543
+ actions.className = "xpm-row__actions";
1544
+ const qty = document.createElement("div");
1545
+ qty.className = "xpm-qty";
1546
+ const minusBtn = document.createElement("button");
1547
+ minusBtn.className = "xpm-qty__btn";
1548
+ minusBtn.innerHTML = iconMinus(14);
1549
+ minusBtn.disabled = (product.quantity || 1) <= 1 || !isPurchasable;
1550
+ minusBtn.addEventListener("click", () => this.changeQty(product, -1));
1551
+ const qtyVal = document.createElement("span");
1552
+ qtyVal.className = "xpm-qty__value";
1553
+ qtyVal.textContent = String(product.quantity || 1);
1554
+ const plusBtn = document.createElement("button");
1555
+ plusBtn.className = "xpm-qty__btn";
1556
+ plusBtn.innerHTML = iconPlus(14);
1557
+ plusBtn.disabled = !isPurchasable;
1558
+ plusBtn.addEventListener("click", () => this.changeQty(product, 1));
1559
+ qty.appendChild(minusBtn);
1560
+ qty.appendChild(qtyVal);
1561
+ qty.appendChild(plusBtn);
1562
+ actions.appendChild(qty);
1563
+ meta.appendChild(actions);
1564
+ info.appendChild(meta);
1565
+ row.appendChild(info);
1566
+ return row;
1567
+ }
1568
+ renderSelect(product, key, index, keys, selected) {
1569
+ var _a, _b;
1570
+ const open = ((_a = this.state.openSelect) == null ? void 0 : _a.uid) === product.uid && ((_b = this.state.openSelect) == null ? void 0 : _b.key) === key;
1571
+ const wrap2 = document.createElement("div");
1572
+ wrap2.className = "xpm-select" + (open ? " xpm-select--open" : "");
1573
+ wrap2.dataset.xpmStop = "1";
1574
+ const trigger = document.createElement("button");
1575
+ trigger.type = "button";
1576
+ trigger.className = "xpm-select__trigger";
1577
+ const currentVal = selected[key] || "";
1578
+ if (!currentVal) trigger.classList.add("xpm-select__trigger--placeholder");
1579
+ const label = document.createElement("span");
1580
+ label.textContent = currentVal || "";
1581
+ trigger.appendChild(label);
1582
+ const chev = document.createElement("span");
1583
+ chev.innerHTML = open ? iconChevronUp(14) : iconChevronDown(14);
1584
+ trigger.appendChild(chev);
1585
+ trigger.addEventListener("click", (e) => {
1586
+ e.stopPropagation();
1587
+ this.setState({
1588
+ openSelect: open ? null : { uid: product.uid, key },
1589
+ shopMenuOpen: false
1590
+ });
1591
+ });
1592
+ wrap2.appendChild(trigger);
1593
+ if (open) {
1594
+ const values = getOptionValues(product, keys, index, selected);
1595
+ const menu = document.createElement("div");
1596
+ menu.className = "xpm-select__menu";
1597
+ values.forEach((val) => {
1598
+ const opt = document.createElement("button");
1599
+ opt.type = "button";
1600
+ opt.className = "xpm-select__option" + (val === currentVal ? " xpm-select__option--selected" : "");
1601
+ opt.textContent = val;
1602
+ opt.addEventListener("click", (e) => {
1603
+ e.stopPropagation();
1604
+ this.handleVariantChange(product, keys, key, index, val);
1605
+ });
1606
+ menu.appendChild(opt);
1607
+ });
1608
+ wrap2.appendChild(menu);
1609
+ }
1610
+ return wrap2;
1611
+ }
1612
+ handleVariantChange(product, keys, key, index, value) {
1613
+ const next = { ...this.state.productSelectedOptions[product.uid] || {} };
1614
+ next[key] = value;
1615
+ for (let i = index + 1; i < keys.length; i++) {
1616
+ const nk = keys[i];
1617
+ next[nk] = "";
1618
+ const tempSelected = { ...next };
1619
+ const values = getOptionValues(product, keys, i, tempSelected);
1620
+ if (values[0]) next[nk] = values[0];
1621
+ }
1622
+ const nextOptions = {
1623
+ ...this.state.productSelectedOptions,
1624
+ [product.uid]: next
1625
+ };
1626
+ const variant = findVariantByOptions(product, next);
1627
+ const nextChecked = new Set(this.state.checkedUids);
1628
+ if (isVariantPurchasable(variant)) {
1629
+ nextChecked.add(product.uid);
1630
+ } else {
1631
+ nextChecked.delete(product.uid);
1632
+ }
1633
+ this.setState({
1634
+ productSelectedOptions: nextOptions,
1635
+ checkedUids: Array.from(nextChecked),
1636
+ openSelect: null
1637
+ });
1638
+ }
1639
+ changeQty(product, delta) {
1640
+ const next = (product.quantity || 1) + delta;
1641
+ if (next < 1) return;
1642
+ product.quantity = next;
1643
+ this.setState({});
1644
+ }
1645
+ renderFooter() {
1646
+ const footer = document.createElement("footer");
1647
+ footer.className = "xpm-footer";
1648
+ const inner = document.createElement("div");
1649
+ inner.className = "xpm-footer__inner";
1650
+ let total = 0;
1651
+ let hasPurchasable = false;
1652
+ this.state.checkedUids.forEach((uid) => {
1653
+ const product = this.state.productList.find((p) => p.uid === uid);
1654
+ if (!product) return;
1655
+ const options = this.state.productSelectedOptions[uid] || {};
1656
+ const variant = findVariantByOptions(product, options);
1657
+ if (!variant) return;
1658
+ if (isVariantPurchasable(variant)) hasPurchasable = true;
1659
+ if (variant.price) {
1660
+ total += (parseFloat(String(variant.price)) || 0) * (product.quantity || 1);
1661
+ }
1662
+ });
1663
+ const totalBlock = document.createElement("div");
1664
+ totalBlock.className = "xpm-footer__total-block";
1665
+ const totalRow = document.createElement("div");
1666
+ totalRow.className = "xpm-footer__total-row";
1667
+ const tl = document.createElement("span");
1668
+ tl.className = "xpm-footer__total-label";
1669
+ tl.textContent = this.i18n.t("total");
1670
+ const tp = document.createElement("span");
1671
+ tp.className = "xpm-footer__total-price";
1672
+ tp.textContent = formatMoney(total.toFixed(2));
1673
+ totalRow.appendChild(tl);
1674
+ totalRow.appendChild(tp);
1675
+ const warn = document.createElement("p");
1676
+ warn.className = "xpm-footer__warning";
1677
+ warn.textContent = this.i18n.t("discount_notice");
1678
+ totalBlock.appendChild(totalRow);
1679
+ totalBlock.appendChild(warn);
1680
+ const buyBtn = document.createElement("button");
1681
+ buyBtn.className = "xpm-buy-now";
1682
+ buyBtn.textContent = this.state.checkoutLoading ? this.i18n.t("loading") : this.i18n.t("buy_now");
1683
+ buyBtn.disabled = !hasPurchasable || this.state.checkoutLoading;
1684
+ buyBtn.addEventListener("click", () => this.callbacks.onCheckout());
1685
+ inner.appendChild(totalBlock);
1686
+ inner.appendChild(buyBtn);
1687
+ footer.appendChild(inner);
1688
+ return footer;
1689
+ }
1690
+ handleDocClick(e) {
1691
+ const path = e.composedPath();
1692
+ const insideDropdown = path.some(
1693
+ (n) => {
1694
+ var _a;
1695
+ return ((_a = n == null ? void 0 : n.dataset) == null ? void 0 : _a.xpmStop) === "1";
1696
+ }
1697
+ );
1698
+ if (insideDropdown) return;
1699
+ if (this.state.openSelect || this.state.shopMenuOpen) {
1700
+ this.setState({ openSelect: null, shopMenuOpen: false });
1701
+ }
1702
+ }
1703
+ }
1704
+ const DEFAULT_Z_INDEX = 9999;
1705
+ class PurchaseModal {
1706
+ constructor() {
1707
+ __publicField(this, "options", null);
1708
+ __publicField(this, "apiBaseUrl", DEFAULT_API_BASE_URL);
1709
+ __publicField(this, "i18n", new I18n());
1710
+ __publicField(this, "shopify", new ShopifyClient());
1711
+ __publicField(this, "renderer", null);
1712
+ __publicField(this, "state", this.createInitialState());
1713
+ __publicField(this, "accessoryCache", null);
1714
+ }
1715
+ createInitialState() {
1716
+ return {
1717
+ productList: [],
1718
+ currentShopName: "",
1719
+ loading: false,
1720
+ checkoutLoading: false,
1721
+ checkedUids: [],
1722
+ productSelectedOptions: {},
1723
+ shopMenuOpen: false,
1724
+ openSelect: null
1725
+ };
1726
+ }
1727
+ init(options) {
1728
+ if (!options || !options.generatorId || !options.accessoryType) {
1729
+ throw new Error("[GeneratorMaterialPurchase] init requires generatorId and accessoryType");
1730
+ }
1731
+ this.options = options;
1732
+ this.apiBaseUrl = options.apiBaseUrl || DEFAULT_API_BASE_URL;
1733
+ this.i18n.setLocale(options.locale || "en");
1734
+ this.i18n.extend(options.messages);
1735
+ }
1736
+ async open() {
1737
+ var _a, _b;
1738
+ if (!this.options) {
1739
+ throw new Error("[GeneratorMaterialPurchase] call init() before open()");
1740
+ }
1741
+ this.state = this.createInitialState();
1742
+ this.renderer = new Renderer(
1743
+ this.state,
1744
+ this.i18n,
1745
+ {
1746
+ onClose: () => this.close(),
1747
+ onShopChange: (v) => this.handleShopChange(v),
1748
+ onCheckout: () => this.handleCheckout()
1749
+ },
1750
+ this.options.zIndex || DEFAULT_Z_INDEX
1751
+ );
1752
+ this.renderer.mount();
1753
+ this.renderer.setState({ loading: true });
1754
+ try {
1755
+ const cached = localStorage.getItem(STORAGE_KEYS.CURRENT_SHOP) || "";
1756
+ let initialShop = cached;
1757
+ if (!initialShop || !PROD_STORE_OPTIONS.find((s) => s.value === initialShop)) {
1758
+ try {
1759
+ const loc = await fetchIpLocation(this.apiBaseUrl);
1760
+ initialShop = countryToSite(loc);
1761
+ } catch {
1762
+ initialShop = ShopifyStoreName.MAKEBLOCK_XTOOL;
1763
+ }
1764
+ }
1765
+ await this.shopify.init(initialShop);
1766
+ this.renderer.setState({ currentShopName: this.shopify.currentShopName });
1767
+ await this.loadProducts(false);
1768
+ } catch (e) {
1769
+ console.error("[GeneratorMaterialPurchase] open failed:", e);
1770
+ (_b = (_a = this.options).onError) == null ? void 0 : _b.call(_a, e);
1771
+ this.renderer.setState({ loading: false });
1772
+ this.renderer.showToast(this.i18n.t("failed_load_product_list"));
1773
+ }
1774
+ }
1775
+ close() {
1776
+ var _a, _b;
1777
+ if (!this.renderer) return;
1778
+ const r = this.renderer;
1779
+ this.renderer = null;
1780
+ r.playClose().then(() => r.unmount());
1781
+ (_b = (_a = this.options) == null ? void 0 : _a.onClose) == null ? void 0 : _b.call(_a);
1782
+ }
1783
+ destroy() {
1784
+ if (this.renderer) {
1785
+ this.renderer.unmount();
1786
+ this.renderer = null;
1787
+ }
1788
+ this.options = null;
1789
+ this.shopify = new ShopifyClient();
1790
+ this.accessoryCache = null;
1791
+ }
1792
+ async handleShopChange(shopName) {
1793
+ var _a, _b;
1794
+ if (!this.renderer || !this.options) return;
1795
+ this.renderer.setState({
1796
+ loading: true,
1797
+ productList: [],
1798
+ checkedUids: [],
1799
+ productSelectedOptions: {}
1800
+ });
1801
+ try {
1802
+ await this.shopify.changeShop(shopName);
1803
+ this.renderer.setState({ currentShopName: this.shopify.currentShopName });
1804
+ await this.loadProducts(true);
1805
+ } catch (e) {
1806
+ console.error("[GeneratorMaterialPurchase] handleShopChange failed:", e);
1807
+ (_b = (_a = this.options).onError) == null ? void 0 : _b.call(_a, e);
1808
+ this.renderer.setState({ loading: false });
1809
+ this.renderer.showToast(this.i18n.t("failed_load_product_list"));
1810
+ }
1811
+ }
1812
+ async loadProducts(force) {
1813
+ var _a, _b, _c;
1814
+ if (!this.renderer || !this.options) return;
1815
+ try {
1816
+ let raw;
1817
+ if (!force && ((_a = this.accessoryCache) == null ? void 0 : _a.length)) {
1818
+ raw = this.accessoryCache;
1819
+ } else {
1820
+ raw = await fetchAccessoryPack(this.apiBaseUrl, {
1821
+ store: this.shopify.currentShopName,
1822
+ generatorId: this.options.generatorId,
1823
+ accessoryType: this.options.accessoryType
1824
+ });
1825
+ this.accessoryCache = raw;
1826
+ }
1827
+ const list = (raw || []).map((item, idx) => ({
1828
+ ...item,
1829
+ uid: item.uid ?? assignUid(item, idx),
1830
+ quantity: item.quantity || 1
1831
+ }));
1832
+ refreshProductPrices(list);
1833
+ const initialChecked = [];
1834
+ list.forEach((p) => {
1835
+ const variant = findVariantByOptions(p, {});
1836
+ if (isVariantPurchasable(variant)) initialChecked.push(p.uid);
1837
+ });
1838
+ this.renderer.setState({
1839
+ productList: list,
1840
+ checkedUids: initialChecked,
1841
+ productSelectedOptions: {},
1842
+ loading: false
1843
+ });
1844
+ } catch (e) {
1845
+ console.error("[GeneratorMaterialPurchase] loadProducts failed:", e);
1846
+ (_c = (_b = this.options).onError) == null ? void 0 : _c.call(_b, e);
1847
+ this.renderer.setState({ loading: false });
1848
+ this.renderer.showToast(this.i18n.t("failed_load_product_list"));
1849
+ }
1850
+ }
1851
+ async handleCheckout() {
1852
+ var _a, _b, _c;
1853
+ if (!this.renderer || !this.options) return;
1854
+ if (this.state.checkedUids.length === 0) return;
1855
+ const options = this.options;
1856
+ this.renderer.setState({ checkoutLoading: true });
1857
+ try {
1858
+ let skippedOutOfStock = 0;
1859
+ const lines = this.state.checkedUids.map((uid) => {
1860
+ const product = this.state.productList.find((p) => p.uid === uid);
1861
+ if (!product) return null;
1862
+ const options2 = this.state.productSelectedOptions[uid] || {};
1863
+ const variant = findVariantByOptions(product, options2);
1864
+ if (!(variant == null ? void 0 : variant.id)) return null;
1865
+ if (!isVariantPurchasable(variant)) {
1866
+ skippedOutOfStock += 1;
1867
+ return null;
1868
+ }
1869
+ return {
1870
+ merchandiseId: String(variant.id),
1871
+ quantity: product.quantity || 1
1872
+ };
1873
+ }).filter(Boolean);
1874
+ if (lines.length === 0) {
1875
+ const key = skippedOutOfStock > 0 ? "selected_items_out_of_stock" : "no_valid_products_selected";
1876
+ this.renderer.showToast(this.i18n.t(key));
1877
+ return;
1878
+ }
1879
+ let trackId;
1880
+ try {
1881
+ const productContent = lines.map((l) => ({
1882
+ variantId: l.merchandiseId,
1883
+ productQuantity: l.quantity,
1884
+ store: this.shopify.currentShopName,
1885
+ relatedObjectId: 1,
1886
+ relatedObjectType: options.generatorId,
1887
+ pageSource: "generator"
1888
+ }));
1889
+ const res = await fetchDistributionTrackId(this.apiBaseUrl, productContent);
1890
+ trackId = res == null ? void 0 : res.trackId;
1891
+ } catch (e) {
1892
+ console.warn("[GeneratorMaterialPurchase] fetchDistributionTrackId failed:", e);
1893
+ }
1894
+ const cart = await this.shopify.createCheckout(lines, { trackId });
1895
+ if (cart == null ? void 0 : cart.checkoutUrl) {
1896
+ if (options.onCheckoutSuccess) {
1897
+ options.onCheckoutSuccess(cart.checkoutUrl);
1898
+ } else {
1899
+ window.open(cart.checkoutUrl, "_blank");
1900
+ }
1901
+ }
1902
+ } catch (e) {
1903
+ console.error("[GeneratorMaterialPurchase] checkout failed:", e);
1904
+ (_a = options.onError) == null ? void 0 : _a.call(options, e);
1905
+ (_b = this.renderer) == null ? void 0 : _b.showToast(this.i18n.t("checkout_failed"));
1906
+ } finally {
1907
+ (_c = this.renderer) == null ? void 0 : _c.setState({ checkoutLoading: false });
1908
+ }
1909
+ }
1910
+ }
1911
+ const generatorMaterialPurchaseInstance = new PurchaseModal();
1912
+ const api = {
1913
+ init: (options) => generatorMaterialPurchaseInstance.init(options),
1914
+ open: () => generatorMaterialPurchaseInstance.open(),
1915
+ close: () => generatorMaterialPurchaseInstance.close(),
1916
+ destroy: () => generatorMaterialPurchaseInstance.destroy()
1917
+ };
1918
+ if (typeof window !== "undefined") {
1919
+ window.GeneratorMaterialPurchase = api;
1920
+ }
1921
+ export {
1922
+ api as GeneratorMaterialPurchase,
1923
+ api as default
1924
+ };
1925
+ //# sourceMappingURL=index.es.js.map