@umxr/bagsy 0.0.0-stage → 0.2.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,566 @@
1
+ import {
2
+ clearVisitorId,
3
+ createTransport,
4
+ getList,
5
+ getSharedList,
6
+ getVisitorId,
7
+ iterateItems,
8
+ listLists,
9
+ lookupSaved,
10
+ mergeVisitor,
11
+ peekVisitorId,
12
+ productGid,
13
+ removeProduct,
14
+ shareList,
15
+ toggleItem,
16
+ unshareList,
17
+ variantGid
18
+ } from "./chunk-QV3JPND6.js";
19
+
20
+ // ../element/src/store.ts
21
+ var BagsyStore = class extends EventTarget {
22
+ constructor(config = {}) {
23
+ super();
24
+ this.config = config;
25
+ const root = this.root = (config.root ?? "/").replace(/\/$/, "");
26
+ const visitorId = config.customer ? void 0 : getVisitorId();
27
+ this.transport = createTransport({
28
+ mode: "proxy",
29
+ proxyPath: `${root}/apps/bagsy`,
30
+ ...config.baseUrl && { baseUrl: config.baseUrl },
31
+ ...visitorId && { visitorId },
32
+ ...config.fetch && { fetch: config.fetch }
33
+ });
34
+ this.ready = this.mergeGuestIfNeeded();
35
+ }
36
+ config;
37
+ /** Use with any `@umxr/bagsy` operation, e.g. `listLists(Bagsy.transport)`. */
38
+ transport;
39
+ saved = /* @__PURE__ */ new Map();
40
+ pending = /* @__PURE__ */ new Set();
41
+ flush;
42
+ listeners = /* @__PURE__ */ new Map();
43
+ /** Resolves once any pending guest merge has finished. */
44
+ ready;
45
+ count;
46
+ /** Storefront root without a trailing slash: '' or '/en-gb'. */
47
+ root;
48
+ /** Fold the pre-login guest list into the customer's, once. */
49
+ async mergeGuestIfNeeded() {
50
+ if (!this.config.customer) return;
51
+ const guest = peekVisitorId();
52
+ if (!guest) return;
53
+ try {
54
+ await mergeVisitor(this.transport, guest);
55
+ clearVisitorId();
56
+ } catch (error) {
57
+ this.emit("bagsy:error", { error });
58
+ }
59
+ }
60
+ /** Subscribe to a product's saved state; triggers a (batched) lookup. */
61
+ watch(productId, listener) {
62
+ const id = productGid(productId);
63
+ let set = this.listeners.get(id);
64
+ if (!set) this.listeners.set(id, set = /* @__PURE__ */ new Set());
65
+ set.add(listener);
66
+ if (!this.saved.has(id)) this.queue(id);
67
+ else listener();
68
+ return () => set.delete(listener);
69
+ }
70
+ isSaved(productId, variantId) {
71
+ const entries = this.saved.get(productGid(productId));
72
+ if (!entries) return void 0;
73
+ if (variantId === void 0) return entries.length > 0;
74
+ const gid = variantGid(variantId);
75
+ return entries.some((entry) => !entry.variantId || entry.variantId === gid);
76
+ }
77
+ /** Optimistic: listeners see the new state immediately and revert on failure. */
78
+ async toggle(productId, variantId) {
79
+ await this.ready;
80
+ const id = productGid(productId);
81
+ const previous = this.saved.get(id) ?? [];
82
+ const wasSaved = this.isSaved(id, variantId) ?? false;
83
+ const variant = variantId === void 0 ? void 0 : variantGid(variantId);
84
+ const whole = wasSaved && variant && previous.some((entry) => !entry.variantId);
85
+ this.saved.set(
86
+ id,
87
+ wasSaved ? previous.filter((entry) => variant && !whole ? entry.variantId !== variant : false) : [...previous, { listId: "default", itemId: "pending", ...variant && { variantId: variant } }]
88
+ );
89
+ this.adjustCount(wasSaved ? -1 : 1);
90
+ this.notify(id);
91
+ try {
92
+ const { saved } = whole ? (await removeProduct(this.transport, id), { saved: false }) : await toggleItem(this.transport, id, variant ? { variantId: variant } : {});
93
+ if (saved === wasSaved) {
94
+ this.queue(id);
95
+ void this.loadCount().catch(() => {
96
+ });
97
+ }
98
+ this.emit("bagsy:change", { productId: id, ...variant && { variantId: variant }, saved });
99
+ return saved;
100
+ } catch (error) {
101
+ this.saved.set(id, previous);
102
+ this.adjustCount(wasSaved ? 1 : -1);
103
+ this.notify(id);
104
+ this.queue(id);
105
+ this.emit("bagsy:error", { error, productId: id });
106
+ throw error;
107
+ }
108
+ }
109
+ /** Items in the default list, newest first. */
110
+ async *items() {
111
+ await this.ready;
112
+ yield* iterateItems(this.transport, "default");
113
+ }
114
+ countRequest;
115
+ /** Load the count once per page; later calls share the first request. */
116
+ ensureCount() {
117
+ return this.countRequest ??= this.loadCount();
118
+ }
119
+ /** Number of items in the default list, for header badges. */
120
+ async loadCount() {
121
+ await this.ready;
122
+ const lists = await listLists(this.transport);
123
+ this.count = lists.find((list) => list.isDefault)?.itemCount ?? 0;
124
+ this.emit("bagsy:count", { count: this.count });
125
+ return this.count;
126
+ }
127
+ adjustCount(delta) {
128
+ if (this.count === void 0) return;
129
+ this.count = Math.max(0, this.count + delta);
130
+ this.emit("bagsy:count", { count: this.count });
131
+ }
132
+ queue(id) {
133
+ this.pending.add(id);
134
+ this.flush ??= Promise.resolve().then(async () => {
135
+ const ids = [...this.pending];
136
+ this.pending.clear();
137
+ this.flush = void 0;
138
+ try {
139
+ await this.ready;
140
+ const lookup = await lookupSaved(this.transport, ids);
141
+ for (const pid of ids) {
142
+ this.saved.set(pid, lookup.get(pid));
143
+ this.notify(pid);
144
+ }
145
+ } catch (error) {
146
+ this.emit("bagsy:error", { error });
147
+ }
148
+ });
149
+ }
150
+ notify(id) {
151
+ for (const listener of this.listeners.get(id) ?? []) listener();
152
+ }
153
+ emit(type, detail) {
154
+ const event = new CustomEvent(type, { detail });
155
+ this.dispatchEvent(event);
156
+ globalThis.document?.dispatchEvent(new CustomEvent(type, { detail }));
157
+ }
158
+ };
159
+ var shared;
160
+ function getStore() {
161
+ if (shared) return shared;
162
+ const script = globalThis.document?.getElementById("bagsy-config");
163
+ let config = {};
164
+ try {
165
+ config = script?.textContent ? JSON.parse(script.textContent) : {};
166
+ } catch {
167
+ }
168
+ return shared = new BagsyStore(config);
169
+ }
170
+ function setStore(store) {
171
+ shared = store;
172
+ }
173
+
174
+ // ../element/src/button.ts
175
+ var VARIANT_INPUT = 'form[action*="/cart/add"] [name=id]';
176
+ var VARIANT_EVENTS = ["change", "variant:change", "variant:update"];
177
+ var HEART = '<svg part="icon" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><path d="M12 20.5 10.6 19.2C5.6 14.7 2.5 11.9 2.5 8.4 2.5 5.6 4.7 3.5 7.4 3.5c1.6 0 3.2.8 4.1 2 .9-1.2 2.5-2 4.1-2 2.7 0 4.9 2.1 4.9 4.9 0 3.5-3.1 6.3-8.1 10.8z"/></svg>';
178
+ var BagsyButton = class extends HTMLElement {
179
+ static observedAttributes = ["product-id", "variant-id", "label", "label-saved"];
180
+ button;
181
+ label;
182
+ unwatch;
183
+ busy = false;
184
+ // Themes update the variant input after their own change handlers run.
185
+ onVariantChange = () => setTimeout(() => this.syncVariant());
186
+ constructor() {
187
+ super();
188
+ const root = this.attachShadow({ mode: "open" });
189
+ root.innerHTML = `<button part="button" type="button" aria-pressed="false" aria-busy="true"><slot>${HEART}<span part="label"></span></slot></button>`;
190
+ this.button = root.querySelector("button");
191
+ this.label = root.querySelector("[part=label]");
192
+ this.button.addEventListener("click", (event) => this.onClick(event));
193
+ }
194
+ connectedCallback() {
195
+ if (this.hasAttribute("variant-source")) {
196
+ this.syncVariant();
197
+ for (const type of VARIANT_EVENTS) document.addEventListener(type, this.onVariantChange);
198
+ }
199
+ this.subscribe();
200
+ }
201
+ disconnectedCallback() {
202
+ for (const type of VARIANT_EVENTS) document.removeEventListener(type, this.onVariantChange);
203
+ this.unwatch?.();
204
+ this.unwatch = void 0;
205
+ }
206
+ /** Follow the selected variant in the product form, preferring this section's form. */
207
+ syncVariant() {
208
+ const input = this.closest(".shopify-section")?.querySelector(VARIANT_INPUT) ?? document.querySelector(VARIANT_INPUT);
209
+ if (input?.value && input.value !== this.variantId) this.setAttribute("variant-id", input.value);
210
+ }
211
+ attributeChangedCallback(name, previous, next) {
212
+ if (previous === next) return;
213
+ if ((name === "product-id" || name === "variant-id") && this.isConnected) this.subscribe();
214
+ else this.render();
215
+ }
216
+ get productId() {
217
+ return this.getAttribute("product-id") ?? "";
218
+ }
219
+ get variantId() {
220
+ return this.getAttribute("variant-id") || void 0;
221
+ }
222
+ subscribe() {
223
+ this.unwatch?.();
224
+ if (!this.productId) return;
225
+ try {
226
+ this.unwatch = getStore().watch(this.productId, () => this.render());
227
+ } catch (error) {
228
+ console.warn("<bagsy-button>:", error);
229
+ }
230
+ this.render();
231
+ }
232
+ render() {
233
+ const saved = this.productId ? getStore().isSaved(this.productId, this.variantId) : void 0;
234
+ const text = saved ? this.getAttribute("label-saved") ?? "Saved" : this.getAttribute("label") ?? "Save";
235
+ this.toggleAttribute("saved", saved === true);
236
+ this.button.setAttribute("aria-pressed", String(saved === true));
237
+ const busy = String(saved === void 0 || this.busy);
238
+ this.button.setAttribute("aria-busy", busy);
239
+ this.setAttribute("aria-busy", busy);
240
+ this.button.setAttribute("aria-label", text);
241
+ this.label.textContent = text;
242
+ }
243
+ async onClick(event) {
244
+ event.preventDefault();
245
+ event.stopPropagation();
246
+ if (this.busy || !this.productId || getStore().isSaved(this.productId, this.variantId) === void 0) return;
247
+ this.busy = true;
248
+ this.render();
249
+ try {
250
+ await getStore().toggle(this.productId, this.variantId);
251
+ } catch {
252
+ } finally {
253
+ this.busy = false;
254
+ this.render();
255
+ }
256
+ }
257
+ };
258
+
259
+ // ../element/src/count.ts
260
+ var BagsyCount = class extends HTMLElement {
261
+ onCount = () => this.render();
262
+ connectedCallback() {
263
+ const store = getStore();
264
+ store.addEventListener("bagsy:count", this.onCount);
265
+ if (store.count === void 0) store.ensureCount().catch(() => {
266
+ });
267
+ this.render();
268
+ }
269
+ disconnectedCallback() {
270
+ getStore().removeEventListener("bagsy:count", this.onCount);
271
+ }
272
+ render() {
273
+ const count = getStore().count;
274
+ this.textContent = count === void 0 ? "" : String(count);
275
+ this.toggleAttribute("hidden", this.hasAttribute("hide-empty") && !count);
276
+ }
277
+ };
278
+
279
+ // ../element/src/wishlist.ts
280
+ var STOREFRONT_API_VERSION = "2026-10";
281
+ var MAX_ITEMS = 250;
282
+ var BATCH = 50;
283
+ var SHARE_PARAM = "bagsy_share";
284
+ var PRODUCTS_QUERY = `query BagsyWishlist($ids: [ID!]!, $country: CountryCode, $language: LanguageCode)
285
+ @inContext(country: $country, language: $language) {
286
+ nodes(ids: $ids) {
287
+ ... on Product {
288
+ id
289
+ title
290
+ handle
291
+ availableForSale
292
+ featuredImage { url altText width height }
293
+ priceRange { minVariantPrice { amount currencyCode } }
294
+ }
295
+ ... on ProductVariant {
296
+ id
297
+ title
298
+ availableForSale
299
+ image { url altText width height }
300
+ price { amount currencyCode }
301
+ product { id title handle featuredImage { url altText width height } }
302
+ }
303
+ }
304
+ }`;
305
+ var toCard = (node) => "product" in node ? {
306
+ productId: node.product.id,
307
+ variantId: node.id,
308
+ title: node.product.title,
309
+ // Shopify names the only variant of a single-variant product "Default Title".
310
+ variantTitle: node.title === "Default Title" ? void 0 : node.title,
311
+ handle: node.product.handle,
312
+ available: node.availableForSale,
313
+ image: node.image ?? node.product.featuredImage,
314
+ price: node.price
315
+ } : {
316
+ productId: node.id,
317
+ title: node.title,
318
+ handle: node.handle,
319
+ available: node.availableForSale,
320
+ image: node.featuredImage,
321
+ price: node.priceRange.minVariantPrice
322
+ };
323
+ var keyOf = (item) => item.variantId ?? item.productId;
324
+ var BagsyWishlist = class extends HTMLElement {
325
+ shared = false;
326
+ onChange = (event) => {
327
+ const { productId, variantId, saved } = event.detail;
328
+ if (saved || this.shared) return;
329
+ for (const card of this.querySelectorAll(".bagsy-wishlist__item")) {
330
+ if (variantId ? card.dataset.key === variantId : card.dataset.productId === productId) card.remove();
331
+ }
332
+ if (!this.querySelector(".bagsy-wishlist__item")) this.renderEmpty(this.getAttribute("empty-text") ?? "Your wishlist is empty.");
333
+ };
334
+ text = (name, fallback) => this.getAttribute(name) ?? fallback;
335
+ connectedCallback() {
336
+ getStore().addEventListener("bagsy:change", this.onChange);
337
+ this.setAttribute("aria-busy", "true");
338
+ const token = new URLSearchParams(location.search).get(SHARE_PARAM);
339
+ (token ? this.loadShared(token) : this.loadOwn()).catch((error) => {
340
+ console.warn("<bagsy-wishlist>:", error);
341
+ this.dispatchEvent(new CustomEvent("bagsy:error", { detail: { error }, bubbles: true }));
342
+ }).finally(() => this.removeAttribute("aria-busy"));
343
+ }
344
+ disconnectedCallback() {
345
+ getStore().removeEventListener("bagsy:change", this.onChange);
346
+ }
347
+ async loadOwn() {
348
+ const store = getStore();
349
+ const items = [];
350
+ for await (const item of store.items()) {
351
+ items.push(item);
352
+ if (items.length >= MAX_ITEMS) break;
353
+ }
354
+ const cards = items.length ? await this.cards(items) : [];
355
+ if (!cards.length) return this.renderEmpty(this.text("empty-text", "Your wishlist is empty."));
356
+ const nodes = [];
357
+ if (this.hasAttribute("share")) {
358
+ const list = await getList(store.transport, "default");
359
+ nodes.push(this.shareControls(Boolean(list.shareToken)));
360
+ }
361
+ nodes.push(this.grid(cards, this.text("save-label", "Save"), this.text("remove-label", "Remove from wishlist")));
362
+ this.replaceChildren(...nodes);
363
+ }
364
+ async loadShared(token) {
365
+ this.shared = true;
366
+ const t = getStore().transport;
367
+ const items = [];
368
+ let name = "";
369
+ let after;
370
+ try {
371
+ do {
372
+ const page = await getSharedList(t, token, { limit: 100, after });
373
+ name = page.list.name;
374
+ items.push(...page.items.data);
375
+ after = page.items.pageInfo.hasNextPage ? page.items.pageInfo.endCursor ?? void 0 : void 0;
376
+ } while (after && items.length < MAX_ITEMS);
377
+ } catch (error) {
378
+ if (error?.status === 404) {
379
+ return this.renderEmpty(this.text("shared-missing-text", "This shared wishlist is no longer available."));
380
+ }
381
+ throw error;
382
+ }
383
+ this.toggleAttribute("shared-view", true);
384
+ const heading = document.createElement("h2");
385
+ heading.className = "bagsy-wishlist__shared";
386
+ heading.textContent = `${this.text("shared-heading", "Shared wishlist")}: ${name}`;
387
+ const cards = items.length ? await this.cards(items) : [];
388
+ this.replaceChildren(
389
+ heading,
390
+ cards.length ? this.grid(cards, this.text("save-label", "Save"), this.text("saved-label", "Saved")) : this.message(this.text("empty-text", "Your wishlist is empty."))
391
+ );
392
+ }
393
+ /** Saved items → live cards, in saved order. Deleted or unpublished products drop out. */
394
+ async cards(items) {
395
+ const ids = [...new Set(items.map(keyOf))];
396
+ const variables = {
397
+ country: this.getAttribute("country") || void 0,
398
+ language: this.getAttribute("language")?.toUpperCase().replace("-", "_") || void 0
399
+ };
400
+ const batches = [];
401
+ for (let i = 0; i < ids.length; i += BATCH) batches.push(ids.slice(i, i + BATCH));
402
+ const results = await Promise.all(
403
+ batches.map(async (batch) => {
404
+ const response = await fetch(`/api/${STOREFRONT_API_VERSION}/graphql.json`, {
405
+ method: "POST",
406
+ headers: { "Content-Type": "application/json", Accept: "application/json" },
407
+ body: JSON.stringify({ query: PRODUCTS_QUERY, variables: { ...variables, ids: batch } })
408
+ });
409
+ if (!response.ok) throw new Error(`Storefront API responded ${response.status}`);
410
+ const body = await response.json();
411
+ if (!body.data) throw new Error(`Storefront API error: ${JSON.stringify(body.errors)}`);
412
+ return body.data.nodes;
413
+ })
414
+ );
415
+ const byId = /* @__PURE__ */ new Map();
416
+ for (const node of results.flat()) {
417
+ if (node && typeof node === "object" && "title" in node) byId.set(node.id, toCard(node));
418
+ }
419
+ return ids.map((id) => byId.get(id)).filter((card) => card !== void 0);
420
+ }
421
+ shareControls(isShared) {
422
+ const store = getStore();
423
+ const actions = document.createElement("div");
424
+ actions.className = "bagsy-wishlist__actions";
425
+ const share = this.actionButton("bagsy-wishlist__share", this.text("share-label", "Share wishlist"));
426
+ const unshare = this.actionButton("bagsy-wishlist__unshare", this.text("unshare-label", "Stop sharing"));
427
+ const status = document.createElement("span");
428
+ status.className = "bagsy-wishlist__status";
429
+ status.setAttribute("role", "status");
430
+ unshare.hidden = !isShared;
431
+ share.addEventListener("click", async () => {
432
+ try {
433
+ const token = await shareList(store.transport, "default");
434
+ const url = new URL(location.href);
435
+ url.search = "";
436
+ url.hash = "";
437
+ url.searchParams.set(SHARE_PARAM, token);
438
+ unshare.hidden = false;
439
+ if (navigator.share) {
440
+ await navigator.share({ title: document.title, url: url.toString() }).catch(() => {
441
+ });
442
+ } else {
443
+ await navigator.clipboard.writeText(url.toString());
444
+ status.textContent = this.text("copied-label", "Link copied");
445
+ }
446
+ } catch (error) {
447
+ this.dispatchEvent(new CustomEvent("bagsy:error", { detail: { error }, bubbles: true }));
448
+ }
449
+ });
450
+ unshare.addEventListener("click", async () => {
451
+ try {
452
+ await unshareList(store.transport, "default");
453
+ unshare.hidden = true;
454
+ status.textContent = this.text("unshared-label", "Sharing stopped");
455
+ } catch (error) {
456
+ this.dispatchEvent(new CustomEvent("bagsy:error", { detail: { error }, bubbles: true }));
457
+ }
458
+ });
459
+ actions.append(share, unshare, status);
460
+ return actions;
461
+ }
462
+ actionButton(className, label) {
463
+ const button = document.createElement("button");
464
+ button.type = "button";
465
+ button.className = className;
466
+ button.textContent = label;
467
+ return button;
468
+ }
469
+ message(text) {
470
+ const message = document.createElement("p");
471
+ message.className = "bagsy-wishlist__empty";
472
+ message.textContent = text;
473
+ return message;
474
+ }
475
+ renderEmpty(text) {
476
+ this.replaceChildren(this.message(text));
477
+ }
478
+ grid(cards, label, labelSaved) {
479
+ const root = getStore().root;
480
+ const locale = document.documentElement.lang || void 0;
481
+ const list = document.createElement("ul");
482
+ list.className = "bagsy-wishlist__grid";
483
+ list.setAttribute("role", "list");
484
+ for (const card of cards) {
485
+ const item = document.createElement("li");
486
+ item.className = "bagsy-wishlist__item";
487
+ item.dataset.key = keyOf(card);
488
+ item.dataset.productId = card.productId;
489
+ if (card.image) {
490
+ const image = document.createElement("img");
491
+ image.className = "bagsy-wishlist__image";
492
+ const src = new URL(card.image.url);
493
+ src.searchParams.set("width", "600");
494
+ image.src = src.toString();
495
+ image.alt = card.image.altText ?? "";
496
+ image.loading = "lazy";
497
+ image.decoding = "async";
498
+ if (card.image.width && card.image.height) {
499
+ image.width = card.image.width;
500
+ image.height = card.image.height;
501
+ }
502
+ item.append(image);
503
+ }
504
+ const title = document.createElement("a");
505
+ title.className = "bagsy-wishlist__title";
506
+ const href = new URL(`${root}/products/${encodeURIComponent(card.handle)}`, location.origin);
507
+ if (card.variantId) href.searchParams.set("variant", card.variantId.split("/").pop());
508
+ title.href = href.pathname + href.search;
509
+ title.textContent = card.title;
510
+ item.append(title);
511
+ if (card.variantTitle) {
512
+ const variant = document.createElement("span");
513
+ variant.className = "bagsy-wishlist__variant";
514
+ variant.textContent = card.variantTitle;
515
+ item.append(variant);
516
+ }
517
+ const price = document.createElement("span");
518
+ price.className = "bagsy-wishlist__price";
519
+ price.textContent = formatPrice(card.price.amount, card.price.currencyCode, locale);
520
+ const button = document.createElement("bagsy-button");
521
+ button.setAttribute("product-id", card.productId);
522
+ if (card.variantId) button.setAttribute("variant-id", card.variantId);
523
+ button.setAttribute("icon-only", "");
524
+ button.setAttribute("label", label);
525
+ button.setAttribute("label-saved", labelSaved);
526
+ item.append(price, button);
527
+ if (!card.available) {
528
+ const soldOut = document.createElement("span");
529
+ soldOut.className = "bagsy-wishlist__sold-out";
530
+ soldOut.textContent = this.text("sold-out-label", "Sold out");
531
+ item.append(soldOut);
532
+ }
533
+ list.append(item);
534
+ }
535
+ return list;
536
+ }
537
+ };
538
+ function formatPrice(amount, currency, locale) {
539
+ try {
540
+ return new Intl.NumberFormat(locale, { style: "currency", currency }).format(Number(amount));
541
+ } catch {
542
+ return `${amount} ${currency}`;
543
+ }
544
+ }
545
+
546
+ // ../element/src/index.ts
547
+ function define() {
548
+ if (typeof customElements === "undefined") return;
549
+ for (const [name, element] of [
550
+ ["bagsy-button", BagsyButton],
551
+ ["bagsy-count", BagsyCount],
552
+ ["bagsy-wishlist", BagsyWishlist]
553
+ ]) {
554
+ if (!customElements.get(name)) customElements.define(name, element);
555
+ }
556
+ }
557
+ export {
558
+ BagsyButton,
559
+ BagsyCount,
560
+ BagsyStore,
561
+ BagsyWishlist,
562
+ define,
563
+ getStore,
564
+ setStore
565
+ };
566
+ //# sourceMappingURL=element.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../element/src/store.ts","../../element/src/button.ts","../../element/src/count.ts","../../element/src/wishlist.ts","../../element/src/index.ts"],"sourcesContent":["import {\n clearVisitorId,\n createTransport,\n getVisitorId,\n iterateItems,\n listLists,\n lookupSaved,\n mergeVisitor,\n peekVisitorId,\n productGid,\n removeProduct,\n toggleItem,\n variantGid,\n type SavedEntry,\n type Transport,\n} from '@bagsy/core';\n\nexport interface BagsyConfig {\n /** Shopify `routes.root_url`, e.g. `/` or `/en-gb`. */\n root?: string;\n /** Whether Shopify has a logged-in customer for this page. */\n customer?: boolean;\n /** Override the proxy base URL entirely (tests, custom setups). */\n baseUrl?: string;\n fetch?: typeof fetch;\n}\n\nexport interface ChangeDetail {\n productId: string;\n variantId?: string;\n saved: boolean;\n}\n\ntype Listener = () => void;\n\n/**\n * One per page: a single client, one batched `/saved` lookup for every button\n * on the page, optimistic toggles, and the guest → customer merge on login.\n */\nexport class BagsyStore extends EventTarget {\n /** Use with any `@umxr/bagsy` operation, e.g. `listLists(Bagsy.transport)`. */\n readonly transport: Transport;\n private saved = new Map<string, SavedEntry[]>();\n private pending = new Set<string>();\n private flush?: Promise<void>;\n private listeners = new Map<string, Set<Listener>>();\n /** Resolves once any pending guest merge has finished. */\n readonly ready: Promise<void>;\n count?: number;\n /** Storefront root without a trailing slash: '' or '/en-gb'. */\n readonly root: string;\n\n constructor(private config: BagsyConfig = {}) {\n super();\n const root = (this.root = (config.root ?? '/').replace(/\\/$/, ''));\n // Signed-in customers are identified by Shopify; the guest id only matters before login.\n const visitorId = config.customer ? undefined : getVisitorId();\n this.transport = createTransport({\n mode: 'proxy',\n proxyPath: `${root}/apps/bagsy`,\n ...(config.baseUrl && { baseUrl: config.baseUrl }),\n ...(visitorId && { visitorId }),\n ...(config.fetch && { fetch: config.fetch }),\n });\n this.ready = this.mergeGuestIfNeeded();\n }\n\n /** Fold the pre-login guest list into the customer's, once. */\n private async mergeGuestIfNeeded() {\n if (!this.config.customer) return;\n const guest = peekVisitorId();\n if (!guest) return;\n try {\n await mergeVisitor(this.transport, guest);\n clearVisitorId();\n } catch (error) {\n this.emit('bagsy:error', { error });\n }\n }\n\n /** Subscribe to a product's saved state; triggers a (batched) lookup. */\n watch(productId: string, listener: Listener): () => void {\n const id = productGid(productId);\n let set = this.listeners.get(id);\n if (!set) this.listeners.set(id, (set = new Set()));\n set.add(listener);\n if (!this.saved.has(id)) this.queue(id);\n else listener();\n return () => set!.delete(listener);\n }\n\n isSaved(productId: string, variantId?: string): boolean | undefined {\n const entries = this.saved.get(productGid(productId));\n if (!entries) return undefined;\n if (variantId === undefined) return entries.length > 0;\n // That variant, or the product saved without one (which means any variant).\n const gid = variantGid(variantId);\n return entries.some((entry) => !entry.variantId || entry.variantId === gid);\n }\n\n /** Optimistic: listeners see the new state immediately and revert on failure. */\n async toggle(productId: string, variantId?: string): Promise<boolean> {\n await this.ready;\n const id = productGid(productId);\n const previous = this.saved.get(id) ?? [];\n const wasSaved = this.isSaved(id, variantId) ?? false;\n const variant = variantId === undefined ? undefined : variantGid(variantId);\n // Saved for the whole product: unsaving from a variant button removes the product.\n const whole = wasSaved && variant && previous.some((entry) => !entry.variantId);\n\n this.saved.set(\n id,\n wasSaved\n ? previous.filter((entry) => (variant && !whole ? entry.variantId !== variant : false))\n : [...previous, { listId: 'default', itemId: 'pending', ...(variant && { variantId: variant }) }],\n );\n this.adjustCount(wasSaved ? -1 : 1);\n this.notify(id);\n\n try {\n const { saved } = whole\n ? (await removeProduct(this.transport, id), { saved: false })\n : await toggleItem(this.transport, id, variant ? { variantId: variant } : {});\n if (saved === wasSaved) {\n // Changed in another tab or list; take the server's word for it.\n this.queue(id);\n void this.loadCount().catch(() => {});\n }\n this.emit('bagsy:change', { productId: id, ...(variant && { variantId: variant }), saved });\n return saved;\n } catch (error) {\n this.saved.set(id, previous);\n this.adjustCount(wasSaved ? 1 : -1);\n this.notify(id);\n this.queue(id); // re-sync from the server\n this.emit('bagsy:error', { error, productId: id });\n throw error;\n }\n }\n\n /** Items in the default list, newest first. */\n async *items() {\n await this.ready;\n yield* iterateItems(this.transport, 'default');\n }\n\n private countRequest?: Promise<number>;\n\n /** Load the count once per page; later calls share the first request. */\n ensureCount(): Promise<number> {\n return (this.countRequest ??= this.loadCount());\n }\n\n /** Number of items in the default list, for header badges. */\n async loadCount(): Promise<number> {\n await this.ready;\n const lists = await listLists(this.transport);\n this.count = lists.find((list) => list.isDefault)?.itemCount ?? 0;\n this.emit('bagsy:count', { count: this.count });\n return this.count;\n }\n\n private adjustCount(delta: number) {\n if (this.count === undefined) return;\n this.count = Math.max(0, this.count + delta);\n this.emit('bagsy:count', { count: this.count });\n }\n\n private queue(id: string) {\n this.pending.add(id);\n // Every button that connects in the same task shares one request.\n this.flush ??= Promise.resolve().then(async () => {\n const ids = [...this.pending];\n this.pending.clear();\n this.flush = undefined;\n try {\n await this.ready;\n const lookup = await lookupSaved(this.transport, ids);\n for (const pid of ids) {\n this.saved.set(pid, lookup.get(pid));\n this.notify(pid);\n }\n } catch (error) {\n this.emit('bagsy:error', { error });\n }\n });\n }\n\n private notify(id: string) {\n for (const listener of this.listeners.get(id) ?? []) listener();\n }\n\n private emit(type: string, detail: unknown) {\n const event = new CustomEvent(type, { detail });\n this.dispatchEvent(event);\n // Also on document, so themes can listen without a reference to the store.\n globalThis.document?.dispatchEvent(new CustomEvent(type, { detail }));\n }\n}\n\nlet shared: BagsyStore | undefined;\n\n/** The page's store, configured from the app embed's JSON on first use. */\nexport function getStore(): BagsyStore {\n if (shared) return shared;\n const script = globalThis.document?.getElementById('bagsy-config');\n let config: BagsyConfig = {};\n try {\n config = script?.textContent ? JSON.parse(script.textContent) : {};\n } catch {\n // Malformed config: fall back to defaults.\n }\n return (shared = new BagsyStore(config));\n}\n\n/** Replace the page store (tests, or custom setups before elements connect). */\nexport function setStore(store: BagsyStore | undefined) {\n shared = store;\n}\n","import { getStore } from './store.js';\n\n// The product form's variant input, and the events themes fire when it changes.\nconst VARIANT_INPUT = 'form[action*=\"/cart/add\"] [name=id]';\nconst VARIANT_EVENTS = ['change', 'variant:change', 'variant:update'];\n\n// Presentation attributes give a usable default even without styles.css.\nconst HEART =\n '<svg part=\"icon\" viewBox=\"0 0 24 24\" width=\"18\" height=\"18\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" aria-hidden=\"true\"><path d=\"M12 20.5 10.6 19.2C5.6 14.7 2.5 11.9 2.5 8.4 2.5 5.6 4.7 3.5 7.4 3.5c1.6 0 3.2.8 4.1 2 .9-1.2 2.5-2 4.1-2 2.7 0 4.9 2.1 4.9 4.9 0 3.5-3.1 6.3-8.1 10.8z\"/></svg>';\n\n\n/**\n * `<bagsy-button product-id=\"123\">` — a save/unsave toggle.\n *\n * Attributes: `product-id` (required, numeric or GID), `variant-id` (save a\n * specific variant), `variant-source` (follow the selected variant in the\n * product form on the page), `label` / `label-saved` (text, also used as the\n * accessible name), `icon-only`. Reflects `saved` for styling.\n * Default look: `@umxr/bagsy/styles.css`. Style with `--bagsy-accent`,\n * `--bagsy-color`, `--bagsy-background`, `--bagsy-border`, `--bagsy-radius`,\n * `--bagsy-padding`, or `bagsy-button::part(button | icon | label)`.\n * Put your own markup inside to replace the default icon and label.\n */\nexport class BagsyButton extends HTMLElement {\n static observedAttributes = ['product-id', 'variant-id', 'label', 'label-saved'];\n\n private button: HTMLButtonElement;\n private label: HTMLSpanElement;\n private unwatch?: () => void;\n private busy = false;\n // Themes update the variant input after their own change handlers run.\n private onVariantChange = () => setTimeout(() => this.syncVariant());\n\n constructor() {\n super();\n const root = this.attachShadow({ mode: 'open' });\n root.innerHTML = `<button part=\"button\" type=\"button\" aria-pressed=\"false\" aria-busy=\"true\"><slot>${HEART}<span part=\"label\"></span></slot></button>`;\n this.button = root.querySelector('button')!;\n this.label = root.querySelector('[part=label]')!;\n this.button.addEventListener('click', (event) => this.onClick(event));\n }\n\n connectedCallback() {\n if (this.hasAttribute('variant-source')) {\n this.syncVariant();\n for (const type of VARIANT_EVENTS) document.addEventListener(type, this.onVariantChange);\n }\n this.subscribe();\n }\n\n disconnectedCallback() {\n for (const type of VARIANT_EVENTS) document.removeEventListener(type, this.onVariantChange);\n this.unwatch?.();\n this.unwatch = undefined;\n }\n\n /** Follow the selected variant in the product form, preferring this section's form. */\n private syncVariant() {\n const input = (this.closest('.shopify-section')?.querySelector(VARIANT_INPUT) ??\n document.querySelector(VARIANT_INPUT)) as { value: string } | null;\n if (input?.value && input.value !== this.variantId) this.setAttribute('variant-id', input.value);\n }\n\n attributeChangedCallback(name: string, previous: string | null, next: string | null) {\n if (previous === next) return;\n if ((name === 'product-id' || name === 'variant-id') && this.isConnected) this.subscribe();\n else this.render();\n }\n\n get productId() {\n return this.getAttribute('product-id') ?? '';\n }\n\n get variantId() {\n return this.getAttribute('variant-id') || undefined;\n }\n\n private subscribe() {\n this.unwatch?.();\n if (!this.productId) return;\n try {\n this.unwatch = getStore().watch(this.productId, () => this.render());\n } catch (error) {\n console.warn('<bagsy-button>:', error);\n }\n this.render();\n }\n\n private render() {\n const saved = this.productId ? getStore().isSaved(this.productId, this.variantId) : undefined;\n const text = saved ? (this.getAttribute('label-saved') ?? 'Saved') : (this.getAttribute('label') ?? 'Save');\n this.toggleAttribute('saved', saved === true);\n this.button.setAttribute('aria-pressed', String(saved === true));\n const busy = String(saved === undefined || this.busy);\n this.button.setAttribute('aria-busy', busy);\n this.setAttribute('aria-busy', busy);\n this.button.setAttribute('aria-label', text);\n this.label.textContent = text;\n }\n\n private async onClick(event: Event) {\n // Buttons often sit inside product-card links; don't navigate.\n event.preventDefault();\n event.stopPropagation();\n if (this.busy || !this.productId || getStore().isSaved(this.productId, this.variantId) === undefined) return;\n this.busy = true;\n this.render();\n try {\n await getStore().toggle(this.productId, this.variantId);\n } catch {\n // The store reverted the state and dispatched `bagsy:error`.\n } finally {\n this.busy = false;\n this.render();\n }\n }\n}\n","import { getStore } from './store.js';\n\n/**\n * `<bagsy-count>` — how many items are in the shopper's wishlist, e.g. for a\n * header link. Renders plain text so it inherits theme styles. Add\n * `hide-empty` to hide it at zero.\n */\nexport class BagsyCount extends HTMLElement {\n private onCount = () => this.render();\n\n connectedCallback() {\n const store = getStore();\n store.addEventListener('bagsy:count', this.onCount);\n if (store.count === undefined) store.ensureCount().catch(() => {});\n this.render();\n }\n\n disconnectedCallback() {\n getStore().removeEventListener('bagsy:count', this.onCount);\n }\n\n private render() {\n const count = getStore().count;\n this.textContent = count === undefined ? '' : String(count);\n this.toggleAttribute('hidden', this.hasAttribute('hide-empty') && !count);\n }\n}\n","import { getList, getSharedList, shareList, unshareList, type Item } from '@bagsy/core';\nimport type { ChangeDetail } from './store.js';\nimport { getStore } from './store.js';\n\nconst STOREFRONT_API_VERSION = '2026-10';\nconst MAX_ITEMS = 250;\nconst BATCH = 50; // keeps each tokenless query well under the 1,000 complexity limit\nexport const SHARE_PARAM = 'bagsy_share';\n\nexport const PRODUCTS_QUERY = `query BagsyWishlist($ids: [ID!]!, $country: CountryCode, $language: LanguageCode)\n@inContext(country: $country, language: $language) {\n nodes(ids: $ids) {\n ... on Product {\n id\n title\n handle\n availableForSale\n featuredImage { url altText width height }\n priceRange { minVariantPrice { amount currencyCode } }\n }\n ... on ProductVariant {\n id\n title\n availableForSale\n image { url altText width height }\n price { amount currencyCode }\n product { id title handle featuredImage { url altText width height } }\n }\n }\n}`;\n\ntype Image = { url: string; altText: string | null; width: number | null; height: number | null } | null;\ntype Money = { amount: string; currencyCode: string };\ntype ProductNode = {\n id: string;\n title: string;\n handle: string;\n availableForSale: boolean;\n featuredImage: Image;\n priceRange: { minVariantPrice: Money };\n};\ntype VariantNode = {\n id: string;\n title: string;\n availableForSale: boolean;\n image: Image;\n price: Money;\n product: { id: string; title: string; handle: string; featuredImage: Image };\n};\n\n/** One card: a saved product, or a saved variant of one. */\nexport interface Card {\n productId: string;\n variantId?: string;\n title: string;\n variantTitle?: string;\n handle: string;\n available: boolean;\n image: Image;\n price: Money;\n}\n\nconst toCard = (node: ProductNode | VariantNode): Card =>\n 'product' in node\n ? {\n productId: node.product.id,\n variantId: node.id,\n title: node.product.title,\n // Shopify names the only variant of a single-variant product \"Default Title\".\n variantTitle: node.title === 'Default Title' ? undefined : node.title,\n handle: node.product.handle,\n available: node.availableForSale,\n image: node.image ?? node.product.featuredImage,\n price: node.price,\n }\n : {\n productId: node.id,\n title: node.title,\n handle: node.handle,\n available: node.availableForSale,\n image: node.featuredImage,\n price: node.priceRange.minVariantPrice,\n };\n\nconst keyOf = (item: { productId: string; variantId?: string | null }) => item.variantId ?? item.productId;\n\n/**\n * `<bagsy-wishlist>`: the shopper's saved products as a grid of cards, or a\n * shared list when the URL has `?bagsy_share=<token>`.\n *\n * Product data comes live from the store's Storefront API (tokenless, same\n * origin), so titles, prices and availability match the shopper's market.\n * Attributes: `country`, `language`, `share` (show share controls),\n * `empty-text`, `remove-label`, `save-label`, `saved-label`, `sold-out-label`,\n * `share-label`, `unshare-label`, `copied-label`, `unshared-label`,\n * `shared-heading`, `shared-missing-text`. Renders light DOM with\n * `bagsy-wishlist__*` classes, styled by `@umxr/bagsy/styles.css`.\n */\nexport class BagsyWishlist extends HTMLElement {\n private shared = false;\n\n private onChange = (event: Event) => {\n const { productId, variantId, saved } = (event as CustomEvent<ChangeDetail>).detail;\n if (saved || this.shared) return;\n // Unsaving a product (no variant) removes every saved variant of it too, as the API does.\n for (const card of this.querySelectorAll<HTMLElement>('.bagsy-wishlist__item')) {\n if (variantId ? card.dataset.key === variantId : card.dataset.productId === productId) card.remove();\n }\n if (!this.querySelector('.bagsy-wishlist__item')) this.renderEmpty(this.getAttribute('empty-text') ?? 'Your wishlist is empty.');\n };\n\n private text = (name: string, fallback: string) => this.getAttribute(name) ?? fallback;\n\n connectedCallback() {\n getStore().addEventListener('bagsy:change', this.onChange);\n this.setAttribute('aria-busy', 'true');\n const token = new URLSearchParams(location.search).get(SHARE_PARAM);\n (token ? this.loadShared(token) : this.loadOwn())\n .catch((error) => {\n console.warn('<bagsy-wishlist>:', error);\n this.dispatchEvent(new CustomEvent('bagsy:error', { detail: { error }, bubbles: true }));\n })\n .finally(() => this.removeAttribute('aria-busy'));\n }\n\n disconnectedCallback() {\n getStore().removeEventListener('bagsy:change', this.onChange);\n }\n\n private async loadOwn() {\n const store = getStore();\n const items: Item[] = [];\n for await (const item of store.items()) {\n items.push(item);\n if (items.length >= MAX_ITEMS) break;\n }\n const cards = items.length ? await this.cards(items) : [];\n if (!cards.length) return this.renderEmpty(this.text('empty-text', 'Your wishlist is empty.'));\n\n const nodes: Node[] = [];\n if (this.hasAttribute('share')) {\n const list = await getList(store.transport, 'default');\n nodes.push(this.shareControls(Boolean(list.shareToken)));\n }\n nodes.push(this.grid(cards, this.text('save-label', 'Save'), this.text('remove-label', 'Remove from wishlist')));\n this.replaceChildren(...nodes);\n }\n\n private async loadShared(token: string) {\n this.shared = true;\n const t = getStore().transport;\n const items: Item[] = [];\n let name = '';\n let after: string | undefined;\n try {\n do {\n const page = await getSharedList(t, token, { limit: 100, after });\n name = page.list.name;\n items.push(...page.items.data);\n after = page.items.pageInfo.hasNextPage ? (page.items.pageInfo.endCursor ?? undefined) : undefined;\n } while (after && items.length < MAX_ITEMS);\n } catch (error) {\n // Checked by shape: in the theme, errors come from the main asset's copy of BagsyError.\n if ((error as { status?: number } | null)?.status === 404) {\n return this.renderEmpty(this.text('shared-missing-text', 'This shared wishlist is no longer available.'));\n }\n throw error;\n }\n\n // Stands in for the page block's own heading, which styles.css hides in this view.\n this.toggleAttribute('shared-view', true);\n const heading = document.createElement('h2');\n heading.className = 'bagsy-wishlist__shared';\n heading.textContent = `${this.text('shared-heading', 'Shared wishlist')}: ${name}`;\n const cards = items.length ? await this.cards(items) : [];\n // Visitors save items to their own wishlist from here.\n this.replaceChildren(\n heading,\n cards.length\n ? this.grid(cards, this.text('save-label', 'Save'), this.text('saved-label', 'Saved'))\n : this.message(this.text('empty-text', 'Your wishlist is empty.')),\n );\n }\n\n /** Saved items → live cards, in saved order. Deleted or unpublished products drop out. */\n async cards(items: { productId: string; variantId?: string | null }[]): Promise<Card[]> {\n const ids = [...new Set(items.map(keyOf))];\n const variables = {\n country: this.getAttribute('country') || undefined,\n language: this.getAttribute('language')?.toUpperCase().replace('-', '_') || undefined,\n };\n const batches: string[][] = [];\n for (let i = 0; i < ids.length; i += BATCH) batches.push(ids.slice(i, i + BATCH));\n\n const results = await Promise.all(\n batches.map(async (batch) => {\n const response = await fetch(`/api/${STOREFRONT_API_VERSION}/graphql.json`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Accept: 'application/json' },\n body: JSON.stringify({ query: PRODUCTS_QUERY, variables: { ...variables, ids: batch } }),\n });\n if (!response.ok) throw new Error(`Storefront API responded ${response.status}`);\n const body = (await response.json()) as { data?: { nodes: (ProductNode | VariantNode | null)[] }; errors?: unknown };\n if (!body.data) throw new Error(`Storefront API error: ${JSON.stringify(body.errors)}`);\n return body.data.nodes;\n }),\n );\n const byId = new Map<string, Card>();\n for (const node of results.flat()) {\n if (node && typeof node === 'object' && 'title' in node) byId.set(node.id, toCard(node));\n }\n return ids.map((id) => byId.get(id)).filter((card): card is Card => card !== undefined);\n }\n\n private shareControls(isShared: boolean) {\n const store = getStore();\n const actions = document.createElement('div');\n actions.className = 'bagsy-wishlist__actions';\n const share = this.actionButton('bagsy-wishlist__share', this.text('share-label', 'Share wishlist'));\n const unshare = this.actionButton('bagsy-wishlist__unshare', this.text('unshare-label', 'Stop sharing'));\n const status = document.createElement('span');\n status.className = 'bagsy-wishlist__status';\n status.setAttribute('role', 'status');\n unshare.hidden = !isShared;\n\n share.addEventListener('click', async () => {\n try {\n const token = await shareList(store.transport, 'default');\n const url = new URL(location.href);\n url.search = '';\n url.hash = '';\n url.searchParams.set(SHARE_PARAM, token);\n unshare.hidden = false;\n if (navigator.share) {\n await navigator.share({ title: document.title, url: url.toString() }).catch(() => {});\n } else {\n await navigator.clipboard.writeText(url.toString());\n status.textContent = this.text('copied-label', 'Link copied');\n }\n } catch (error) {\n this.dispatchEvent(new CustomEvent('bagsy:error', { detail: { error }, bubbles: true }));\n }\n });\n unshare.addEventListener('click', async () => {\n try {\n await unshareList(store.transport, 'default');\n unshare.hidden = true;\n status.textContent = this.text('unshared-label', 'Sharing stopped');\n } catch (error) {\n this.dispatchEvent(new CustomEvent('bagsy:error', { detail: { error }, bubbles: true }));\n }\n });\n\n actions.append(share, unshare, status);\n return actions;\n }\n\n private actionButton(className: string, label: string) {\n const button = document.createElement('button');\n button.type = 'button';\n button.className = className;\n button.textContent = label;\n return button;\n }\n\n private message(text: string) {\n const message = document.createElement('p');\n message.className = 'bagsy-wishlist__empty';\n message.textContent = text;\n return message;\n }\n\n private renderEmpty(text: string) {\n this.replaceChildren(this.message(text));\n }\n\n private grid(cards: Card[], label: string, labelSaved: string) {\n const root = getStore().root;\n const locale = document.documentElement.lang || undefined;\n const list = document.createElement('ul');\n list.className = 'bagsy-wishlist__grid';\n list.setAttribute('role', 'list');\n\n for (const card of cards) {\n const item = document.createElement('li');\n item.className = 'bagsy-wishlist__item';\n item.dataset.key = keyOf(card);\n item.dataset.productId = card.productId;\n\n if (card.image) {\n const image = document.createElement('img');\n image.className = 'bagsy-wishlist__image';\n const src = new URL(card.image.url);\n src.searchParams.set('width', '600');\n image.src = src.toString();\n image.alt = card.image.altText ?? '';\n image.loading = 'lazy';\n image.decoding = 'async';\n if (card.image.width && card.image.height) {\n image.width = card.image.width;\n image.height = card.image.height;\n }\n item.append(image);\n }\n\n const title = document.createElement('a');\n title.className = 'bagsy-wishlist__title';\n const href = new URL(`${root}/products/${encodeURIComponent(card.handle)}`, location.origin);\n if (card.variantId) href.searchParams.set('variant', card.variantId.split('/').pop()!);\n title.href = href.pathname + href.search;\n title.textContent = card.title;\n item.append(title);\n\n if (card.variantTitle) {\n const variant = document.createElement('span');\n variant.className = 'bagsy-wishlist__variant';\n variant.textContent = card.variantTitle;\n item.append(variant);\n }\n\n const price = document.createElement('span');\n price.className = 'bagsy-wishlist__price';\n price.textContent = formatPrice(card.price.amount, card.price.currencyCode, locale);\n\n const button = document.createElement('bagsy-button');\n button.setAttribute('product-id', card.productId);\n if (card.variantId) button.setAttribute('variant-id', card.variantId);\n button.setAttribute('icon-only', '');\n button.setAttribute('label', label);\n button.setAttribute('label-saved', labelSaved);\n\n item.append(price, button);\n if (!card.available) {\n const soldOut = document.createElement('span');\n soldOut.className = 'bagsy-wishlist__sold-out';\n soldOut.textContent = this.text('sold-out-label', 'Sold out');\n item.append(soldOut);\n }\n list.append(item);\n }\n return list;\n }\n}\n\nfunction formatPrice(amount: string, currency: string, locale?: string) {\n try {\n return new Intl.NumberFormat(locale, { style: 'currency', currency }).format(Number(amount));\n } catch {\n return `${amount} ${currency}`;\n }\n}\n","import { BagsyButton } from './button.js';\nimport { BagsyCount } from './count.js';\nimport { BagsyWishlist } from './wishlist.js';\n\nexport { BagsyButton, BagsyCount, BagsyWishlist };\nexport { BagsyStore, getStore, setStore, type BagsyConfig, type ChangeDetail } from './store.js';\n\n/** Register the elements. Safe to call more than once. */\nexport function define() {\n if (typeof customElements === 'undefined') return;\n for (const [name, element] of [\n ['bagsy-button', BagsyButton],\n ['bagsy-count', BagsyCount],\n ['bagsy-wishlist', BagsyWishlist],\n ] as const) {\n if (!customElements.get(name)) customElements.define(name, element);\n }\n}\n"],"mappings":"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","names":[]}