@asteby/storefront-react 0.6.0 → 0.8.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,144 @@
1
+ import * as react from 'react';
2
+ import { ElementType, ReactNode } from 'react';
3
+ import { Cart, CartLine, Money } from '@asteby/storefront';
4
+
5
+ type CartDrawerLabels = {
6
+ /** Título; el cajón agrega " (N)" con la cantidad de unidades. */
7
+ title: string;
8
+ /** Texto accesible del botón de cierre. */
9
+ close: string;
10
+ empty: string;
11
+ emptyHint?: string;
12
+ /** Texto del enlace "seguir comprando" del estado vacío (se muestra con `paths.browse`). */
13
+ browse?: string;
14
+ subtotal: string;
15
+ /** Nota bajo el subtotal. */
16
+ note?: string;
17
+ checkout: string;
18
+ viewCart: string;
19
+ remove: string;
20
+ increase: string;
21
+ decrease: string;
22
+ };
23
+ type CartDrawerPaths = {
24
+ /** Página del carrito: acción de las mutaciones y destino de "ver carrito". */
25
+ cart: string;
26
+ checkout: string;
27
+ browse?: string;
28
+ /** Enlace de la línea al producto; sin esto el título no es enlace. */
29
+ product?: (handle: string, variantId: string) => string;
30
+ };
31
+ /** Contrato de la acción de ruta del carrito de la tienda (nombres de campo e intenciones). */
32
+ type CartDrawerFormContract = {
33
+ intentField?: string;
34
+ lineField?: string;
35
+ quantityField?: string;
36
+ updateIntent?: string;
37
+ removeIntent?: string;
38
+ };
39
+ type CartDrawerShipping = {
40
+ /** Mensaje ya calculado por la tienda con lo que manda OPS. */
41
+ message: ReactNode;
42
+ /** 0..100 */
43
+ percent: number;
44
+ reached?: boolean;
45
+ progressLabel: string;
46
+ };
47
+ type CartDrawerLineForm = {
48
+ /** Componente de formulario (`'form'`, o `fetcher.Form` del router para mejorar sin recargar). */
49
+ Form: ElementType;
50
+ /** Datos en vuelo del formulario, para pintar la cantidad de forma optimista. */
51
+ formData?: FormData | null | undefined;
52
+ };
53
+ type CartDrawerLinkProps = {
54
+ href: string;
55
+ className?: string | undefined;
56
+ children: ReactNode;
57
+ onClick?: () => void;
58
+ };
59
+ type CartDrawerSlot = 'dialog' | 'panel' | 'header' | 'title' | 'close' | 'empty' | 'body' | 'shipping' | 'bar' | 'barFill' | 'lines' | 'line' | 'image' | 'lineTitle' | 'variant' | 'price' | 'qty' | 'qtyButton' | 'qtyValue' | 'remove' | 'upsell' | 'footer' | 'subtotal' | 'note' | 'actions' | 'checkout' | 'viewCart' | 'browse' | 'emptyTitle' | 'emptyHint' | 'shippingMessage' | 'lineMain' | 'lineHead' | 'lineText' | 'controls' | 'upsellTitle' | 'subtotalLabel' | 'subtotalValue';
60
+ type CartDrawerIcons = {
61
+ close?: ReactNode;
62
+ decrease?: ReactNode;
63
+ increase?: ReactNode;
64
+ shipping?: ReactNode;
65
+ shippingReached?: ReactNode;
66
+ };
67
+ type CartDrawerProps = {
68
+ /** `cart.cost` y cada línea vienen de OPS; el cajón no calcula nada. */
69
+ cart: Cart | null | undefined;
70
+ labels: CartDrawerLabels;
71
+ paths: CartDrawerPaths;
72
+ form?: CartDrawerFormContract;
73
+ /** Hook que da el formulario de cada línea. Por omisión `<form method="post">` (funciona sin JavaScript). */
74
+ useLineForm?: () => CartDrawerLineForm;
75
+ shipping?: CartDrawerShipping | null;
76
+ /** Aviso fijo cuando no hay barra de envío (p. ej. "Envío gratis"). */
77
+ notice?: ReactNode;
78
+ /** Sección "podría interesarte" (la tienda decide qué mostrar). */
79
+ upsell?: ReactNode;
80
+ upsellTitle?: string;
81
+ renderLine?: (line: CartLine, defaults: {
82
+ controls: ReactNode;
83
+ price: ReactNode;
84
+ image: ReactNode;
85
+ }) => ReactNode;
86
+ renderImage?: (line: CartLine) => ReactNode;
87
+ renderMoney?: (money: Money) => ReactNode;
88
+ renderLink?: (props: CartDrawerLinkProps) => ReactNode;
89
+ /** Omite el variante cuando es el valor por defecto de OPS ("Default Title"). */
90
+ isDefaultVariantTitle?: (title: string) => boolean;
91
+ icons?: CartDrawerIcons;
92
+ /** Clases por pieza (además de los `data-sf-cart-*`), para tiendas con Tailwind. */
93
+ classNames?: Partial<Record<CartDrawerSlot, string>>;
94
+ locale?: string;
95
+ /** Se llama al abrir/cerrar por cualquier vía. */
96
+ onOpenChange?: (open: boolean) => void;
97
+ };
98
+ /**
99
+ * Cajón lateral del carrito ("Tu carrito (N)"). Sin textos ni marca: todo llega por props.
100
+ * Se monta una vez (en la raíz) y se abre con `openCartDrawer()` / `useCartDrawer()`.
101
+ * `<dialog>` modal: `role="dialog"`, `aria-modal`, foco atrapado y devuelto al abridor, Esc y clic fuera cierran,
102
+ * el fondo no hace scroll. Las mutaciones usan la acción de ruta de la tienda (formularios nativos que
103
+ * funcionan sin JavaScript, o el `Form` de un fetcher con `useLineForm`). Estilos: `@asteby/storefront-react/cart.css`
104
+ * (tokens de la tienda) y/o `classNames`.
105
+ */
106
+ declare function CartDrawer(props: CartDrawerProps): react.JSX.Element | null;
107
+
108
+ /** Abre el cajón del carrito desde cualquier parte. */
109
+ declare function openCartDrawer(): void;
110
+ /** Cierra el cajón del carrito. */
111
+ declare function closeCartDrawer(): void;
112
+ declare function toggleCartDrawer(): void;
113
+ declare function isCartDrawerOpen(): boolean;
114
+ /** Estado y acciones del cajón. `open` es `false` en el servidor y en la primera pintura. */
115
+ declare function useCartDrawer(): {
116
+ open: boolean;
117
+ openDrawer: typeof openCartDrawer;
118
+ closeDrawer: typeof closeCartDrawer;
119
+ toggle: () => void;
120
+ };
121
+ /**
122
+ * Abre el cajón cuando termina un "agregar al carrito". `busy` es verdadero mientras hay un agregado en vuelo
123
+ * (en React Router: algún fetcher con la acción del carrito e intención de agregar). Con `skip` verdadero al
124
+ * terminar (error, agregado silencioso) no se abre.
125
+ */
126
+ declare function useOpenCartDrawerWhenDone(busy: boolean, options?: {
127
+ skip?: boolean;
128
+ }): void;
129
+ type ClickLike = {
130
+ defaultPrevented: boolean;
131
+ button: number;
132
+ metaKey: boolean;
133
+ ctrlKey: boolean;
134
+ shiftKey: boolean;
135
+ altKey: boolean;
136
+ preventDefault: () => void;
137
+ };
138
+ /**
139
+ * `onClick` para el enlace "Mi carrito" de la cabecera: con un clic normal abre el cajón; con clic con
140
+ * modificador, clic central o sin JavaScript el `href` (la página del carrito) sigue funcionando.
141
+ */
142
+ declare function handleCartLinkClick(e: ClickLike): void;
143
+
144
+ export { CartDrawer, type CartDrawerFormContract, type CartDrawerIcons, type CartDrawerLabels, type CartDrawerLineForm, type CartDrawerLinkProps, type CartDrawerPaths, type CartDrawerProps, type CartDrawerShipping, type CartDrawerSlot, closeCartDrawer, handleCartLinkClick, isCartDrawerOpen, openCartDrawer, toggleCartDrawer, useCartDrawer, useOpenCartDrawerWhenDone };
package/dist/cart.d.ts ADDED
@@ -0,0 +1,144 @@
1
+ import * as react from 'react';
2
+ import { ElementType, ReactNode } from 'react';
3
+ import { Cart, CartLine, Money } from '@asteby/storefront';
4
+
5
+ type CartDrawerLabels = {
6
+ /** Título; el cajón agrega " (N)" con la cantidad de unidades. */
7
+ title: string;
8
+ /** Texto accesible del botón de cierre. */
9
+ close: string;
10
+ empty: string;
11
+ emptyHint?: string;
12
+ /** Texto del enlace "seguir comprando" del estado vacío (se muestra con `paths.browse`). */
13
+ browse?: string;
14
+ subtotal: string;
15
+ /** Nota bajo el subtotal. */
16
+ note?: string;
17
+ checkout: string;
18
+ viewCart: string;
19
+ remove: string;
20
+ increase: string;
21
+ decrease: string;
22
+ };
23
+ type CartDrawerPaths = {
24
+ /** Página del carrito: acción de las mutaciones y destino de "ver carrito". */
25
+ cart: string;
26
+ checkout: string;
27
+ browse?: string;
28
+ /** Enlace de la línea al producto; sin esto el título no es enlace. */
29
+ product?: (handle: string, variantId: string) => string;
30
+ };
31
+ /** Contrato de la acción de ruta del carrito de la tienda (nombres de campo e intenciones). */
32
+ type CartDrawerFormContract = {
33
+ intentField?: string;
34
+ lineField?: string;
35
+ quantityField?: string;
36
+ updateIntent?: string;
37
+ removeIntent?: string;
38
+ };
39
+ type CartDrawerShipping = {
40
+ /** Mensaje ya calculado por la tienda con lo que manda OPS. */
41
+ message: ReactNode;
42
+ /** 0..100 */
43
+ percent: number;
44
+ reached?: boolean;
45
+ progressLabel: string;
46
+ };
47
+ type CartDrawerLineForm = {
48
+ /** Componente de formulario (`'form'`, o `fetcher.Form` del router para mejorar sin recargar). */
49
+ Form: ElementType;
50
+ /** Datos en vuelo del formulario, para pintar la cantidad de forma optimista. */
51
+ formData?: FormData | null | undefined;
52
+ };
53
+ type CartDrawerLinkProps = {
54
+ href: string;
55
+ className?: string | undefined;
56
+ children: ReactNode;
57
+ onClick?: () => void;
58
+ };
59
+ type CartDrawerSlot = 'dialog' | 'panel' | 'header' | 'title' | 'close' | 'empty' | 'body' | 'shipping' | 'bar' | 'barFill' | 'lines' | 'line' | 'image' | 'lineTitle' | 'variant' | 'price' | 'qty' | 'qtyButton' | 'qtyValue' | 'remove' | 'upsell' | 'footer' | 'subtotal' | 'note' | 'actions' | 'checkout' | 'viewCart' | 'browse' | 'emptyTitle' | 'emptyHint' | 'shippingMessage' | 'lineMain' | 'lineHead' | 'lineText' | 'controls' | 'upsellTitle' | 'subtotalLabel' | 'subtotalValue';
60
+ type CartDrawerIcons = {
61
+ close?: ReactNode;
62
+ decrease?: ReactNode;
63
+ increase?: ReactNode;
64
+ shipping?: ReactNode;
65
+ shippingReached?: ReactNode;
66
+ };
67
+ type CartDrawerProps = {
68
+ /** `cart.cost` y cada línea vienen de OPS; el cajón no calcula nada. */
69
+ cart: Cart | null | undefined;
70
+ labels: CartDrawerLabels;
71
+ paths: CartDrawerPaths;
72
+ form?: CartDrawerFormContract;
73
+ /** Hook que da el formulario de cada línea. Por omisión `<form method="post">` (funciona sin JavaScript). */
74
+ useLineForm?: () => CartDrawerLineForm;
75
+ shipping?: CartDrawerShipping | null;
76
+ /** Aviso fijo cuando no hay barra de envío (p. ej. "Envío gratis"). */
77
+ notice?: ReactNode;
78
+ /** Sección "podría interesarte" (la tienda decide qué mostrar). */
79
+ upsell?: ReactNode;
80
+ upsellTitle?: string;
81
+ renderLine?: (line: CartLine, defaults: {
82
+ controls: ReactNode;
83
+ price: ReactNode;
84
+ image: ReactNode;
85
+ }) => ReactNode;
86
+ renderImage?: (line: CartLine) => ReactNode;
87
+ renderMoney?: (money: Money) => ReactNode;
88
+ renderLink?: (props: CartDrawerLinkProps) => ReactNode;
89
+ /** Omite el variante cuando es el valor por defecto de OPS ("Default Title"). */
90
+ isDefaultVariantTitle?: (title: string) => boolean;
91
+ icons?: CartDrawerIcons;
92
+ /** Clases por pieza (además de los `data-sf-cart-*`), para tiendas con Tailwind. */
93
+ classNames?: Partial<Record<CartDrawerSlot, string>>;
94
+ locale?: string;
95
+ /** Se llama al abrir/cerrar por cualquier vía. */
96
+ onOpenChange?: (open: boolean) => void;
97
+ };
98
+ /**
99
+ * Cajón lateral del carrito ("Tu carrito (N)"). Sin textos ni marca: todo llega por props.
100
+ * Se monta una vez (en la raíz) y se abre con `openCartDrawer()` / `useCartDrawer()`.
101
+ * `<dialog>` modal: `role="dialog"`, `aria-modal`, foco atrapado y devuelto al abridor, Esc y clic fuera cierran,
102
+ * el fondo no hace scroll. Las mutaciones usan la acción de ruta de la tienda (formularios nativos que
103
+ * funcionan sin JavaScript, o el `Form` de un fetcher con `useLineForm`). Estilos: `@asteby/storefront-react/cart.css`
104
+ * (tokens de la tienda) y/o `classNames`.
105
+ */
106
+ declare function CartDrawer(props: CartDrawerProps): react.JSX.Element | null;
107
+
108
+ /** Abre el cajón del carrito desde cualquier parte. */
109
+ declare function openCartDrawer(): void;
110
+ /** Cierra el cajón del carrito. */
111
+ declare function closeCartDrawer(): void;
112
+ declare function toggleCartDrawer(): void;
113
+ declare function isCartDrawerOpen(): boolean;
114
+ /** Estado y acciones del cajón. `open` es `false` en el servidor y en la primera pintura. */
115
+ declare function useCartDrawer(): {
116
+ open: boolean;
117
+ openDrawer: typeof openCartDrawer;
118
+ closeDrawer: typeof closeCartDrawer;
119
+ toggle: () => void;
120
+ };
121
+ /**
122
+ * Abre el cajón cuando termina un "agregar al carrito". `busy` es verdadero mientras hay un agregado en vuelo
123
+ * (en React Router: algún fetcher con la acción del carrito e intención de agregar). Con `skip` verdadero al
124
+ * terminar (error, agregado silencioso) no se abre.
125
+ */
126
+ declare function useOpenCartDrawerWhenDone(busy: boolean, options?: {
127
+ skip?: boolean;
128
+ }): void;
129
+ type ClickLike = {
130
+ defaultPrevented: boolean;
131
+ button: number;
132
+ metaKey: boolean;
133
+ ctrlKey: boolean;
134
+ shiftKey: boolean;
135
+ altKey: boolean;
136
+ preventDefault: () => void;
137
+ };
138
+ /**
139
+ * `onClick` para el enlace "Mi carrito" de la cabecera: con un clic normal abre el cajón; con clic con
140
+ * modificador, clic central o sin JavaScript el `href` (la página del carrito) sigue funcionando.
141
+ */
142
+ declare function handleCartLinkClick(e: ClickLike): void;
143
+
144
+ export { CartDrawer, type CartDrawerFormContract, type CartDrawerIcons, type CartDrawerLabels, type CartDrawerLineForm, type CartDrawerLinkProps, type CartDrawerPaths, type CartDrawerProps, type CartDrawerShipping, type CartDrawerSlot, closeCartDrawer, handleCartLinkClick, isCartDrawerOpen, openCartDrawer, toggleCartDrawer, useCartDrawer, useOpenCartDrawerWhenDone };
package/dist/cart.js ADDED
@@ -0,0 +1,210 @@
1
+ import { useResolvedLocale, useModalDialog } from './chunk-2WZIWIBW.js';
2
+ import './chunk-DQBL2DFJ.js';
3
+ import { useSyncExternalStore, useCallback, useRef, useEffect, useId } from 'react';
4
+ import { formatMoney } from '@asteby/storefront';
5
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
6
+
7
+ var isOpen = false;
8
+ var listeners = /* @__PURE__ */ new Set();
9
+ function set(next) {
10
+ if (isOpen === next) return;
11
+ isOpen = next;
12
+ for (const l of [...listeners]) l();
13
+ }
14
+ var subscribe = (l) => {
15
+ listeners.add(l);
16
+ return () => {
17
+ listeners.delete(l);
18
+ };
19
+ };
20
+ function openCartDrawer() {
21
+ set(true);
22
+ }
23
+ function closeCartDrawer() {
24
+ set(false);
25
+ }
26
+ function toggleCartDrawer() {
27
+ set(!isOpen);
28
+ }
29
+ function isCartDrawerOpen() {
30
+ return isOpen;
31
+ }
32
+ function useCartDrawer() {
33
+ const open = useSyncExternalStore(subscribe, () => isOpen, () => false);
34
+ const toggle = useCallback(() => toggleCartDrawer(), []);
35
+ return { open, openDrawer: openCartDrawer, closeDrawer: closeCartDrawer, toggle };
36
+ }
37
+ function useOpenCartDrawerWhenDone(busy, options = {}) {
38
+ const was = useRef(false);
39
+ const skip = useRef(false);
40
+ skip.current = !!options.skip;
41
+ useEffect(() => {
42
+ if (was.current && !busy && !skip.current) openCartDrawer();
43
+ was.current = busy;
44
+ }, [busy]);
45
+ }
46
+ function handleCartLinkClick(e) {
47
+ if (e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
48
+ e.preventDefault();
49
+ openCartDrawer();
50
+ }
51
+ var DEFAULT_FORM = {
52
+ intentField: "intent",
53
+ lineField: "line_id",
54
+ quantityField: "quantity",
55
+ updateIntent: "update",
56
+ removeIntent: "remove"
57
+ };
58
+ var useNativeForm = () => ({ Form: "form" });
59
+ var defaultIsDefaultVariant = (t) => /^default title$/i.test(t.trim());
60
+ function useBodyScrollLock(locked) {
61
+ useEffect(() => {
62
+ if (!locked || typeof document === "undefined") return;
63
+ const el = document.documentElement;
64
+ const prev = el.style.overflow;
65
+ el.style.overflow = "hidden";
66
+ return () => {
67
+ el.style.overflow = prev;
68
+ };
69
+ }, [locked]);
70
+ }
71
+ function CartDrawer(props) {
72
+ const { cart, labels, paths, shipping, notice, upsell, upsellTitle, icons, classNames: cx = {}, onOpenChange } = props;
73
+ const locale = useResolvedLocale(props.locale);
74
+ const { open } = useCartDrawer();
75
+ const titleId = useId();
76
+ const upsellId = useId();
77
+ const { ref, onBackdropClick } = useModalDialog({ open, onClose: () => closeCartDrawer() });
78
+ useBodyScrollLock(open);
79
+ useEffect(() => {
80
+ onOpenChange?.(open);
81
+ }, [open]);
82
+ if (!open) return null;
83
+ const money = props.renderMoney ?? ((m) => formatMoney(m, locale));
84
+ const link = props.renderLink ?? (({ href, className, children, onClick }) => /* @__PURE__ */ jsx("a", { href, className, onClick, children }));
85
+ const count = cart?.total_quantity ?? 0;
86
+ const lines = cart?.lines ?? [];
87
+ const empty = !cart || lines.length === 0;
88
+ const close = () => closeCartDrawer();
89
+ return /* @__PURE__ */ jsx(
90
+ "dialog",
91
+ {
92
+ ref,
93
+ role: "dialog",
94
+ "aria-modal": "true",
95
+ "aria-labelledby": titleId,
96
+ onClick: onBackdropClick,
97
+ onClose: close,
98
+ className: cx.dialog,
99
+ "data-sf-cart-drawer": "",
100
+ children: /* @__PURE__ */ jsxs("div", { className: cx.panel, "data-sf-cart-panel": "", children: [
101
+ /* @__PURE__ */ jsxs("div", { className: cx.header, "data-sf-cart-header": "", children: [
102
+ /* @__PURE__ */ jsxs("h2", { id: titleId, tabIndex: -1, className: cx.title, "data-sf-cart-title": "", children: [
103
+ labels.title,
104
+ count > 0 ? ` (${count})` : ""
105
+ ] }),
106
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: close, "aria-label": labels.close, className: cx.close, "data-sf-cart-close": "", "data-sf-autofocus": "", children: icons?.close ?? /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\u2715" }) })
107
+ ] }),
108
+ empty ? /* @__PURE__ */ jsxs("div", { className: cx.empty, "data-sf-cart-empty": "", children: [
109
+ /* @__PURE__ */ jsx("p", { className: cx.emptyTitle, "data-sf-cart-empty-title": "", children: labels.empty }),
110
+ labels.emptyHint ? /* @__PURE__ */ jsx("p", { className: cx.emptyHint, "data-sf-cart-empty-hint": "", children: labels.emptyHint }) : null,
111
+ paths.browse && labels.browse ? link({ href: paths.browse, className: cx.browse, children: labels.browse, onClick: close }) : null
112
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
113
+ /* @__PURE__ */ jsxs("div", { className: cx.body, "data-sf-cart-body": "", children: [
114
+ shipping ? /* @__PURE__ */ jsxs("div", { className: cx.shipping, "data-sf-cart-shipping": "", "data-reached": shipping.reached || void 0, children: [
115
+ /* @__PURE__ */ jsxs("p", { className: cx.shippingMessage, children: [
116
+ shipping.reached ? icons?.shippingReached : icons?.shipping,
117
+ shipping.message
118
+ ] }),
119
+ /* @__PURE__ */ jsx(
120
+ "div",
121
+ {
122
+ role: "progressbar",
123
+ "aria-valuemin": 0,
124
+ "aria-valuemax": 100,
125
+ "aria-valuenow": Math.round(shipping.percent),
126
+ "aria-label": shipping.progressLabel,
127
+ className: cx.bar,
128
+ "data-sf-cart-bar": "",
129
+ children: /* @__PURE__ */ jsx("div", { className: cx.barFill, "data-sf-cart-bar-fill": "", style: { width: `${Math.max(0, Math.min(100, shipping.percent))}%` } })
130
+ }
131
+ )
132
+ ] }) : notice ? /* @__PURE__ */ jsxs("p", { className: [cx.shipping, cx.shippingMessage].filter(Boolean).join(" ") || void 0, "data-sf-cart-shipping": "", "data-notice": "", children: [
133
+ icons?.shippingReached,
134
+ notice
135
+ ] }) : null,
136
+ /* @__PURE__ */ jsx("ul", { className: cx.lines, "data-sf-cart-lines": "", children: lines.map((line) => /* @__PURE__ */ jsx(DrawerLine, { line, props, money, link, close }, line.id)) }),
137
+ upsell ? /* @__PURE__ */ jsxs("section", { className: cx.upsell, "data-sf-cart-upsell": "", "aria-labelledby": upsellTitle ? upsellId : void 0, children: [
138
+ upsellTitle ? /* @__PURE__ */ jsx("h3", { id: upsellId, className: cx.upsellTitle, children: upsellTitle }) : null,
139
+ upsell
140
+ ] }) : null
141
+ ] }),
142
+ /* @__PURE__ */ jsxs("div", { className: cx.footer, "data-sf-cart-footer": "", children: [
143
+ /* @__PURE__ */ jsxs("div", { className: cx.subtotal, "data-sf-cart-subtotal": "", children: [
144
+ /* @__PURE__ */ jsx("span", { className: cx.subtotalLabel, children: labels.subtotal }),
145
+ /* @__PURE__ */ jsx("span", { className: cx.subtotalValue, "data-sf-cart-subtotal-value": "", children: cart ? money(cart.cost.subtotal) : null })
146
+ ] }),
147
+ labels.note ? /* @__PURE__ */ jsx("p", { className: cx.note, "data-sf-cart-note": "", children: labels.note }) : null,
148
+ /* @__PURE__ */ jsxs("div", { className: cx.actions, "data-sf-cart-actions": "", children: [
149
+ link({ href: paths.checkout, className: cx.checkout, children: labels.checkout, onClick: close }),
150
+ link({ href: paths.cart, className: cx.viewCart, children: labels.viewCart, onClick: close })
151
+ ] })
152
+ ] })
153
+ ] })
154
+ ] })
155
+ }
156
+ );
157
+ }
158
+ function DrawerLine({
159
+ line,
160
+ props,
161
+ money,
162
+ link,
163
+ close
164
+ }) {
165
+ const { labels, paths, icons, classNames: cx = {} } = props;
166
+ const f = { ...DEFAULT_FORM, ...props.form };
167
+ const { Form, formData } = (props.useLineForm ?? useNativeForm)();
168
+ const quantity = formData ? Number(formData.get(f.quantityField) ?? 0) : line.quantity;
169
+ if (formData && (formData.get(f.intentField) === f.removeIntent || quantity === 0)) return null;
170
+ const merch = line.merchandise;
171
+ const isDefault = props.isDefaultVariantTitle ?? defaultIsDefaultVariant;
172
+ const title = merch.product.title;
173
+ const href = paths.product?.(merch.product.handle, merch.id);
174
+ const img = merch.image ?? merch.product.featured_image;
175
+ const image = props.renderImage ? props.renderImage(line) : img ? /* @__PURE__ */ jsx("img", { src: img.url, alt: img.alt ?? "", width: 80, height: 80, loading: "lazy", decoding: "async" }) : null;
176
+ const imageNode = /* @__PURE__ */ jsx("span", { className: cx.image, "data-sf-cart-image": "", children: image });
177
+ const price = /* @__PURE__ */ jsx("span", { className: cx.price, "data-sf-cart-price": "", children: money(line.cost.total) });
178
+ const controls = /* @__PURE__ */ jsxs("div", { className: cx.controls, "data-sf-cart-controls": "", children: [
179
+ /* @__PURE__ */ jsxs(Form, { method: "post", action: paths.cart, className: cx.qty, "data-sf-cart-qty": "", children: [
180
+ /* @__PURE__ */ jsx("input", { type: "hidden", name: f.intentField, value: f.updateIntent }),
181
+ /* @__PURE__ */ jsx("input", { type: "hidden", name: f.lineField, value: line.id }),
182
+ /* @__PURE__ */ jsx("button", { type: "submit", name: f.quantityField, value: quantity - 1, "aria-label": labels.decrease, className: cx.qtyButton, "data-sf-cart-qty-button": "", children: icons?.decrease ?? /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\u2212" }) }),
183
+ /* @__PURE__ */ jsx("span", { className: cx.qtyValue, "aria-live": "polite", "data-sf-cart-qty-value": "", children: quantity }),
184
+ /* @__PURE__ */ jsx("button", { type: "submit", name: f.quantityField, value: quantity + 1, "aria-label": labels.increase, className: cx.qtyButton, "data-sf-cart-qty-button": "", children: icons?.increase ?? /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "+" }) })
185
+ ] }),
186
+ /* @__PURE__ */ jsxs(Form, { method: "post", action: paths.cart, "data-sf-cart-remove-form": "", children: [
187
+ /* @__PURE__ */ jsx("input", { type: "hidden", name: f.intentField, value: f.removeIntent }),
188
+ /* @__PURE__ */ jsx("input", { type: "hidden", name: f.lineField, value: line.id }),
189
+ /* @__PURE__ */ jsx("button", { type: "submit", className: cx.remove, "data-sf-cart-remove": "", children: labels.remove })
190
+ ] })
191
+ ] });
192
+ if (props.renderLine) return /* @__PURE__ */ jsx("li", { className: cx.line, "data-sf-cart-line": "", children: props.renderLine(line, { controls, price, image: imageNode }) }, line.id);
193
+ return /* @__PURE__ */ jsxs("li", { className: cx.line, "data-sf-cart-line": "", children: [
194
+ imageNode,
195
+ /* @__PURE__ */ jsxs("div", { className: cx.lineMain, "data-sf-cart-line-main": "", children: [
196
+ /* @__PURE__ */ jsxs("div", { className: cx.lineHead, "data-sf-cart-line-head": "", children: [
197
+ /* @__PURE__ */ jsxs("div", { className: cx.lineText, "data-sf-cart-line-text": "", children: [
198
+ href ? link({ href, className: cx.lineTitle, children: title, onClick: close }) : /* @__PURE__ */ jsx("span", { className: cx.lineTitle, "data-sf-cart-line-title": "", children: title }),
199
+ !isDefault(merch.title) ? /* @__PURE__ */ jsx("p", { className: cx.variant, "data-sf-cart-variant": "", children: merch.title }) : null
200
+ ] }),
201
+ price
202
+ ] }),
203
+ controls
204
+ ] })
205
+ ] });
206
+ }
207
+
208
+ export { CartDrawer, closeCartDrawer, handleCartLinkClick, isCartDrawerOpen, openCartDrawer, toggleCartDrawer, useCartDrawer, useOpenCartDrawerWhenDone };
209
+ //# sourceMappingURL=cart.js.map
210
+ //# sourceMappingURL=cart.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cart-drawer/store.ts","../src/cart-drawer/cart-drawer.tsx"],"names":["useEffect"],"mappings":";;;;;;AAKA,IAAI,MAAA,GAAS,KAAA;AACb,IAAM,SAAA,uBAAgB,GAAA,EAAgB;AAEtC,SAAS,IAAI,IAAA,EAAe;AAC1B,EAAA,IAAI,WAAW,IAAA,EAAM;AACrB,EAAA,MAAA,GAAS,IAAA;AACT,EAAA,KAAA,MAAW,CAAA,IAAK,CAAC,GAAG,SAAS,GAAG,CAAA,EAAE;AACpC;AAEA,IAAM,SAAA,GAAY,CAAC,CAAA,KAAkB;AACnC,EAAA,SAAA,CAAU,IAAI,CAAC,CAAA;AACf,EAAA,OAAO,MAAM;AACX,IAAA,SAAA,CAAU,OAAO,CAAC,CAAA;AAAA,EACpB,CAAA;AACF,CAAA;AAGO,SAAS,cAAA,GAAiB;AAC/B,EAAA,GAAA,CAAI,IAAI,CAAA;AACV;AAGO,SAAS,eAAA,GAAkB;AAChC,EAAA,GAAA,CAAI,KAAK,CAAA;AACX;AAEO,SAAS,gBAAA,GAAmB;AACjC,EAAA,GAAA,CAAI,CAAC,MAAM,CAAA;AACb;AAEO,SAAS,gBAAA,GAAmB;AACjC,EAAA,OAAO,MAAA;AACT;AAGO,SAAS,aAAA,GAAgB;AAC9B,EAAA,MAAM,OAAO,oBAAA,CAAqB,SAAA,EAAW,MAAM,MAAA,EAAQ,MAAM,KAAK,CAAA;AACtE,EAAA,MAAM,SAAS,WAAA,CAAY,MAAM,gBAAA,EAAiB,EAAG,EAAE,CAAA;AACvD,EAAA,OAAO,EAAE,IAAA,EAAM,UAAA,EAAY,cAAA,EAAgB,WAAA,EAAa,iBAAiB,MAAA,EAAO;AAClF;AAOO,SAAS,yBAAA,CAA0B,IAAA,EAAe,OAAA,GAA8B,EAAC,EAAG;AACzF,EAAA,MAAM,GAAA,GAAM,OAAO,KAAK,CAAA;AACxB,EAAA,MAAM,IAAA,GAAO,OAAO,KAAK,CAAA;AACzB,EAAA,IAAA,CAAK,OAAA,GAAU,CAAC,CAAC,OAAA,CAAQ,IAAA;AACzB,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,IAAI,OAAA,IAAW,CAAC,QAAQ,CAAC,IAAA,CAAK,SAAS,cAAA,EAAe;AAC1D,IAAA,GAAA,CAAI,OAAA,GAAU,IAAA;AAAA,EAChB,CAAA,EAAG,CAAC,IAAI,CAAC,CAAA;AACX;AAgBO,SAAS,oBAAoB,CAAA,EAAc;AAChD,EAAA,IAAI,CAAA,CAAE,gBAAA,IAAoB,CAAA,CAAE,MAAA,KAAW,CAAA,IAAK,CAAA,CAAE,OAAA,IAAW,CAAA,CAAE,OAAA,IAAW,CAAA,CAAE,QAAA,IAAY,CAAA,CAAE,MAAA,EAAQ;AAC9F,EAAA,CAAA,CAAE,cAAA,EAAe;AACjB,EAAA,cAAA,EAAe;AACjB;ACkBA,IAAM,YAAA,GAAiD;AAAA,EACrD,WAAA,EAAa,QAAA;AAAA,EACb,SAAA,EAAW,SAAA;AAAA,EACX,aAAA,EAAe,UAAA;AAAA,EACf,YAAA,EAAc,QAAA;AAAA,EACd,YAAA,EAAc;AAChB,CAAA;AAEA,IAAM,aAAA,GAAgB,OAA2B,EAAE,IAAA,EAAM,MAAA,EAAO,CAAA;AAEhE,IAAM,0BAA0B,CAAC,CAAA,KAAc,mBAAmB,IAAA,CAAK,CAAA,CAAE,MAAM,CAAA;AAE/E,SAAS,kBAAkB,MAAA,EAAiB;AAC1C,EAAAA,UAAU,MAAM;AACd,IAAA,IAAI,CAAC,MAAA,IAAU,OAAO,QAAA,KAAa,WAAA,EAAa;AAChD,IAAA,MAAM,KAAK,QAAA,CAAS,eAAA;AACpB,IAAA,MAAM,IAAA,GAAO,GAAG,KAAA,CAAM,QAAA;AACtB,IAAA,EAAA,CAAG,MAAM,QAAA,GAAW,QAAA;AACpB,IAAA,OAAO,MAAM;AACX,MAAA,EAAA,CAAG,MAAM,QAAA,GAAW,IAAA;AAAA,IACtB,CAAA;AAAA,EACF,CAAA,EAAG,CAAC,MAAM,CAAC,CAAA;AACb;AAUO,SAAS,WAAW,KAAA,EAAwB;AACjD,EAAA,MAAM,EAAE,IAAA,EAAM,MAAA,EAAQ,KAAA,EAAO,UAAU,MAAA,EAAQ,MAAA,EAAQ,WAAA,EAAa,KAAA,EAAO,UAAA,EAAY,EAAA,GAAK,EAAC,EAAG,cAAa,GAAI,KAAA;AACjH,EAAA,MAAM,MAAA,GAAS,iBAAA,CAAkB,KAAA,CAAM,MAAM,CAAA;AAC7C,EAAA,MAAM,EAAE,IAAA,EAAK,GAAI,aAAA,EAAc;AAC/B,EAAA,MAAM,UAAU,KAAA,EAAM;AACtB,EAAA,MAAM,WAAW,KAAA,EAAM;AACvB,EAAA,MAAM,EAAE,GAAA,EAAK,eAAA,EAAgB,GAAI,cAAA,CAAe,EAAE,IAAA,EAAM,OAAA,EAAS,MAAM,eAAA,EAAgB,EAAG,CAAA;AAC1F,EAAA,iBAAA,CAAkB,IAAI,CAAA;AACtB,EAAAA,UAAU,MAAM;AACd,IAAA,YAAA,GAAe,IAAI,CAAA;AAAA,EACrB,CAAA,EAAG,CAAC,IAAI,CAAC,CAAA;AAET,EAAA,IAAI,CAAC,MAAM,OAAO,IAAA;AAElB,EAAA,MAAM,QAAQ,KAAA,CAAM,WAAA,KAAgB,CAAC,CAAA,KAAa,WAAA,CAAY,GAAG,MAAM,CAAA,CAAA;AACvE,EAAA,MAAM,IAAA,GAAO,KAAA,CAAM,UAAA,KAAe,CAAC,EAAE,IAAA,EAAM,SAAA,EAAW,QAAA,EAAU,OAAA,uBAC9D,GAAA,CAAC,GAAA,EAAA,EAAE,IAAA,EAAY,SAAA,EAAsB,SAAmB,QAAA,EAAS,CAAA,CAAA;AAEnE,EAAA,MAAM,KAAA,GAAQ,MAAM,cAAA,IAAkB,CAAA;AACtC,EAAA,MAAM,KAAA,GAAQ,IAAA,EAAM,KAAA,IAAS,EAAC;AAC9B,EAAA,MAAM,KAAA,GAAQ,CAAC,IAAA,IAAQ,KAAA,CAAM,MAAA,KAAW,CAAA;AACxC,EAAA,MAAM,KAAA,GAAQ,MAAM,eAAA,EAAgB;AAEpC,EAAA,uBACE,GAAA;AAAA,IAAC,QAAA;AAAA,IAAA;AAAA,MACC,GAAA;AAAA,MACA,IAAA,EAAK,QAAA;AAAA,MACL,YAAA,EAAW,MAAA;AAAA,MACX,iBAAA,EAAiB,OAAA;AAAA,MACjB,OAAA,EAAS,eAAA;AAAA,MACT,OAAA,EAAS,KAAA;AAAA,MACT,WAAW,EAAA,CAAG,MAAA;AAAA,MACd,qBAAA,EAAoB,EAAA;AAAA,MAEpB,+BAAC,KAAA,EAAA,EAAI,SAAA,EAAW,EAAA,CAAG,KAAA,EAAO,sBAAmB,EAAA,EAC3C,QAAA,EAAA;AAAA,wBAAA,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAW,EAAA,CAAG,MAAA,EAAQ,uBAAoB,EAAA,EAC7C,QAAA,EAAA;AAAA,0BAAA,IAAA,CAAC,IAAA,EAAA,EAAG,IAAI,OAAA,EAAS,QAAA,EAAU,IAAI,SAAA,EAAW,EAAA,CAAG,KAAA,EAAO,oBAAA,EAAmB,EAAA,EACpE,QAAA,EAAA;AAAA,YAAA,MAAA,CAAO,KAAA;AAAA,YAAO,KAAA,GAAQ,CAAA,GAAI,CAAA,EAAA,EAAK,KAAK,CAAA,CAAA,CAAA,GAAM;AAAA,WAAA,EAC7C,CAAA;AAAA,0BACA,GAAA,CAAC,YAAO,IAAA,EAAK,QAAA,EAAS,SAAS,KAAA,EAAO,YAAA,EAAY,MAAA,CAAO,KAAA,EAAO,SAAA,EAAW,EAAA,CAAG,OAAO,oBAAA,EAAmB,EAAA,EAAG,mBAAA,EAAkB,EAAA,EAC1H,QAAA,EAAA,KAAA,EAAO,KAAA,wBAAU,MAAA,EAAA,EAAK,aAAA,EAAY,MAAA,EAAO,QAAA,EAAA,QAAA,EAAC,CAAA,EAC7C;AAAA,SAAA,EACF,CAAA;AAAA,QAEC,wBACC,IAAA,CAAC,KAAA,EAAA,EAAI,WAAW,EAAA,CAAG,KAAA,EAAO,sBAAmB,EAAA,EAC3C,QAAA,EAAA;AAAA,0BAAA,GAAA,CAAC,OAAE,SAAA,EAAW,EAAA,CAAG,YAAY,0BAAA,EAAyB,EAAA,EAAI,iBAAO,KAAA,EAAM,CAAA;AAAA,UACtE,MAAA,CAAO,SAAA,mBAAY,GAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAW,EAAA,CAAG,SAAA,EAAW,yBAAA,EAAwB,EAAA,EAAI,QAAA,EAAA,MAAA,CAAO,SAAA,EAAU,CAAA,GAAO,IAAA;AAAA,UACnG,MAAM,MAAA,IAAU,MAAA,CAAO,SAAS,IAAA,CAAK,EAAE,MAAM,KAAA,CAAM,MAAA,EAAQ,SAAA,EAAW,EAAA,CAAG,QAAQ,QAAA,EAAU,MAAA,CAAO,QAAQ,OAAA,EAAS,KAAA,EAAO,CAAA,GAAI;AAAA,SAAA,EACjI,oBAEA,IAAA,CAAA,QAAA,EAAA,EACE,QAAA,EAAA;AAAA,0BAAA,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAW,EAAA,CAAG,IAAA,EAAM,qBAAkB,EAAA,EACxC,QAAA,EAAA;AAAA,YAAA,QAAA,mBACC,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAW,EAAA,CAAG,QAAA,EAAU,yBAAsB,EAAA,EAAG,cAAA,EAAc,QAAA,CAAS,OAAA,IAAW,MAAA,EACtF,QAAA,EAAA;AAAA,8BAAA,IAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAW,EAAA,CAAG,eAAA,EACd,QAAA,EAAA;AAAA,gBAAA,QAAA,CAAS,OAAA,GAAU,KAAA,EAAO,eAAA,GAAkB,KAAA,EAAO,QAAA;AAAA,gBACnD,QAAA,CAAS;AAAA,eAAA,EACZ,CAAA;AAAA,8BACA,GAAA;AAAA,gBAAC,KAAA;AAAA,gBAAA;AAAA,kBACC,IAAA,EAAK,aAAA;AAAA,kBACL,eAAA,EAAe,CAAA;AAAA,kBACf,eAAA,EAAe,GAAA;AAAA,kBACf,eAAA,EAAe,IAAA,CAAK,KAAA,CAAM,QAAA,CAAS,OAAO,CAAA;AAAA,kBAC1C,cAAY,QAAA,CAAS,aAAA;AAAA,kBACrB,WAAW,EAAA,CAAG,GAAA;AAAA,kBACd,kBAAA,EAAiB,EAAA;AAAA,kBAEjB,QAAA,kBAAA,GAAA,CAAC,SAAI,SAAA,EAAW,EAAA,CAAG,SAAS,uBAAA,EAAsB,EAAA,EAAG,KAAA,EAAO,EAAE,KAAA,EAAO,CAAA,EAAG,KAAK,GAAA,CAAI,CAAA,EAAG,KAAK,GAAA,CAAI,GAAA,EAAK,SAAS,OAAO,CAAC,CAAC,CAAA,CAAA,CAAA,EAAI,EAAG;AAAA;AAAA;AAC7H,aAAA,EACF,CAAA,GACE,yBACF,IAAA,CAAC,GAAA,EAAA,EAAE,WAAW,CAAC,EAAA,CAAG,UAAU,EAAA,CAAG,eAAe,EAAE,MAAA,CAAO,OAAO,EAAE,IAAA,CAAK,GAAG,KAAK,MAAA,EAAW,uBAAA,EAAsB,EAAA,EAAG,aAAA,EAAY,EAAA,EAAI,QAAA,EAAA;AAAA,cAAA,KAAA,EAAO,eAAA;AAAA,cAAiB;AAAA,aAAA,EAAO,CAAA,GAC9J,IAAA;AAAA,4BAEJ,GAAA,CAAC,QAAG,SAAA,EAAW,EAAA,CAAG,OAAO,oBAAA,EAAmB,EAAA,EACzC,gBAAM,GAAA,CAAI,CAAC,yBACV,GAAA,CAAC,UAAA,EAAA,EAAyB,MAAY,KAAA,EAAc,KAAA,EAAc,MAAY,KAAA,EAAA,EAA7D,IAAA,CAAK,EAAsE,CAC7F,CAAA,EACH,CAAA;AAAA,YAEC,MAAA,mBACC,IAAA,CAAC,SAAA,EAAA,EAAQ,SAAA,EAAW,EAAA,CAAG,MAAA,EAAQ,qBAAA,EAAoB,EAAA,EAAG,iBAAA,EAAiB,WAAA,GAAc,QAAA,GAAW,MAAA,EAC7F,QAAA,EAAA;AAAA,cAAA,WAAA,mBAAc,GAAA,CAAC,QAAG,EAAA,EAAI,QAAA,EAAU,WAAW,EAAA,CAAG,WAAA,EAAc,uBAAY,CAAA,GAAQ,IAAA;AAAA,cAChF;AAAA,aAAA,EACH,CAAA,GACE;AAAA,WAAA,EACN,CAAA;AAAA,+BAEC,KAAA,EAAA,EAAI,SAAA,EAAW,EAAA,CAAG,MAAA,EAAQ,uBAAoB,EAAA,EAC7C,QAAA,EAAA;AAAA,4BAAA,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAW,EAAA,CAAG,QAAA,EAAU,yBAAsB,EAAA,EACjD,QAAA,EAAA;AAAA,8BAAA,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAW,EAAA,CAAG,aAAA,EAAgB,iBAAO,QAAA,EAAS,CAAA;AAAA,8BACpD,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAW,EAAA,CAAG,aAAA,EAAe,6BAAA,EAA4B,EAAA,EAAI,QAAA,EAAA,IAAA,GAAO,KAAA,CAAM,IAAA,CAAK,IAAA,CAAK,QAAQ,IAAI,IAAA,EAAK;AAAA,aAAA,EAC7G,CAAA;AAAA,YACC,MAAA,CAAO,IAAA,mBAAO,GAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAW,EAAA,CAAG,IAAA,EAAM,mBAAA,EAAkB,EAAA,EAAI,QAAA,EAAA,MAAA,CAAO,IAAA,EAAK,CAAA,GAAO,IAAA;AAAA,iCAC9E,KAAA,EAAA,EAAI,SAAA,EAAW,EAAA,CAAG,OAAA,EAAS,wBAAqB,EAAA,EAC9C,QAAA,EAAA;AAAA,cAAA,IAAA,CAAK,EAAE,IAAA,EAAM,KAAA,CAAM,QAAA,EAAU,SAAA,EAAW,EAAA,CAAG,QAAA,EAAU,QAAA,EAAU,MAAA,CAAO,QAAA,EAAU,OAAA,EAAS,KAAA,EAAO,CAAA;AAAA,cAChG,IAAA,CAAK,EAAE,IAAA,EAAM,KAAA,CAAM,IAAA,EAAM,SAAA,EAAW,EAAA,CAAG,QAAA,EAAU,QAAA,EAAU,MAAA,CAAO,QAAA,EAAU,OAAA,EAAS,OAAO;AAAA,aAAA,EAC/F;AAAA,WAAA,EACF;AAAA,SAAA,EACF;AAAA,OAAA,EAEJ;AAAA;AAAA,GACF;AAEJ;AAEA,SAAS,UAAA,CAAW;AAAA,EAClB,IAAA;AAAA,EACA,KAAA;AAAA,EACA,KAAA;AAAA,EACA,IAAA;AAAA,EACA;AACF,CAAA,EAMG;AACD,EAAA,MAAM,EAAE,QAAQ,KAAA,EAAO,KAAA,EAAO,YAAY,EAAA,GAAK,IAAG,GAAI,KAAA;AACtD,EAAA,MAAM,IAAI,EAAE,GAAG,YAAA,EAAc,GAAG,MAAM,IAAA,EAAK;AAC3C,EAAA,MAAM,EAAE,IAAA,EAAM,QAAA,EAAS,GAAA,CAAK,KAAA,CAAM,eAAe,aAAA,GAAe;AAChE,EAAA,MAAM,QAAA,GAAW,QAAA,GAAW,MAAA,CAAO,QAAA,CAAS,GAAA,CAAI,EAAE,aAAa,CAAA,IAAK,CAAC,CAAA,GAAI,IAAA,CAAK,QAAA;AAC9E,EAAA,IAAI,QAAA,KAAa,QAAA,CAAS,GAAA,CAAI,CAAA,CAAE,WAAW,MAAM,CAAA,CAAE,YAAA,IAAgB,QAAA,KAAa,CAAA,CAAA,EAAI,OAAO,IAAA;AAE3F,EAAA,MAAM,QAAQ,IAAA,CAAK,WAAA;AACnB,EAAA,MAAM,SAAA,GAAY,MAAM,qBAAA,IAAyB,uBAAA;AACjD,EAAA,MAAM,KAAA,GAAQ,MAAM,OAAA,CAAQ,KAAA;AAC5B,EAAA,MAAM,OAAO,KAAA,CAAM,OAAA,GAAU,MAAM,OAAA,CAAQ,MAAA,EAAQ,MAAM,EAAE,CAAA;AAC3D,EAAA,MAAM,GAAA,GAAM,KAAA,CAAM,KAAA,IAAS,KAAA,CAAM,OAAA,CAAQ,cAAA;AACzC,EAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,WAAA,GAAc,KAAA,CAAM,WAAA,CAAY,IAAI,CAAA,GAAI,GAAA,mBAC1D,GAAA,CAAC,KAAA,EAAA,EAAI,GAAA,EAAK,GAAA,CAAI,KAAK,GAAA,EAAK,GAAA,CAAI,GAAA,IAAO,EAAA,EAAI,KAAA,EAAO,EAAA,EAAI,MAAA,EAAQ,EAAA,EAAI,OAAA,EAAQ,MAAA,EAAO,QAAA,EAAS,OAAA,EAAQ,CAAA,GAC5F,IAAA;AAEJ,EAAA,MAAM,SAAA,uBAAa,MAAA,EAAA,EAAK,SAAA,EAAW,GAAG,KAAA,EAAO,oBAAA,EAAmB,IAAI,QAAA,EAAA,KAAA,EAAM,CAAA;AAC1E,EAAA,MAAM,KAAA,mBAAQ,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAW,EAAA,CAAG,KAAA,EAAO,oBAAA,EAAmB,EAAA,EAAI,QAAA,EAAA,KAAA,CAAM,IAAA,CAAK,IAAA,CAAK,KAAK,CAAA,EAAE,CAAA;AACvF,EAAA,MAAM,2BACJ,IAAA,CAAC,KAAA,EAAA,EAAI,WAAW,EAAA,CAAG,QAAA,EAAU,yBAAsB,EAAA,EACjD,QAAA,EAAA;AAAA,oBAAA,IAAA,CAAC,IAAA,EAAA,EAAK,MAAA,EAAO,MAAA,EAAO,MAAA,EAAQ,KAAA,CAAM,MAAM,SAAA,EAAW,EAAA,CAAG,GAAA,EAAK,kBAAA,EAAiB,EAAA,EAC1E,QAAA,EAAA;AAAA,sBAAA,GAAA,CAAC,OAAA,EAAA,EAAM,MAAK,QAAA,EAAS,IAAA,EAAM,EAAE,WAAA,EAAa,KAAA,EAAO,EAAE,YAAA,EAAc,CAAA;AAAA,sBACjE,GAAA,CAAC,WAAM,IAAA,EAAK,QAAA,EAAS,MAAM,CAAA,CAAE,SAAA,EAAW,KAAA,EAAO,IAAA,CAAK,EAAA,EAAI,CAAA;AAAA,sBACxD,GAAA,CAAC,QAAA,EAAA,EAAO,IAAA,EAAK,QAAA,EAAS,IAAA,EAAM,EAAE,aAAA,EAAe,KAAA,EAAO,QAAA,GAAW,CAAA,EAAG,YAAA,EAAY,MAAA,CAAO,UAAU,SAAA,EAAW,EAAA,CAAG,SAAA,EAAW,yBAAA,EAAwB,EAAA,EAC7I,QAAA,EAAA,KAAA,EAAO,QAAA,oBAAY,GAAA,CAAC,MAAA,EAAA,EAAK,aAAA,EAAY,MAAA,EAAO,QAAA,EAAA,QAAA,EAAC,CAAA,EAChD,CAAA;AAAA,sBACA,GAAA,CAAC,UAAK,SAAA,EAAW,EAAA,CAAG,UAAU,WAAA,EAAU,QAAA,EAAS,wBAAA,EAAuB,EAAA,EAAI,QAAA,EAAA,QAAA,EAAS,CAAA;AAAA,sBACrF,GAAA,CAAC,QAAA,EAAA,EAAO,IAAA,EAAK,QAAA,EAAS,IAAA,EAAM,EAAE,aAAA,EAAe,KAAA,EAAO,QAAA,GAAW,CAAA,EAAG,YAAA,EAAY,MAAA,CAAO,UAAU,SAAA,EAAW,EAAA,CAAG,SAAA,EAAW,yBAAA,EAAwB,EAAA,EAC7I,QAAA,EAAA,KAAA,EAAO,QAAA,oBAAY,GAAA,CAAC,MAAA,EAAA,EAAK,aAAA,EAAY,MAAA,EAAO,QAAA,EAAA,GAAA,EAAC,CAAA,EAChD;AAAA,KAAA,EACF,CAAA;AAAA,oBACA,IAAA,CAAC,QAAK,MAAA,EAAO,MAAA,EAAO,QAAQ,KAAA,CAAM,IAAA,EAAM,4BAAyB,EAAA,EAC/D,QAAA,EAAA;AAAA,sBAAA,GAAA,CAAC,OAAA,EAAA,EAAM,MAAK,QAAA,EAAS,IAAA,EAAM,EAAE,WAAA,EAAa,KAAA,EAAO,EAAE,YAAA,EAAc,CAAA;AAAA,sBACjE,GAAA,CAAC,WAAM,IAAA,EAAK,QAAA,EAAS,MAAM,CAAA,CAAE,SAAA,EAAW,KAAA,EAAO,IAAA,CAAK,EAAA,EAAI,CAAA;AAAA,sBACxD,GAAA,CAAC,QAAA,EAAA,EAAO,IAAA,EAAK,QAAA,EAAS,SAAA,EAAW,GAAG,MAAA,EAAQ,qBAAA,EAAoB,EAAA,EAAI,QAAA,EAAA,MAAA,CAAO,MAAA,EAAO;AAAA,KAAA,EACpF;AAAA,GAAA,EACF,CAAA;AAGF,EAAA,IAAI,KAAA,CAAM,YAAY,uBAAO,GAAA,CAAC,QAAiB,SAAA,EAAW,EAAA,CAAG,MAAM,mBAAA,EAAkB,EAAA,EAAI,gBAAM,UAAA,CAAW,IAAA,EAAM,EAAE,QAAA,EAAU,KAAA,EAAO,OAAO,SAAA,EAAW,CAAA,EAAA,EAA/G,IAAA,CAAK,EAA4G,CAAA;AAEvJ,EAAA,4BACG,IAAA,EAAA,EAAG,SAAA,EAAW,EAAA,CAAG,IAAA,EAAM,qBAAkB,EAAA,EACvC,QAAA,EAAA;AAAA,IAAA,SAAA;AAAA,yBACA,KAAA,EAAA,EAAI,SAAA,EAAW,EAAA,CAAG,QAAA,EAAU,0BAAuB,EAAA,EAClD,QAAA,EAAA;AAAA,sBAAA,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAW,EAAA,CAAG,QAAA,EAAU,0BAAuB,EAAA,EAClD,QAAA,EAAA;AAAA,wBAAA,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAW,EAAA,CAAG,QAAA,EAAU,0BAAuB,EAAA,EACjD,QAAA,EAAA;AAAA,UAAA,IAAA,GAAO,KAAK,EAAE,IAAA,EAAM,WAAW,EAAA,CAAG,SAAA,EAAW,UAAU,KAAA,EAAO,OAAA,EAAS,OAAO,CAAA,uBAAK,MAAA,EAAA,EAAK,SAAA,EAAW,GAAG,SAAA,EAAW,yBAAA,EAAwB,IAAI,QAAA,EAAA,KAAA,EAAM,CAAA;AAAA,UACnJ,CAAC,SAAA,CAAU,KAAA,CAAM,KAAK,oBAAI,GAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAW,EAAA,CAAG,OAAA,EAAS,sBAAA,EAAqB,EAAA,EAAI,QAAA,EAAA,KAAA,CAAM,OAAM,CAAA,GAAO;AAAA,SAAA,EACnG,CAAA;AAAA,QACC;AAAA,OAAA,EACH,CAAA;AAAA,MACC;AAAA,KAAA,EACH;AAAA,GAAA,EACF,CAAA;AAEJ","file":"cart.js","sourcesContent":["import { useCallback, useEffect, useRef, useSyncExternalStore } from 'react'\n\n// Estado mínimo del cajón del carrito, fuera de React: cualquier parte de la tienda (cabecera, tarjeta, ficha)\n// lo abre sin pasar props. Es del navegador; en el servidor siempre está cerrado.\n\nlet isOpen = false\nconst listeners = new Set<() => void>()\n\nfunction set(next: boolean) {\n if (isOpen === next) return\n isOpen = next\n for (const l of [...listeners]) l()\n}\n\nconst subscribe = (l: () => void) => {\n listeners.add(l)\n return () => {\n listeners.delete(l)\n }\n}\n\n/** Abre el cajón del carrito desde cualquier parte. */\nexport function openCartDrawer() {\n set(true)\n}\n\n/** Cierra el cajón del carrito. */\nexport function closeCartDrawer() {\n set(false)\n}\n\nexport function toggleCartDrawer() {\n set(!isOpen)\n}\n\nexport function isCartDrawerOpen() {\n return isOpen\n}\n\n/** Estado y acciones del cajón. `open` es `false` en el servidor y en la primera pintura. */\nexport function useCartDrawer() {\n const open = useSyncExternalStore(subscribe, () => isOpen, () => false)\n const toggle = useCallback(() => toggleCartDrawer(), [])\n return { open, openDrawer: openCartDrawer, closeDrawer: closeCartDrawer, toggle }\n}\n\n/**\n * Abre el cajón cuando termina un \"agregar al carrito\". `busy` es verdadero mientras hay un agregado en vuelo\n * (en React Router: algún fetcher con la acción del carrito e intención de agregar). Con `skip` verdadero al\n * terminar (error, agregado silencioso) no se abre.\n */\nexport function useOpenCartDrawerWhenDone(busy: boolean, options: { skip?: boolean } = {}) {\n const was = useRef(false)\n const skip = useRef(false)\n skip.current = !!options.skip\n useEffect(() => {\n if (was.current && !busy && !skip.current) openCartDrawer()\n was.current = busy\n }, [busy])\n}\n\ntype ClickLike = {\n defaultPrevented: boolean\n button: number\n metaKey: boolean\n ctrlKey: boolean\n shiftKey: boolean\n altKey: boolean\n preventDefault: () => void\n}\n\n/**\n * `onClick` para el enlace \"Mi carrito\" de la cabecera: con un clic normal abre el cajón; con clic con\n * modificador, clic central o sin JavaScript el `href` (la página del carrito) sigue funcionando.\n */\nexport function handleCartLinkClick(e: ClickLike) {\n if (e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return\n e.preventDefault()\n openCartDrawer()\n}\n","import { useEffect, useId, type ElementType, type ReactNode } from 'react'\nimport { formatMoney, type Cart, type CartLine, type Money } from '@asteby/storefront'\nimport { useModalDialog } from '../dialog.js'\nimport { useResolvedLocale } from '../locale.js'\nimport { closeCartDrawer, useCartDrawer } from './store.js'\n\nexport type CartDrawerLabels = {\n /** Título; el cajón agrega \" (N)\" con la cantidad de unidades. */\n title: string\n /** Texto accesible del botón de cierre. */\n close: string\n empty: string\n emptyHint?: string\n /** Texto del enlace \"seguir comprando\" del estado vacío (se muestra con `paths.browse`). */\n browse?: string\n subtotal: string\n /** Nota bajo el subtotal. */\n note?: string\n checkout: string\n viewCart: string\n remove: string\n increase: string\n decrease: string\n}\n\nexport type CartDrawerPaths = {\n /** Página del carrito: acción de las mutaciones y destino de \"ver carrito\". */\n cart: string\n checkout: string\n browse?: string\n /** Enlace de la línea al producto; sin esto el título no es enlace. */\n product?: (handle: string, variantId: string) => string\n}\n\n/** Contrato de la acción de ruta del carrito de la tienda (nombres de campo e intenciones). */\nexport type CartDrawerFormContract = {\n intentField?: string\n lineField?: string\n quantityField?: string\n updateIntent?: string\n removeIntent?: string\n}\n\nexport type CartDrawerShipping = {\n /** Mensaje ya calculado por la tienda con lo que manda OPS. */\n message: ReactNode\n /** 0..100 */\n percent: number\n reached?: boolean\n progressLabel: string\n}\n\nexport type CartDrawerLineForm = {\n /** Componente de formulario (`'form'`, o `fetcher.Form` del router para mejorar sin recargar). */\n Form: ElementType\n /** Datos en vuelo del formulario, para pintar la cantidad de forma optimista. */\n formData?: FormData | null | undefined\n}\n\nexport type CartDrawerLinkProps = { href: string; className?: string | undefined; children: ReactNode; onClick?: () => void }\n\nexport type CartDrawerSlot =\n | 'dialog' | 'panel' | 'header' | 'title' | 'close' | 'empty' | 'body' | 'shipping' | 'bar' | 'barFill' | 'lines' | 'line'\n | 'image' | 'lineTitle' | 'variant' | 'price' | 'qty' | 'qtyButton' | 'qtyValue' | 'remove' | 'upsell' | 'footer' | 'subtotal'\n | 'note' | 'actions' | 'checkout' | 'viewCart' | 'browse' | 'emptyTitle' | 'emptyHint' | 'shippingMessage' | 'lineMain'\n | 'lineHead' | 'lineText' | 'controls' | 'upsellTitle' | 'subtotalLabel' | 'subtotalValue'\n\nexport type CartDrawerIcons = { close?: ReactNode; decrease?: ReactNode; increase?: ReactNode; shipping?: ReactNode; shippingReached?: ReactNode }\n\nexport type CartDrawerProps = {\n /** `cart.cost` y cada línea vienen de OPS; el cajón no calcula nada. */\n cart: Cart | null | undefined\n labels: CartDrawerLabels\n paths: CartDrawerPaths\n form?: CartDrawerFormContract\n /** Hook que da el formulario de cada línea. Por omisión `<form method=\"post\">` (funciona sin JavaScript). */\n useLineForm?: () => CartDrawerLineForm\n shipping?: CartDrawerShipping | null\n /** Aviso fijo cuando no hay barra de envío (p. ej. \"Envío gratis\"). */\n notice?: ReactNode\n /** Sección \"podría interesarte\" (la tienda decide qué mostrar). */\n upsell?: ReactNode\n upsellTitle?: string\n renderLine?: (line: CartLine, defaults: { controls: ReactNode; price: ReactNode; image: ReactNode }) => ReactNode\n renderImage?: (line: CartLine) => ReactNode\n renderMoney?: (money: Money) => ReactNode\n renderLink?: (props: CartDrawerLinkProps) => ReactNode\n /** Omite el variante cuando es el valor por defecto de OPS (\"Default Title\"). */\n isDefaultVariantTitle?: (title: string) => boolean\n icons?: CartDrawerIcons\n /** Clases por pieza (además de los `data-sf-cart-*`), para tiendas con Tailwind. */\n classNames?: Partial<Record<CartDrawerSlot, string>>\n locale?: string\n /** Se llama al abrir/cerrar por cualquier vía. */\n onOpenChange?: (open: boolean) => void\n}\n\nconst DEFAULT_FORM: Required<CartDrawerFormContract> = {\n intentField: 'intent',\n lineField: 'line_id',\n quantityField: 'quantity',\n updateIntent: 'update',\n removeIntent: 'remove',\n}\n\nconst useNativeForm = (): CartDrawerLineForm => ({ Form: 'form' })\n\nconst defaultIsDefaultVariant = (t: string) => /^default title$/i.test(t.trim())\n\nfunction useBodyScrollLock(locked: boolean) {\n useEffect(() => {\n if (!locked || typeof document === 'undefined') return\n const el = document.documentElement\n const prev = el.style.overflow\n el.style.overflow = 'hidden'\n return () => {\n el.style.overflow = prev\n }\n }, [locked])\n}\n\n/**\n * Cajón lateral del carrito (\"Tu carrito (N)\"). Sin textos ni marca: todo llega por props.\n * Se monta una vez (en la raíz) y se abre con `openCartDrawer()` / `useCartDrawer()`.\n * `<dialog>` modal: `role=\"dialog\"`, `aria-modal`, foco atrapado y devuelto al abridor, Esc y clic fuera cierran,\n * el fondo no hace scroll. Las mutaciones usan la acción de ruta de la tienda (formularios nativos que\n * funcionan sin JavaScript, o el `Form` de un fetcher con `useLineForm`). Estilos: `@asteby/storefront-react/cart.css`\n * (tokens de la tienda) y/o `classNames`.\n */\nexport function CartDrawer(props: CartDrawerProps) {\n const { cart, labels, paths, shipping, notice, upsell, upsellTitle, icons, classNames: cx = {}, onOpenChange } = props\n const locale = useResolvedLocale(props.locale)\n const { open } = useCartDrawer()\n const titleId = useId()\n const upsellId = useId()\n const { ref, onBackdropClick } = useModalDialog({ open, onClose: () => closeCartDrawer() })\n useBodyScrollLock(open)\n useEffect(() => {\n onOpenChange?.(open)\n }, [open]) // eslint-disable-line react-hooks/exhaustive-deps\n\n if (!open) return null\n\n const money = props.renderMoney ?? ((m: Money) => formatMoney(m, locale))\n const link = props.renderLink ?? (({ href, className, children, onClick }: CartDrawerLinkProps) => (\n <a href={href} className={className} onClick={onClick}>{children}</a>\n ))\n const count = cart?.total_quantity ?? 0\n const lines = cart?.lines ?? []\n const empty = !cart || lines.length === 0\n const close = () => closeCartDrawer()\n\n return (\n <dialog\n ref={ref}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={titleId}\n onClick={onBackdropClick}\n onClose={close}\n className={cx.dialog}\n data-sf-cart-drawer=\"\"\n >\n <div className={cx.panel} data-sf-cart-panel=\"\">\n <div className={cx.header} data-sf-cart-header=\"\">\n <h2 id={titleId} tabIndex={-1} className={cx.title} data-sf-cart-title=\"\">\n {labels.title}{count > 0 ? ` (${count})` : ''}\n </h2>\n <button type=\"button\" onClick={close} aria-label={labels.close} className={cx.close} data-sf-cart-close=\"\" data-sf-autofocus=\"\">\n {icons?.close ?? <span aria-hidden=\"true\">✕</span>}\n </button>\n </div>\n\n {empty ? (\n <div className={cx.empty} data-sf-cart-empty=\"\">\n <p className={cx.emptyTitle} data-sf-cart-empty-title=\"\">{labels.empty}</p>\n {labels.emptyHint ? <p className={cx.emptyHint} data-sf-cart-empty-hint=\"\">{labels.emptyHint}</p> : null}\n {paths.browse && labels.browse ? link({ href: paths.browse, className: cx.browse, children: labels.browse, onClick: close }) : null}\n </div>\n ) : (\n <>\n <div className={cx.body} data-sf-cart-body=\"\">\n {shipping ? (\n <div className={cx.shipping} data-sf-cart-shipping=\"\" data-reached={shipping.reached || undefined}>\n <p className={cx.shippingMessage}>\n {shipping.reached ? icons?.shippingReached : icons?.shipping}\n {shipping.message}\n </p>\n <div\n role=\"progressbar\"\n aria-valuemin={0}\n aria-valuemax={100}\n aria-valuenow={Math.round(shipping.percent)}\n aria-label={shipping.progressLabel}\n className={cx.bar}\n data-sf-cart-bar=\"\"\n >\n <div className={cx.barFill} data-sf-cart-bar-fill=\"\" style={{ width: `${Math.max(0, Math.min(100, shipping.percent))}%` }} />\n </div>\n </div>\n ) : notice ? (\n <p className={[cx.shipping, cx.shippingMessage].filter(Boolean).join(\" \") || undefined} data-sf-cart-shipping=\"\" data-notice=\"\">{icons?.shippingReached}{notice}</p>\n ) : null}\n\n <ul className={cx.lines} data-sf-cart-lines=\"\">\n {lines.map((line) => (\n <DrawerLine key={line.id} line={line} props={props} money={money} link={link} close={close} />\n ))}\n </ul>\n\n {upsell ? (\n <section className={cx.upsell} data-sf-cart-upsell=\"\" aria-labelledby={upsellTitle ? upsellId : undefined}>\n {upsellTitle ? <h3 id={upsellId} className={cx.upsellTitle}>{upsellTitle}</h3> : null}\n {upsell}\n </section>\n ) : null}\n </div>\n\n <div className={cx.footer} data-sf-cart-footer=\"\">\n <div className={cx.subtotal} data-sf-cart-subtotal=\"\">\n <span className={cx.subtotalLabel}>{labels.subtotal}</span>\n <span className={cx.subtotalValue} data-sf-cart-subtotal-value=\"\">{cart ? money(cart.cost.subtotal) : null}</span>\n </div>\n {labels.note ? <p className={cx.note} data-sf-cart-note=\"\">{labels.note}</p> : null}\n <div className={cx.actions} data-sf-cart-actions=\"\">\n {link({ href: paths.checkout, className: cx.checkout, children: labels.checkout, onClick: close })}\n {link({ href: paths.cart, className: cx.viewCart, children: labels.viewCart, onClick: close })}\n </div>\n </div>\n </>\n )}\n </div>\n </dialog>\n )\n}\n\nfunction DrawerLine({\n line,\n props,\n money,\n link,\n close,\n}: {\n line: CartLine\n props: CartDrawerProps\n money: (m: Money) => ReactNode\n link: (p: CartDrawerLinkProps) => ReactNode\n close: () => void\n}) {\n const { labels, paths, icons, classNames: cx = {} } = props\n const f = { ...DEFAULT_FORM, ...props.form }\n const { Form, formData } = (props.useLineForm ?? useNativeForm)()\n const quantity = formData ? Number(formData.get(f.quantityField) ?? 0) : line.quantity\n if (formData && (formData.get(f.intentField) === f.removeIntent || quantity === 0)) return null\n\n const merch = line.merchandise\n const isDefault = props.isDefaultVariantTitle ?? defaultIsDefaultVariant\n const title = merch.product.title\n const href = paths.product?.(merch.product.handle, merch.id)\n const img = merch.image ?? merch.product.featured_image\n const image = props.renderImage ? props.renderImage(line) : img ? (\n <img src={img.url} alt={img.alt ?? ''} width={80} height={80} loading=\"lazy\" decoding=\"async\" />\n ) : null\n\n const imageNode = <span className={cx.image} data-sf-cart-image=\"\">{image}</span>\n const price = <span className={cx.price} data-sf-cart-price=\"\">{money(line.cost.total)}</span>\n const controls = (\n <div className={cx.controls} data-sf-cart-controls=\"\">\n <Form method=\"post\" action={paths.cart} className={cx.qty} data-sf-cart-qty=\"\">\n <input type=\"hidden\" name={f.intentField} value={f.updateIntent} />\n <input type=\"hidden\" name={f.lineField} value={line.id} />\n <button type=\"submit\" name={f.quantityField} value={quantity - 1} aria-label={labels.decrease} className={cx.qtyButton} data-sf-cart-qty-button=\"\">\n {icons?.decrease ?? <span aria-hidden=\"true\">−</span>}\n </button>\n <span className={cx.qtyValue} aria-live=\"polite\" data-sf-cart-qty-value=\"\">{quantity}</span>\n <button type=\"submit\" name={f.quantityField} value={quantity + 1} aria-label={labels.increase} className={cx.qtyButton} data-sf-cart-qty-button=\"\">\n {icons?.increase ?? <span aria-hidden=\"true\">+</span>}\n </button>\n </Form>\n <Form method=\"post\" action={paths.cart} data-sf-cart-remove-form=\"\">\n <input type=\"hidden\" name={f.intentField} value={f.removeIntent} />\n <input type=\"hidden\" name={f.lineField} value={line.id} />\n <button type=\"submit\" className={cx.remove} data-sf-cart-remove=\"\">{labels.remove}</button>\n </Form>\n </div>\n )\n\n if (props.renderLine) return <li key={line.id} className={cx.line} data-sf-cart-line=\"\">{props.renderLine(line, { controls, price, image: imageNode })}</li>\n\n return (\n <li className={cx.line} data-sf-cart-line=\"\">\n {imageNode}\n <div className={cx.lineMain} data-sf-cart-line-main=\"\">\n <div className={cx.lineHead} data-sf-cart-line-head=\"\">\n <div className={cx.lineText} data-sf-cart-line-text=\"\">\n {href ? link({ href, className: cx.lineTitle, children: title, onClick: close }) : <span className={cx.lineTitle} data-sf-cart-line-title=\"\">{title}</span>}\n {!isDefault(merch.title) ? <p className={cx.variant} data-sf-cart-variant=\"\">{merch.title}</p> : null}\n </div>\n {price}\n </div>\n {controls}\n </div>\n </li>\n )\n}\n"]}