@asteby/storefront-react 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,1032 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+ var storefront = require('@asteby/storefront');
5
+ var jsxRuntime = require('react/jsx-runtime');
6
+
7
+ // src/context.tsx
8
+ var StorefrontContext = react.createContext(null);
9
+ function StorefrontProvider({ client, config, shop = null, locale, currency, children }) {
10
+ const configKey = config ? `${config.baseUrl}|${config.token}|${config.locale ?? ""}|${config.customerToken ?? ""}` : "";
11
+ const resolvedClient = react.useMemo(
12
+ () => client ?? (config ? storefront.createStorefrontClient(config) : null),
13
+ [client, configKey]
14
+ );
15
+ const value = react.useMemo(
16
+ () => ({
17
+ client: resolvedClient,
18
+ shop,
19
+ locale: locale ?? config?.locale ?? shop?.locale ?? "es-MX",
20
+ currency: currency ?? shop?.currency_code ?? "MXN"
21
+ }),
22
+ [resolvedClient, shop, locale, currency, config?.locale]
23
+ );
24
+ return /* @__PURE__ */ jsxRuntime.jsx(StorefrontContext.Provider, { value, children });
25
+ }
26
+ function useStorefront() {
27
+ const ctx = react.useContext(StorefrontContext);
28
+ if (!ctx) throw new Error("useStorefront must be used inside <StorefrontProvider>");
29
+ return ctx;
30
+ }
31
+ function useOptionalStorefront() {
32
+ return react.useContext(StorefrontContext);
33
+ }
34
+ function useStorefrontClient() {
35
+ const { client } = useStorefront();
36
+ if (!client) throw new Error("<StorefrontProvider> needs a `client` or `config` for browser-side calls");
37
+ return client;
38
+ }
39
+
40
+ // src/cookie.ts
41
+ var CART_COOKIE_NAME = "sf_cart";
42
+ function getCartIdFromCookie(cookieHeader, name = CART_COOKIE_NAME) {
43
+ if (!cookieHeader) return null;
44
+ for (const part of cookieHeader.split(";")) {
45
+ const eq = part.indexOf("=");
46
+ if (eq < 0) continue;
47
+ if (part.slice(0, eq).trim() !== name) continue;
48
+ const raw = part.slice(eq + 1).trim().replace(/^"(.*)"$/, "$1");
49
+ if (!raw) return null;
50
+ try {
51
+ return decodeURIComponent(raw);
52
+ } catch {
53
+ return raw;
54
+ }
55
+ }
56
+ return null;
57
+ }
58
+ function serializeCartCookie(cartId, options = {}) {
59
+ const name = options.name ?? CART_COOKIE_NAME;
60
+ const parts = [`${name}=${cartId ? encodeURIComponent(cartId) : ""}`];
61
+ parts.push(`Path=${options.path ?? "/"}`);
62
+ const maxAge = cartId ? Math.round((options.maxAgeDays ?? 30) * 86400) : 0;
63
+ parts.push(`Max-Age=${maxAge}`);
64
+ if (options.domain) parts.push(`Domain=${options.domain}`);
65
+ const sameSite = options.sameSite ?? "Lax";
66
+ parts.push(`SameSite=${sameSite}`);
67
+ if (options.secure ?? sameSite === "None") parts.push("Secure");
68
+ return parts.join("; ");
69
+ }
70
+ function writeCartCookie(cartId, options = {}) {
71
+ if (typeof document === "undefined") return;
72
+ const secure = options.secure ?? (typeof location !== "undefined" && location.protocol === "https:");
73
+ document.cookie = serializeCartCookie(cartId, { ...options, secure });
74
+ }
75
+ function readCartCookie(name = CART_COOKIE_NAME) {
76
+ if (typeof document === "undefined") return null;
77
+ return getCartIdFromCookie(document.cookie, name);
78
+ }
79
+ var CartContext = react.createContext(null);
80
+ var toCents = (m) => m ? Math.round(Number.parseFloat(m.amount) * 100) : 0;
81
+ var money = (cents, currency) => ({ amount: (cents / 100).toFixed(2), currency_code: currency });
82
+ function emptyCart(currency) {
83
+ const zero = money(0, currency);
84
+ return {
85
+ id: "",
86
+ lines: [],
87
+ total_quantity: 0,
88
+ discount_codes: [],
89
+ buyer: { email: null, phone: null, customer_id: null },
90
+ shipping_address: null,
91
+ delivery: { method: null, rate_id: null, branch_id: null },
92
+ cost: { subtotal: zero, discount: zero, shipping: null, tax: null, total: zero, tax_included: true },
93
+ updated_at: (/* @__PURE__ */ new Date(0)).toISOString()
94
+ };
95
+ }
96
+ function withLines(cart, lines, totalQuantity) {
97
+ const currency = cart.cost.subtotal.currency_code;
98
+ const subtotal = lines.reduce((sum, l) => sum + toCents(l.cost.total), 0);
99
+ const total = Math.max(0, subtotal - toCents(cart.cost.discount) + toCents(cart.cost.shipping));
100
+ return {
101
+ ...cart,
102
+ lines,
103
+ total_quantity: totalQuantity ?? lines.reduce((sum, l) => sum + l.quantity, 0),
104
+ cost: { ...cart.cost, subtotal: money(subtotal, currency), total: money(total, currency) }
105
+ };
106
+ }
107
+ function lineWithQuantity(line, quantity) {
108
+ const unit = toCents(line.cost.unit);
109
+ return { ...line, quantity, cost: { ...line.cost, total: money(unit * quantity, line.cost.unit.currency_code) } };
110
+ }
111
+ var optimisticCart = {
112
+ add(cart, input, currency) {
113
+ const base = cart ?? emptyCart(currency);
114
+ let lines = base.lines.slice();
115
+ let unknownQuantity = 0;
116
+ for (const l of input) {
117
+ const idx = lines.findIndex((x) => x.merchandise.id === l.variant_id);
118
+ if (idx >= 0) lines[idx] = lineWithQuantity(lines[idx], lines[idx].quantity + l.quantity);
119
+ else if (l.merchandise) {
120
+ const unit = l.merchandise.price;
121
+ lines.push({
122
+ id: `optimistic:${l.variant_id}`,
123
+ quantity: l.quantity,
124
+ merchandise: l.merchandise,
125
+ cost: { unit, compare_at_unit: l.merchandise.compare_at_price, total: money(toCents(unit) * l.quantity, unit.currency_code) }
126
+ });
127
+ } else unknownQuantity += l.quantity;
128
+ }
129
+ const known = lines.reduce((sum, l) => sum + l.quantity, 0);
130
+ return withLines(base, lines, known + unknownQuantity + (base.total_quantity - base.lines.reduce((s, l) => s + l.quantity, 0)));
131
+ },
132
+ update(cart, input) {
133
+ if (!cart) return cart;
134
+ const byId = new Map(input.map((l) => [l.id, l.quantity]));
135
+ const lines = cart.lines.map((l) => byId.has(l.id) ? lineWithQuantity(l, byId.get(l.id)) : l).filter((l) => l.quantity > 0);
136
+ return withLines(cart, lines);
137
+ },
138
+ remove(cart, lineIds) {
139
+ if (!cart) return cart;
140
+ return withLines(cart, cart.lines.filter((l) => !lineIds.includes(l.id)));
141
+ }
142
+ };
143
+ var isNotFound = (err) => storefront.isStorefrontError(err) && err.status === 404;
144
+ function CartProvider({
145
+ initialCart = null,
146
+ cartId: initialCartId = null,
147
+ client: clientProp,
148
+ cookie,
149
+ currency: currencyProp,
150
+ onError,
151
+ onCartChange,
152
+ children
153
+ }) {
154
+ const sf = useOptionalStorefront();
155
+ const client = clientProp ?? sf?.client ?? null;
156
+ const currency = currencyProp ?? initialCart?.cost.subtotal.currency_code ?? sf?.currency ?? "MXN";
157
+ const cookieOptions = cookie === false ? null : cookie ?? {};
158
+ const confirmedRef = react.useRef(initialCart);
159
+ const pendingRef = react.useRef([]);
160
+ const idRef = react.useRef(initialCart?.id ?? initialCartId ?? null);
161
+ const queueRef = react.useRef(Promise.resolve());
162
+ const seqRef = react.useRef(0);
163
+ const callbacks = react.useRef({ onError, onCartChange });
164
+ callbacks.current = { onError, onCartChange };
165
+ const [cart, setCart] = react.useState(initialCart);
166
+ const [cartId, setCartId] = react.useState(idRef.current);
167
+ const [pending, setPending] = react.useState(0);
168
+ const [loading, setLoading] = react.useState(false);
169
+ const [error, setError] = react.useState(null);
170
+ const render = react.useCallback(() => {
171
+ const next = pendingRef.current.reduce((acc, op) => op.apply(acc), confirmedRef.current);
172
+ setCart(next);
173
+ }, []);
174
+ const persistId = react.useCallback(
175
+ (id) => {
176
+ if (idRef.current === id) return;
177
+ idRef.current = id;
178
+ setCartId(id);
179
+ if (cookieOptions) writeCartCookie(id, cookieOptions);
180
+ },
181
+ // cookieOptions is a plain options bag; its identity is irrelevant
182
+ [cookie === false, cookieOptions?.name]
183
+ );
184
+ const confirm = react.useCallback(
185
+ (next) => {
186
+ confirmedRef.current = next;
187
+ if (next) persistId(next.id);
188
+ callbacks.current.onCartChange?.(next);
189
+ },
190
+ [persistId]
191
+ );
192
+ const requireClient = react.useCallback(() => {
193
+ if (!client) throw new Error("CartProvider needs a client: pass `client` or wrap it in <StorefrontProvider config|client>");
194
+ return client;
195
+ }, [client]);
196
+ const mutate = react.useCallback(
197
+ (remote, toCart, optimistic) => {
198
+ const op = optimistic ? { id: ++seqRef.current, apply: optimistic } : null;
199
+ if (op) {
200
+ pendingRef.current = [...pendingRef.current, op];
201
+ render();
202
+ }
203
+ setPending((n) => n + 1);
204
+ const run = async () => {
205
+ try {
206
+ const result = await remote(requireClient());
207
+ const next = toCart(result);
208
+ if (next) confirm(next);
209
+ setError(null);
210
+ return result;
211
+ } catch (err) {
212
+ const e = err instanceof Error ? err : new Error(String(err));
213
+ setError(e);
214
+ callbacks.current.onError?.(e);
215
+ return null;
216
+ } finally {
217
+ if (op) pendingRef.current = pendingRef.current.filter((p2) => p2 !== op);
218
+ render();
219
+ setPending((n) => n - 1);
220
+ }
221
+ };
222
+ const p = queueRef.current.then(run, run);
223
+ queueRef.current = p;
224
+ return p;
225
+ },
226
+ [confirm, render, requireClient]
227
+ );
228
+ const asCart = (c) => c;
229
+ react.useEffect(() => {
230
+ if (confirmedRef.current) {
231
+ if (cookieOptions && idRef.current) writeCartCookie(idRef.current, cookieOptions);
232
+ return;
233
+ }
234
+ const id = idRef.current ?? (cookieOptions ? readCartCookie(cookieOptions.name ?? CART_COOKIE_NAME) : null);
235
+ if (!id || !client) return;
236
+ idRef.current = id;
237
+ setCartId(id);
238
+ let cancelled = false;
239
+ setLoading(true);
240
+ client.carts.get(id).then((loaded) => {
241
+ if (cancelled) return;
242
+ if (loaded) confirm(loaded);
243
+ else persistId(null);
244
+ render();
245
+ }).catch((err) => {
246
+ if (cancelled) return;
247
+ const e = err instanceof Error ? err : new Error(String(err));
248
+ setError(e);
249
+ callbacks.current.onError?.(e);
250
+ }).finally(() => !cancelled && setLoading(false));
251
+ return () => {
252
+ cancelled = true;
253
+ };
254
+ }, []);
255
+ const value = react.useMemo(() => {
256
+ const withId = (fn) => async (c) => {
257
+ if (!idRef.current) throw new Error("No cart yet: add a line first");
258
+ return fn(c, idRef.current);
259
+ };
260
+ return {
261
+ cart,
262
+ cartId,
263
+ status: loading ? "loading" : pending > 0 ? "updating" : "idle",
264
+ error,
265
+ totalQuantity: cart?.total_quantity ?? 0,
266
+ addLines: (lines) => {
267
+ const payload = lines.map(({ variant_id, quantity }) => ({ variant_id, quantity }));
268
+ return mutate(
269
+ async (c) => {
270
+ if (!idRef.current) return c.carts.create({ lines: payload });
271
+ try {
272
+ return await c.carts.addLines(idRef.current, payload);
273
+ } catch (err) {
274
+ if (isNotFound(err)) return c.carts.create({ lines: payload });
275
+ throw err;
276
+ }
277
+ },
278
+ asCart,
279
+ (current) => optimisticCart.add(current, lines, currency)
280
+ );
281
+ },
282
+ updateLines: (lines) => mutate(withId((c, id) => c.carts.updateLines(id, lines)), asCart, (current) => optimisticCart.update(current, lines)),
283
+ removeLines: (lineIds) => mutate(withId((c, id) => c.carts.removeLines(id, lineIds)), asCart, (current) => optimisticCart.remove(current, lineIds)),
284
+ setDiscountCodes: (codes) => mutate(withId((c, id) => c.carts.setDiscountCodes(id, codes)), asCart),
285
+ setBuyer: (buyer) => mutate(withId((c, id) => c.carts.setBuyer(id, buyer)), asCart),
286
+ setShippingAddress: (address) => mutate(withId((c, id) => c.carts.setShippingAddress(id, address)), asCart),
287
+ setDelivery: (delivery) => mutate(withId((c, id) => c.carts.setDelivery(id, delivery)), asCart),
288
+ deliveryOptions: () => mutate(withId((c, id) => c.carts.deliveryOptions(id)), () => null),
289
+ refresh: () => mutate(
290
+ async (c) => idRef.current ? c.carts.get(idRef.current) : null,
291
+ (r) => {
292
+ if (r === null) {
293
+ confirmedRef.current = null;
294
+ persistId(null);
295
+ }
296
+ return r;
297
+ }
298
+ ),
299
+ clear: () => {
300
+ confirmedRef.current = null;
301
+ pendingRef.current = [];
302
+ persistId(null);
303
+ render();
304
+ callbacks.current.onCartChange?.(null);
305
+ }
306
+ };
307
+ }, [cart, cartId, loading, pending, error, mutate, currency, persistId, render]);
308
+ return /* @__PURE__ */ jsxRuntime.jsx(CartContext.Provider, { value, children });
309
+ }
310
+ function useCart() {
311
+ const ctx = react.useContext(CartContext);
312
+ if (!ctx) throw new Error("useCart must be used inside <CartProvider>");
313
+ return ctx;
314
+ }
315
+ function useOptionalCart() {
316
+ return react.useContext(CartContext);
317
+ }
318
+ function Money({
319
+ data,
320
+ as,
321
+ locale,
322
+ withoutTrailingZeros,
323
+ withoutCurrency,
324
+ currencyDisplay,
325
+ ...rest
326
+ }) {
327
+ const sf = useOptionalStorefront();
328
+ const text = storefront.formatMoney(data, locale ?? sf?.locale, {
329
+ ...withoutTrailingZeros !== void 0 ? { withoutTrailingZeros } : {},
330
+ ...withoutCurrency !== void 0 ? { withoutCurrency } : {},
331
+ ...currencyDisplay !== void 0 ? { currencyDisplay } : {}
332
+ });
333
+ return react.createElement(as ?? "span", rest, text);
334
+ }
335
+ var DEFAULT_IMAGE_WIDTHS = [320, 480, 640, 768, 1024, 1280, 1600];
336
+ var defaultImageLoader = (src, width, height) => {
337
+ try {
338
+ const isAbsolute = /^[a-z][a-z0-9+.-]*:/i.test(src);
339
+ const url = new URL(src, "http://relative.invalid");
340
+ url.searchParams.set("width", String(width));
341
+ if (height) url.searchParams.set("height", String(height));
342
+ return isAbsolute ? url.toString() : url.pathname + url.search + url.hash;
343
+ } catch {
344
+ return src;
345
+ }
346
+ };
347
+ function parseRatio(ratio) {
348
+ if (!ratio) return null;
349
+ const [w, h] = ratio.split(/[/:]/).map((n) => Number.parseFloat(n));
350
+ return w && h ? w / h : null;
351
+ }
352
+ function Image({
353
+ data,
354
+ sizes = "100vw",
355
+ widths = DEFAULT_IMAGE_WIDTHS,
356
+ loader = defaultImageLoader,
357
+ priority = false,
358
+ aspectRatio,
359
+ alt,
360
+ loading,
361
+ decoding,
362
+ ...rest
363
+ }) {
364
+ const ratio = parseRatio(aspectRatio) ?? (data.width && data.height ? data.width / data.height : null);
365
+ let candidates = [...widths].sort((a, b) => a - b);
366
+ if (data.width) {
367
+ const capped = candidates.filter((w) => w <= data.width);
368
+ candidates = capped.length ? capped : [data.width];
369
+ }
370
+ const srcSet = loader ? candidates.map((w) => `${loader(data.url, w, ratio ? Math.round(w / ratio) : void 0)} ${w}w`).join(", ") : void 0;
371
+ const width = data.width ?? candidates[candidates.length - 1];
372
+ const height = data.height ?? (ratio && width ? Math.round(width / ratio) : void 0);
373
+ return /* @__PURE__ */ jsxRuntime.jsx(
374
+ "img",
375
+ {
376
+ src: data.url,
377
+ alt: alt ?? data.alt ?? "",
378
+ ...srcSet ? { srcSet, sizes } : {},
379
+ ...width ? { width } : {},
380
+ ...height ? { height } : {},
381
+ loading: loading ?? (priority ? "eager" : "lazy"),
382
+ decoding: decoding ?? (priority ? "auto" : "async"),
383
+ ...priority ? { fetchPriority: "high" } : {},
384
+ ...rest
385
+ }
386
+ );
387
+ }
388
+ var VISUALLY_HIDDEN = {
389
+ position: "absolute",
390
+ width: 1,
391
+ height: 1,
392
+ padding: 0,
393
+ margin: -1,
394
+ overflow: "hidden",
395
+ clip: "rect(0, 0, 0, 0)",
396
+ whiteSpace: "nowrap",
397
+ border: 0
398
+ };
399
+ function ProductPrice({
400
+ product,
401
+ variant,
402
+ fromLabel = "Desde",
403
+ compareAtLabel = "Precio anterior",
404
+ locale,
405
+ withoutTrailingZeros,
406
+ ...rest
407
+ }) {
408
+ let price = null;
409
+ let compareAt = null;
410
+ let isRange = false;
411
+ if (variant) {
412
+ price = variant.price;
413
+ compareAt = variant.compare_at_price;
414
+ } else if (product) {
415
+ price = product.price_range.min;
416
+ compareAt = product.compare_at_price_range?.min ?? null;
417
+ isRange = product.price_range.min.amount !== product.price_range.max.amount;
418
+ }
419
+ if (!price) return null;
420
+ const onSale = compareAt !== null && storefront.parseMoney(compareAt) > storefront.parseMoney(price);
421
+ const opts = { ...locale ? { locale } : {}, ...withoutTrailingZeros !== void 0 ? { withoutTrailingZeros } : {} };
422
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-on-sale": onSale || void 0, ...rest, children: [
423
+ isRange ? /* @__PURE__ */ jsxRuntime.jsxs("span", { "data-price-prefix": "", children: [
424
+ fromLabel,
425
+ " "
426
+ ] }) : null,
427
+ /* @__PURE__ */ jsxRuntime.jsx(Money, { data: price, "data-price": "", ...opts }),
428
+ onSale && compareAt ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
429
+ " ",
430
+ /* @__PURE__ */ jsxRuntime.jsxs("s", { "data-compare-at-price": "", children: [
431
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { style: VISUALLY_HIDDEN, children: [
432
+ compareAtLabel,
433
+ ": "
434
+ ] }),
435
+ /* @__PURE__ */ jsxRuntime.jsx(Money, { data: compareAt, ...opts })
436
+ ] })
437
+ ] }) : null
438
+ ] });
439
+ }
440
+ function toParams(input) {
441
+ if (!input) return new URLSearchParams();
442
+ return input instanceof URLSearchParams ? new URLSearchParams(input) : new URLSearchParams(input);
443
+ }
444
+ function optionsOf(variant) {
445
+ return Object.fromEntries(variant.selected_options.map((o) => [o.name, o.value]));
446
+ }
447
+ function findVariant(product, selection) {
448
+ if (product.options.length === 0) return product.variants[0] ?? null;
449
+ return product.variants.find((v) => v.selected_options.length > 0 && v.selected_options.every((o) => selection[o.name] === o.value)) ?? null;
450
+ }
451
+ function initialSelection(product, params, defaultToFirstAvailable) {
452
+ const byId = params.get("variant");
453
+ const fromId = byId ? product.variants.find((v) => v.id === byId) : void 0;
454
+ if (fromId) return optionsOf(fromId);
455
+ const selection = {};
456
+ for (const opt of product.options) {
457
+ const v = params.get(opt.name);
458
+ if (v !== null && opt.values.includes(v)) selection[opt.name] = v;
459
+ }
460
+ if (Object.keys(selection).length === 0 && defaultToFirstAvailable) {
461
+ const first = product.variants.find((v) => v.available_for_sale) ?? product.variants[0];
462
+ return first ? optionsOf(first) : {};
463
+ }
464
+ return selection;
465
+ }
466
+ function useProductOptions(product, config = {}) {
467
+ const { defaultToFirstAvailable = true, onChange } = config;
468
+ const params = react.useMemo(() => toParams(config.searchParams), [String(config.searchParams ?? "")]);
469
+ const sourceKey = `${product.id ?? ""}|${params.toString()}`;
470
+ const fromSource = react.useMemo(
471
+ () => initialSelection(product, params, defaultToFirstAvailable),
472
+ // product identity is keyed by id + params
473
+ [sourceKey, defaultToFirstAvailable]
474
+ );
475
+ const [local, setLocal] = react.useState(null);
476
+ const selectedOptions = local && local.key === sourceKey ? local.selection : fromSource;
477
+ const onChangeRef = react.useRef(onChange);
478
+ onChangeRef.current = onChange;
479
+ const setOption = react.useCallback(
480
+ (name, value) => {
481
+ const next = { ...selectedOptions, [name]: value };
482
+ setLocal({ key: sourceKey, selection: next });
483
+ onChangeRef.current?.(findVariant(product, next), next);
484
+ },
485
+ [selectedOptions, sourceKey, product]
486
+ );
487
+ const selectedVariant = react.useMemo(() => findVariant(product, selectedOptions), [product, selectedOptions]);
488
+ const options = react.useMemo(
489
+ () => product.options.map((opt) => ({
490
+ name: opt.name,
491
+ values: opt.values.map((value) => {
492
+ const candidate = { ...selectedOptions, [opt.name]: value };
493
+ const exact = findVariant(product, candidate);
494
+ const loose = product.variants.filter((v) => v.selected_options.some((o) => o.name === opt.name && o.value === value));
495
+ const search = new URLSearchParams(params);
496
+ search.delete("variant");
497
+ for (const [k, v] of Object.entries(candidate)) search.set(k, v);
498
+ return {
499
+ value,
500
+ selected: selectedOptions[opt.name] === value,
501
+ exists: exact ? true : loose.length > 0 && Object.keys(selectedOptions).length < product.options.length,
502
+ available: exact ? exact.available_for_sale : Object.keys(selectedOptions).length < product.options.length && loose.some((v) => v.available_for_sale),
503
+ variant: exact,
504
+ search: `?${search.toString()}`
505
+ };
506
+ })
507
+ })),
508
+ [product, selectedOptions, params]
509
+ );
510
+ return { options, selectedOptions, selectedVariant, setOption };
511
+ }
512
+ function VariantSelector({ product, children, ...config }) {
513
+ const state = useProductOptions(product, config);
514
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: state.options.map((option) => /* @__PURE__ */ jsxRuntime.jsx(react.Fragment, { children: children({ option, selectedVariant: state.selectedVariant, selectedOptions: state.selectedOptions, setOption: state.setOption }) }, option.name)) });
515
+ }
516
+ function toMerchandise(product, variant) {
517
+ return { ...variant, product: { handle: product.handle, title: product.title, featured_image: product.featured_image } };
518
+ }
519
+ function AddToCartButton({
520
+ variantId,
521
+ quantity = 1,
522
+ merchandise,
523
+ onAdded,
524
+ onClick,
525
+ disabled,
526
+ children = "Agregar al carrito",
527
+ type = "button",
528
+ ...rest
529
+ }) {
530
+ const cart = useCart();
531
+ const [adding, setAdding] = react.useState(false);
532
+ return /* @__PURE__ */ jsxRuntime.jsx(
533
+ "button",
534
+ {
535
+ type,
536
+ disabled: disabled || !variantId || adding,
537
+ "aria-busy": adding || void 0,
538
+ "data-adding": adding || void 0,
539
+ onClick: async (event) => {
540
+ onClick?.(event);
541
+ if (event.defaultPrevented || !variantId) return;
542
+ setAdding(true);
543
+ try {
544
+ const result = await cart.addLines([{ variant_id: variantId, quantity, ...merchandise ? { merchandise } : {} }]);
545
+ if (result) onAdded?.();
546
+ } finally {
547
+ setAdding(false);
548
+ }
549
+ },
550
+ ...rest,
551
+ children
552
+ }
553
+ );
554
+ }
555
+
556
+ // src/seo.ts
557
+ var clean = (s, max) => {
558
+ if (!s) return void 0;
559
+ const text = s.replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim();
560
+ if (!text) return void 0;
561
+ return max && text.length > max ? text.slice(0, max - 1).trimEnd() + "\u2026" : text;
562
+ };
563
+ function seoMeta(input) {
564
+ const out = [];
565
+ let title = clean(input.title);
566
+ if (title && input.titleSuffix && !title.includes(input.titleSuffix)) title = `${title} | ${input.titleSuffix}`;
567
+ const description = clean(input.description, 160);
568
+ const image = typeof input.image === "string" ? { url: input.image, alt: null } : input.image ?? null;
569
+ if (title) out.push({ title }, { property: "og:title", content: title }, { name: "twitter:title", content: title });
570
+ if (description) {
571
+ out.push({ name: "description", content: description }, { property: "og:description", content: description }, { name: "twitter:description", content: description });
572
+ }
573
+ if (input.url) out.push({ tagName: "link", rel: "canonical", href: input.url }, { property: "og:url", content: input.url });
574
+ out.push({ property: "og:type", content: input.type ?? "website" });
575
+ if (input.siteName) out.push({ property: "og:site_name", content: input.siteName });
576
+ if (input.locale) out.push({ property: "og:locale", content: input.locale.replace("-", "_") });
577
+ if (image) {
578
+ out.push({ property: "og:image", content: image.url });
579
+ if (image.width) out.push({ property: "og:image:width", content: String(image.width) });
580
+ if (image.height) out.push({ property: "og:image:height", content: String(image.height) });
581
+ if (image.alt) out.push({ property: "og:image:alt", content: image.alt });
582
+ }
583
+ out.push({ name: "twitter:card", content: image ? "summary_large_image" : "summary" });
584
+ if (input.noindex) out.push({ name: "robots", content: "noindex, nofollow" });
585
+ return out;
586
+ }
587
+ function seoOf(seo, fallbackTitle, fallbackDescription) {
588
+ return { title: seo.title ?? fallbackTitle, description: seo.description ?? fallbackDescription ?? null };
589
+ }
590
+ function productMeta(product, shop, url) {
591
+ const { title, description } = seoOf(product.seo, product.title, product.description);
592
+ const meta = seoMeta({
593
+ title,
594
+ description,
595
+ url,
596
+ image: product.featured_image,
597
+ type: "product",
598
+ siteName: shop?.name ?? null,
599
+ locale: shop?.locale ?? null,
600
+ titleSuffix: shop?.name ?? null
601
+ });
602
+ meta.push(
603
+ { property: "product:price:amount", content: product.price_range.min.amount },
604
+ { property: "product:price:currency", content: product.price_range.min.currency_code },
605
+ { property: "product:availability", content: product.available_for_sale ? "in stock" : "out of stock" }
606
+ );
607
+ if (product.vendor) meta.push({ property: "product:brand", content: product.vendor });
608
+ return meta;
609
+ }
610
+ function collectionMeta(collection, shop, url) {
611
+ const { title, description } = seoOf(collection.seo, collection.title, collection.description);
612
+ return seoMeta({ title, description, url, image: collection.image, siteName: shop?.name ?? null, locale: shop?.locale ?? null, titleSuffix: shop?.name ?? null });
613
+ }
614
+ var CONTEXT = "https://schema.org";
615
+ function shopUrl(shop, fallback) {
616
+ if (shop.domain) return /^https?:\/\//.test(shop.domain) ? shop.domain : `https://${shop.domain}`;
617
+ if (fallback) {
618
+ try {
619
+ return new URL(fallback).origin;
620
+ } catch {
621
+ return void 0;
622
+ }
623
+ }
624
+ return void 0;
625
+ }
626
+ function compact(obj) {
627
+ for (const k of Object.keys(obj)) {
628
+ const v = obj[k];
629
+ if (v === void 0 || v === null || v === "" || Array.isArray(v) && v.length === 0) delete obj[k];
630
+ }
631
+ return obj;
632
+ }
633
+ function withVariant(url, variantId) {
634
+ try {
635
+ const u = new URL(url);
636
+ u.searchParams.set("variant", variantId);
637
+ return u.toString();
638
+ } catch {
639
+ return `${url}${url.includes("?") ? "&" : "?"}variant=${encodeURIComponent(variantId)}`;
640
+ }
641
+ }
642
+ function productJsonLd(product, shop, url) {
643
+ const images = product.images.length ? product.images.map((i) => i.url) : product.featured_image ? [product.featured_image.url] : [];
644
+ const offers = product.variants.map(
645
+ (v) => compact({
646
+ "@type": "Offer",
647
+ sku: v.sku,
648
+ gtin: v.barcode ?? void 0,
649
+ name: product.variants.length > 1 ? `${product.title} - ${v.title}` : void 0,
650
+ price: v.price.amount,
651
+ priceCurrency: v.price.currency_code,
652
+ availability: v.available_for_sale ? "https://schema.org/InStock" : "https://schema.org/OutOfStock",
653
+ itemCondition: "https://schema.org/NewCondition",
654
+ url: product.variants.length > 1 ? withVariant(url, v.id) : url,
655
+ seller: shop ? { "@type": "Organization", name: shop.name } : void 0
656
+ })
657
+ );
658
+ const additionalProperty = Object.entries(product.attributes).map(([name, value]) => ({ "@type": "PropertyValue", name, value }));
659
+ return compact({
660
+ "@context": CONTEXT,
661
+ "@type": "Product",
662
+ "@id": `${url}#product`,
663
+ name: product.title,
664
+ description: clean(product.description) ?? void 0,
665
+ url,
666
+ image: images,
667
+ sku: product.variants.length === 1 ? product.variants[0].sku : void 0,
668
+ brand: product.vendor ? { "@type": "Brand", name: product.vendor } : void 0,
669
+ category: product.category?.title ?? product.product_type ?? void 0,
670
+ additionalProperty,
671
+ offers: offers.length === 1 ? offers[0] : offers
672
+ });
673
+ }
674
+ function organizationJsonLd(shop, url) {
675
+ const site = shopUrl(shop, url);
676
+ const contactPoint = shop.contact.email || shop.contact.phone ? [compact({ "@type": "ContactPoint", contactType: "customer service", email: shop.contact.email ?? void 0, telephone: shop.contact.phone ?? void 0 })] : [];
677
+ return compact({
678
+ "@context": CONTEXT,
679
+ "@type": "Organization",
680
+ "@id": site ? `${site}#organization` : void 0,
681
+ name: shop.name,
682
+ description: shop.description ?? void 0,
683
+ url: site,
684
+ logo: shop.logo?.url,
685
+ email: shop.contact.email ?? void 0,
686
+ telephone: shop.contact.phone ?? void 0,
687
+ contactPoint
688
+ });
689
+ }
690
+ function localBusinessJsonLd(shop, options = {}) {
691
+ const site = shopUrl(shop, options.url);
692
+ return shop.branches.map(
693
+ (b) => compact({
694
+ "@context": CONTEXT,
695
+ "@type": options.type ?? "Store",
696
+ "@id": site ? `${site}#branch-${b.id}` : void 0,
697
+ name: `${shop.name} - ${b.name}`,
698
+ url: site,
699
+ image: shop.logo?.url,
700
+ telephone: b.phone ?? shop.contact.phone ?? void 0,
701
+ priceRange: options.priceRange,
702
+ address: compact({ "@type": "PostalAddress", streetAddress: b.address, addressLocality: b.city, addressCountry: shop.country_code }),
703
+ geo: b.lat !== null && b.lng !== null ? { "@type": "GeoCoordinates", latitude: b.lat, longitude: b.lng } : void 0,
704
+ openingHours: b.hours ? b.hours.split(/\s*,\s*/).filter(Boolean) : void 0,
705
+ parentOrganization: site ? { "@id": `${site}#organization` } : { "@type": "Organization", name: shop.name }
706
+ })
707
+ );
708
+ }
709
+ function breadcrumbJsonLd(items) {
710
+ return {
711
+ "@context": CONTEXT,
712
+ "@type": "BreadcrumbList",
713
+ itemListElement: items.map((item, i) => ({ "@type": "ListItem", position: i + 1, name: item.name, item: item.url }))
714
+ };
715
+ }
716
+ function faqJsonLd(items) {
717
+ return {
718
+ "@context": CONTEXT,
719
+ "@type": "FAQPage",
720
+ mainEntity: items.map((item) => ({
721
+ "@type": "Question",
722
+ name: item.question,
723
+ acceptedAnswer: { "@type": "Answer", text: item.answer }
724
+ }))
725
+ };
726
+ }
727
+ function collectionJsonLd(collection, products, url, options = {}) {
728
+ const origin = (() => {
729
+ try {
730
+ return new URL(url).origin;
731
+ } catch {
732
+ return "";
733
+ }
734
+ })();
735
+ const productUrl = options.productUrl ?? ((handle) => `${origin}/products/${handle}`);
736
+ return compact({
737
+ "@context": CONTEXT,
738
+ "@type": "CollectionPage",
739
+ name: collection.seo.title ?? collection.title,
740
+ description: clean(collection.seo.description ?? collection.description) ?? void 0,
741
+ url,
742
+ image: collection.image?.url,
743
+ mainEntity: {
744
+ "@type": "ItemList",
745
+ numberOfItems: products.length,
746
+ itemListElement: products.map(
747
+ (p, i) => compact({ "@type": "ListItem", position: i + 1, url: productUrl(p.handle), name: p.title, image: p.featured_image?.url })
748
+ )
749
+ }
750
+ });
751
+ }
752
+ function serializeJsonLd(data) {
753
+ return JSON.stringify(data).replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
754
+ }
755
+ function JsonLd({ data, nonce }) {
756
+ if (!data || Array.isArray(data) && data.length === 0) return null;
757
+ return /* @__PURE__ */ jsxRuntime.jsx("script", { type: "application/ld+json", nonce, dangerouslySetInnerHTML: { __html: serializeJsonLd(data) } });
758
+ }
759
+ function defineSection(definition) {
760
+ return definition;
761
+ }
762
+ var STRING_KINDS = /* @__PURE__ */ new Set(["text", "textarea", "richtext", "url", "image", "color", "collection", "product"]);
763
+ function cloneDefault(value) {
764
+ return value !== null && typeof value === "object" ? structuredClone(value) : value;
765
+ }
766
+ function resolveSectionProps(schema, raw, path = "") {
767
+ const props = {};
768
+ const errors = [];
769
+ const input = raw ?? {};
770
+ for (const [name, spec] of Object.entries(schema)) {
771
+ const key = path ? `${path}.${name}` : name;
772
+ let value = input[name];
773
+ const fail = (message) => {
774
+ errors.push({ prop: key, message });
775
+ if ("default" in spec) props[name] = cloneDefault(spec.default);
776
+ };
777
+ if (value === void 0 || value === null || value === "") {
778
+ if ("default" in spec) props[name] = cloneDefault(spec.default);
779
+ else if (spec.required) errors.push({ prop: key, message: "is required" });
780
+ continue;
781
+ }
782
+ if (STRING_KINDS.has(spec.kind)) {
783
+ if (typeof value !== "string") {
784
+ fail("must be a string");
785
+ continue;
786
+ }
787
+ } else if (spec.kind === "number") {
788
+ if (typeof value === "string" && value.trim() !== "" && Number.isFinite(Number(value))) value = Number(value);
789
+ if (typeof value !== "number" || !Number.isFinite(value)) {
790
+ fail("must be a number");
791
+ continue;
792
+ }
793
+ if (spec.min !== void 0 && value < spec.min || spec.max !== void 0 && value > spec.max) {
794
+ fail(`must be between ${spec.min ?? "-\u221E"} and ${spec.max ?? "\u221E"}`);
795
+ continue;
796
+ }
797
+ } else if (spec.kind === "boolean") {
798
+ if (typeof value !== "boolean") {
799
+ fail("must be a boolean");
800
+ continue;
801
+ }
802
+ } else if (spec.kind === "select") {
803
+ if (typeof value !== "string" || spec.options && !spec.options.some((o) => o.value === value)) {
804
+ fail(`must be one of ${(spec.options ?? []).map((o) => o.value).join(", ")}`);
805
+ continue;
806
+ }
807
+ } else if (spec.kind === "products") {
808
+ if (!Array.isArray(value) || value.some((v) => typeof v !== "string")) {
809
+ fail("must be a list of product handles");
810
+ continue;
811
+ }
812
+ } else if (spec.kind === "list") {
813
+ if (!Array.isArray(value)) {
814
+ fail("must be a list");
815
+ continue;
816
+ }
817
+ const items = [];
818
+ value.forEach((item, i) => {
819
+ if (!spec.items) {
820
+ items.push(item);
821
+ return;
822
+ }
823
+ if (typeof item !== "object" || item === null || Array.isArray(item)) {
824
+ errors.push({ prop: `${key}[${i}]`, message: "must be an object" });
825
+ return;
826
+ }
827
+ const resolved = resolveSectionProps(spec.items, item, `${key}[${i}]`);
828
+ errors.push(...resolved.errors);
829
+ items.push(resolved.props);
830
+ });
831
+ value = items;
832
+ }
833
+ props[name] = value;
834
+ }
835
+ return { props, errors };
836
+ }
837
+ function sectionJsonSchema(definition) {
838
+ const toSchema = (schema) => {
839
+ const properties = {};
840
+ const required = [];
841
+ for (const [name, spec] of Object.entries(schema)) {
842
+ const base = { title: spec.label, "x-kind": spec.kind };
843
+ if (spec.description) base.description = spec.description;
844
+ if ("default" in spec) base.default = spec.default;
845
+ if (spec.required) required.push(name);
846
+ switch (spec.kind) {
847
+ case "number":
848
+ Object.assign(base, { type: "number" }, spec.min !== void 0 ? { minimum: spec.min } : {}, spec.max !== void 0 ? { maximum: spec.max } : {});
849
+ break;
850
+ case "boolean":
851
+ base.type = "boolean";
852
+ break;
853
+ case "select":
854
+ Object.assign(base, { type: "string", enum: (spec.options ?? []).map((o) => o.value), "x-options": spec.options ?? [] });
855
+ break;
856
+ case "products":
857
+ Object.assign(base, { type: "array", items: { type: "string" } });
858
+ break;
859
+ case "list":
860
+ Object.assign(base, { type: "array", items: spec.items ? toSchema(spec.items) : { type: "object" } });
861
+ break;
862
+ case "url":
863
+ Object.assign(base, { type: "string", format: "uri-reference" });
864
+ break;
865
+ default:
866
+ base.type = "string";
867
+ }
868
+ properties[name] = base;
869
+ }
870
+ return { type: "object", additionalProperties: false, properties, ...required.length ? { required } : {} };
871
+ };
872
+ return {
873
+ $schema: "https://json-schema.org/draft/2020-12/schema",
874
+ $id: `section:${definition.type}`,
875
+ title: definition.label ?? definition.type,
876
+ ...definition.description ? { description: definition.description } : {},
877
+ ...toSchema(definition.schema)
878
+ };
879
+ }
880
+ var SectionRegistry = class {
881
+ defs = /* @__PURE__ */ new Map();
882
+ constructor(definitions = []) {
883
+ for (const d of definitions) this.register(d);
884
+ }
885
+ register(definition) {
886
+ this.defs.set(definition.type, definition);
887
+ return this;
888
+ }
889
+ get(type) {
890
+ return this.defs.get(type);
891
+ }
892
+ has(type) {
893
+ return this.defs.has(type);
894
+ }
895
+ types() {
896
+ return [...this.defs.keys()];
897
+ }
898
+ definitions() {
899
+ return [...this.defs.values()];
900
+ }
901
+ /** JSON Schemas of every registered section, keyed by type. */
902
+ toJSONSchema() {
903
+ return Object.fromEntries(this.definitions().map((d) => [d.type, sectionJsonSchema(d)]));
904
+ }
905
+ };
906
+ function createSectionRegistry(definitions = []) {
907
+ return new SectionRegistry(definitions);
908
+ }
909
+ var isDev = () => {
910
+ const p = globalThis.process;
911
+ return p?.env?.NODE_ENV !== "production";
912
+ };
913
+ function Sections({ sections, registry, context, renderUnknown, onInvalid }) {
914
+ if (!sections?.length) return null;
915
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: sections.map((section, index) => {
916
+ const key = section.id || `${section.type}-${index}`;
917
+ const def = registry.get(section.type);
918
+ if (!def) {
919
+ if (isDev()) console.warn(`[storefront-react] Unknown section type "${section.type}" (id ${section.id}); skipping.`);
920
+ return renderUnknown ? /* @__PURE__ */ jsxRuntime.jsx(react.Fragment, { children: renderUnknown(section) }, key) : null;
921
+ }
922
+ const { props, errors } = resolveSectionProps(def.schema, section.props);
923
+ if (errors.length) {
924
+ if (onInvalid) onInvalid(section, errors);
925
+ else if (isDev()) console.warn(`[storefront-react] Section "${section.type}" (id ${section.id}) has invalid props:`, errors);
926
+ }
927
+ if (errors.some((e) => e.message === "is required" && !e.prop.includes("[") && !e.prop.includes("."))) return null;
928
+ const Component = def.component;
929
+ return /* @__PURE__ */ jsxRuntime.jsx(Component, { ...props, section: { id: section.id, type: section.type }, context }, key);
930
+ }) });
931
+ }
932
+ var DEFAULT_FONT_FALLBACK = 'ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif';
933
+ var MONO_FALLBACK = "ui-monospace, SFMono-Regular, Menlo, monospace";
934
+ function tokenName(key) {
935
+ return key.trim().replace(/^-+/, "").replace(/^color-/, "").replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/[\s_.]+/g, "-").toLowerCase().replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "");
936
+ }
937
+ function isSafeCssValue(value) {
938
+ if (!value || value.length > 200) return false;
939
+ if (/[;{}<>\\]/.test(value)) return false;
940
+ if (/url\s*\(|expression\s*\(|@import/i.test(value)) return false;
941
+ return true;
942
+ }
943
+ function fontStack(family, role, fallback) {
944
+ const name = family.replace(/["']/g, "").trim();
945
+ if (!name || !isSafeCssValue(name)) return null;
946
+ return `"${name}", ${role === "mono" ? MONO_FALLBACK : fallback}`;
947
+ }
948
+ function themeToCssVariables(theme, options = {}) {
949
+ const naming = options.naming ?? "both";
950
+ const vars = {};
951
+ for (const [key, raw] of Object.entries(theme?.tokens ?? {})) {
952
+ const name = tokenName(key);
953
+ const value = String(raw).trim();
954
+ if (!name || !isSafeCssValue(value)) continue;
955
+ if (naming !== "metacore") vars[`--color-${name}`] = value;
956
+ if (naming !== "color") vars[`--${name}`] = value;
957
+ }
958
+ const fallback = options.fontFallback ?? DEFAULT_FONT_FALLBACK;
959
+ for (const [role, family] of Object.entries(theme?.fonts ?? {})) {
960
+ const name = tokenName(role);
961
+ const stack = typeof family === "string" ? fontStack(family, name, fallback) : null;
962
+ if (name && stack) vars[`--font-${name}`] = stack;
963
+ }
964
+ if (theme?.radius && isSafeCssValue(theme.radius.trim())) vars["--radius"] = theme.radius.trim();
965
+ return vars;
966
+ }
967
+ function themeToCss(theme, options = {}) {
968
+ const vars = themeToCssVariables(theme, options);
969
+ const body = Object.entries(vars).map(([k, v]) => `${k}:${v}`).join(";");
970
+ if (!body) return "";
971
+ const selector = (options.selector ?? ":root").replace(/[{}<>]/g, "");
972
+ return `${selector}{${body}}`;
973
+ }
974
+ function themeFontsHref(theme, weights = [400, 500, 600, 700]) {
975
+ const families = [...new Set(Object.values(theme?.fonts ?? {}).map((f) => String(f).replace(/["']/g, "").trim()).filter((f) => f && isSafeCssValue(f)))];
976
+ if (!families.length) return null;
977
+ const params = families.map((f) => `family=${encodeURIComponent(f).replace(/%20/g, "+")}:wght@${weights.join(";")}`).join("&");
978
+ return `https://fonts.googleapis.com/css2?${params}&display=swap`;
979
+ }
980
+ function ThemeStyle({ theme, nonce, id = "storefront-theme", ...options }) {
981
+ const css = themeToCss(theme, options);
982
+ if (!css) return null;
983
+ return /* @__PURE__ */ jsxRuntime.jsx("style", { id, nonce, "data-storefront-theme": "", dangerouslySetInnerHTML: { __html: css } });
984
+ }
985
+
986
+ exports.AddToCartButton = AddToCartButton;
987
+ exports.CART_COOKIE_NAME = CART_COOKIE_NAME;
988
+ exports.CartProvider = CartProvider;
989
+ exports.DEFAULT_IMAGE_WIDTHS = DEFAULT_IMAGE_WIDTHS;
990
+ exports.Image = Image;
991
+ exports.JsonLd = JsonLd;
992
+ exports.Money = Money;
993
+ exports.ProductPrice = ProductPrice;
994
+ exports.SectionRegistry = SectionRegistry;
995
+ exports.Sections = Sections;
996
+ exports.StorefrontProvider = StorefrontProvider;
997
+ exports.ThemeStyle = ThemeStyle;
998
+ exports.VariantSelector = VariantSelector;
999
+ exports.breadcrumbJsonLd = breadcrumbJsonLd;
1000
+ exports.collectionJsonLd = collectionJsonLd;
1001
+ exports.collectionMeta = collectionMeta;
1002
+ exports.createSectionRegistry = createSectionRegistry;
1003
+ exports.defaultImageLoader = defaultImageLoader;
1004
+ exports.defineSection = defineSection;
1005
+ exports.faqJsonLd = faqJsonLd;
1006
+ exports.getCartIdFromCookie = getCartIdFromCookie;
1007
+ exports.isSafeCssValue = isSafeCssValue;
1008
+ exports.localBusinessJsonLd = localBusinessJsonLd;
1009
+ exports.optimisticCart = optimisticCart;
1010
+ exports.organizationJsonLd = organizationJsonLd;
1011
+ exports.productJsonLd = productJsonLd;
1012
+ exports.productMeta = productMeta;
1013
+ exports.readCartCookie = readCartCookie;
1014
+ exports.resolveSectionProps = resolveSectionProps;
1015
+ exports.sectionJsonSchema = sectionJsonSchema;
1016
+ exports.seoMeta = seoMeta;
1017
+ exports.serializeCartCookie = serializeCartCookie;
1018
+ exports.serializeJsonLd = serializeJsonLd;
1019
+ exports.themeFontsHref = themeFontsHref;
1020
+ exports.themeToCss = themeToCss;
1021
+ exports.themeToCssVariables = themeToCssVariables;
1022
+ exports.toMerchandise = toMerchandise;
1023
+ exports.tokenName = tokenName;
1024
+ exports.useCart = useCart;
1025
+ exports.useOptionalCart = useOptionalCart;
1026
+ exports.useOptionalStorefront = useOptionalStorefront;
1027
+ exports.useProductOptions = useProductOptions;
1028
+ exports.useStorefront = useStorefront;
1029
+ exports.useStorefrontClient = useStorefrontClient;
1030
+ exports.writeCartCookie = writeCartCookie;
1031
+ //# sourceMappingURL=index.cjs.map
1032
+ //# sourceMappingURL=index.cjs.map