@asteby/storefront-react 0.19.0 → 0.21.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.
Files changed (51) hide show
  1. package/README.md +41 -0
  2. package/dist/account-server.cjs +405 -7
  3. package/dist/account-server.cjs.map +1 -1
  4. package/dist/account-server.d.cts +41 -1
  5. package/dist/account-server.d.ts +41 -1
  6. package/dist/account-server.js +73 -3
  7. package/dist/account-server.js.map +1 -1
  8. package/dist/account.cjs +1663 -4
  9. package/dist/account.cjs.map +1 -1
  10. package/dist/account.css +362 -0
  11. package/dist/account.d.cts +499 -3
  12. package/dist/account.d.ts +499 -3
  13. package/dist/account.js +1102 -2
  14. package/dist/account.js.map +1 -1
  15. package/dist/cart.cjs +125 -2
  16. package/dist/cart.cjs.map +1 -1
  17. package/dist/cart.css +145 -2
  18. package/dist/cart.js +4 -2
  19. package/dist/cart.js.map +1 -1
  20. package/dist/checkout.cjs +129 -1
  21. package/dist/checkout.cjs.map +1 -1
  22. package/dist/checkout.css +142 -0
  23. package/dist/checkout.d.cts +3 -1
  24. package/dist/checkout.d.ts +3 -1
  25. package/dist/checkout.js +10 -3
  26. package/dist/checkout.js.map +1 -1
  27. package/dist/chunk-PPQEUTXJ.js +260 -0
  28. package/dist/chunk-PPQEUTXJ.js.map +1 -0
  29. package/dist/chunk-TXEYBDFG.js +182 -0
  30. package/dist/chunk-TXEYBDFG.js.map +1 -0
  31. package/dist/chunk-VHEY5AU3.js +152 -0
  32. package/dist/chunk-VHEY5AU3.js.map +1 -0
  33. package/dist/fiscal.d.cts +34 -34
  34. package/dist/fiscal.d.ts +34 -34
  35. package/dist/fiscal.js +4 -258
  36. package/dist/fiscal.js.map +1 -1
  37. package/dist/index.js +1 -1
  38. package/dist/payments.css +15 -14
  39. package/dist/payments.js +2 -2
  40. package/dist/product-preview-DRtLWGLS.d.cts +85 -0
  41. package/dist/product-preview-DaICr4f2.d.ts +85 -0
  42. package/dist/ui.cjs +202 -0
  43. package/dist/ui.cjs.map +1 -1
  44. package/dist/ui.css +142 -0
  45. package/dist/ui.d.cts +3 -0
  46. package/dist/ui.d.ts +3 -0
  47. package/dist/ui.js +3 -1
  48. package/dist/ui.js.map +1 -1
  49. package/dist/voucher-document-SFWJCLTN.js +124 -0
  50. package/dist/voucher-document-SFWJCLTN.js.map +1 -0
  51. package/package.json +2 -2
package/dist/payments.css CHANGED
@@ -89,16 +89,20 @@
89
89
  }
90
90
 
91
91
  /*
92
- * 3DS challenge (`<PaymentChallenge>`): a near full-screen modal. Colors are fixed on purpose: the bank page inside
92
+ * 3DS challenge (`<PaymentChallenge>`): a square-ish centered modal (36rem wide, max min(90dvh, 40rem) tall). Colors are fixed on purpose: the bank page inside
93
93
  * the iframe is always white, and the dialog often lives in a themed (dark) panel. Override with a more specific rule.
94
94
  */
95
95
  dialog[data-sf-challenge] {
96
96
  margin: auto;
97
97
  box-sizing: border-box;
98
- width: min(60rem, calc(100% - 2rem));
98
+ inset: 0;
99
+ width: min(36rem, 100% - 2rem);
99
100
  max-width: none;
100
- max-height: 94dvh;
101
- overflow: auto;
101
+ max-height: min(90dvh, 40rem);
102
+ display: flex;
103
+ flex-direction: column;
104
+ /* Solo el cuerpo (el iframe) scrollea; el diálogo no. */
105
+ overflow: hidden;
102
106
  border: 1px solid #c9d2ee;
103
107
  border-radius: 0.75rem;
104
108
  background: #ffffff;
@@ -120,7 +124,10 @@ dialog[data-sf-challenge] h2:focus {
120
124
  }
121
125
  dialog[data-sf-challenge] [data-sf-challenge-frame] {
122
126
  display: block;
123
- height: min(70dvh, 40rem);
127
+ flex: 1 1 auto;
128
+ min-height: 16rem;
129
+ width: 100%;
130
+ overflow: auto;
124
131
  background: #ffffff;
125
132
  }
126
133
  dialog[data-sf-challenge] [data-sf-challenge-escape] {
@@ -139,16 +146,10 @@ dialog[data-sf-challenge] [data-sf-challenge-escape] button {
139
146
  border-radius: 0.5rem;
140
147
  font-weight: 600;
141
148
  }
142
- @media (max-width: 40rem) {
149
+ @media (max-width: 30rem) {
143
150
  dialog[data-sf-challenge] {
144
- width: 100%;
145
- height: 100dvh;
146
- max-height: 100dvh;
147
- border-radius: 0;
148
- border: 0;
151
+ width: calc(100% - 2rem);
152
+ max-height: calc(100dvh - 2rem);
149
153
  padding: 1rem;
150
154
  }
151
- dialog[data-sf-challenge] [data-sf-challenge-frame] {
152
- height: calc(100dvh - 8rem);
153
- }
154
155
  }
package/dist/payments.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import { isSafeCssValue } from './chunk-672OTUM2.js';
2
2
  import { useModalDialog } from './chunk-ZBH7JYU4.js';
3
3
  import { findBrand, BrandMark } from './chunk-JNUJGPJC.js';
4
- import { useResolvedLocale } from './chunk-T52H5HL3.js';
5
- import './chunk-DQBL2DFJ.js';
6
4
  import { paymentRecoveryMessages } from './chunk-MNLROALF.js';
7
5
  export { buildPayChoices, cashProviderData, checkoutToAttempt, choiceValue, parseChoice, payErrorResponse, paymentCsp, paymentCspHeaderName, paymentCspHeaders, paymentCspMode, paymentNonce, paymentNonceFromHeaders, paymentRecoveryMessages, safeHttpStatus, voucherRows } from './chunk-MNLROALF.js';
6
+ import { useResolvedLocale } from './chunk-T52H5HL3.js';
7
+ import './chunk-DQBL2DFJ.js';
8
8
  import { forwardRef, useImperativeHandle, useRef, useEffect, useMemo, useId, useState, useCallback, useSyncExternalStore } from 'react';
9
9
  import { paymentMessages, formatMoney, generateIdempotencyKey, paymentOutcome, paymentErrorView, formatVoucherExpiry, classifyPaymentError } from '@asteby/storefront';
10
10
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
@@ -0,0 +1,85 @@
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+ import { a as ImageLoader } from './components-B5XhSH-m.cjs';
4
+
5
+ /** First letter(s) of the name for the fallback: two initials from two words, one otherwise. Empty when nothing usable. */
6
+ declare function productInitials(name: string | null | undefined): string;
7
+ type ProductThumbClassNames = Partial<Record<'root' | 'img' | 'fallback' | 'badge', string>>;
8
+ type ProductThumbImage = {
9
+ url: string;
10
+ alt?: string | null;
11
+ width?: number;
12
+ height?: number;
13
+ };
14
+ type ProductThumbProps = {
15
+ /** Picture of the product or variant. `null`/missing: shows the fallback. */
16
+ image?: ProductThumbImage | null;
17
+ /** Name of the product: alt of the picture (unless `decorative`) and source of the fallback initial. */
18
+ name?: string | null;
19
+ /** Side in px. Default 56. */
20
+ size?: number;
21
+ /** Aspect ratio of the box, `w/h`. Default `1/1`. */
22
+ ratio?: string;
23
+ /** `contain` (default) never crops; `cover` fills the box. */
24
+ fit?: 'contain' | 'cover';
25
+ /** Quantity badge, shown when greater than 1. */
26
+ quantity?: number;
27
+ /** The name sits next to it as text: the picture has `alt=""` and the fallback is hidden from screen readers. Default false. */
28
+ decorative?: boolean;
29
+ /** Slight zoom on hover (honours `prefers-reduced-motion`). Default true. */
30
+ zoom?: boolean;
31
+ /** Above the fold: eager load. */
32
+ priority?: boolean;
33
+ sizes?: string;
34
+ loader?: ImageLoader | null;
35
+ className?: string;
36
+ classNames?: ProductThumbClassNames;
37
+ };
38
+ /** The picture of a product with fixed ratio, shimmer, lazy loading and a fallback that always looks designed. */
39
+ declare function ProductThumb({ image, name, size, ratio, fit, quantity, decorative, zoom, priority, sizes, loader, className, classNames: cx, }: ProductThumbProps): react.JSX.Element;
40
+ type ProductPreviewClassNames = Partial<Record<'root' | 'media' | 'body' | 'name' | 'variant' | 'meta' | 'sku' | 'qty' | 'price' | 'total' | 'unit', string>> & {
41
+ thumb?: ProductThumbClassNames;
42
+ };
43
+ type ProductPreviewProps = Pick<ProductThumbProps, 'image' | 'size' | 'ratio' | 'fit' | 'zoom' | 'priority' | 'sizes' | 'loader'> & {
44
+ name: string;
45
+ /** "Rojo / L". Omit for default variants. */
46
+ variant?: string | null;
47
+ sku?: string | null;
48
+ /** Quantity: a badge on the picture (> 1) and "Cantidad N" under the name. */
49
+ quantity?: number;
50
+ /** Unit price, already formatted. Shown next to the quantity when it is greater than 1. */
51
+ unitPrice?: ReactNode;
52
+ /** Line total, already formatted: aligned right, the strongest number. */
53
+ total?: ReactNode;
54
+ /** Link on the name (product page). */
55
+ href?: string | null;
56
+ /** Rendered under the text (status, note, actions). */
57
+ children?: ReactNode;
58
+ /** Text of the quantity for screen readers and the meta line. Default `Cantidad {n}`. */
59
+ quantityLabel?: string;
60
+ /** Name shown when the line has none (product deleted). Default `Producto no disponible`. */
61
+ unavailableLabel?: string;
62
+ className?: string;
63
+ classNames?: ProductPreviewClassNames;
64
+ };
65
+ /**
66
+ * A product line: picture, name (2 lines), variant, SKU, quantity and prices with a clear hierarchy. Degrades to a
67
+ * designed placeholder when the product has no image or no longer exists (empty name).
68
+ */
69
+ declare function ProductPreview({ image, name, variant, sku, quantity, unitPrice, total, href, children, size, ratio, fit, zoom, priority, sizes, loader, quantityLabel, unavailableLabel, className, classNames: cx, }: ProductPreviewProps): react.JSX.Element;
70
+ type ProductThumbStackProps = {
71
+ items: ReadonlyArray<{
72
+ image?: ProductThumbImage | null;
73
+ name?: string | null;
74
+ }>;
75
+ /** Thumbs shown before "+N". Default 4. */
76
+ max?: number;
77
+ size?: number;
78
+ /** Screen reader text for the hidden remainder, `{n}` = how many. Default `y {n} más`. */
79
+ moreLabel?: string;
80
+ className?: string;
81
+ };
82
+ /** Overlapped thumbs with a "+N" counter: the products of an order at a glance. */
83
+ declare function ProductThumbStack({ items, max, size, moreLabel, className }: ProductThumbStackProps): react.JSX.Element | null;
84
+
85
+ export { ProductPreview as P, type ProductPreviewClassNames as a, type ProductPreviewProps as b, ProductThumb as c, type ProductThumbClassNames as d, type ProductThumbImage as e, type ProductThumbProps as f, ProductThumbStack as g, type ProductThumbStackProps as h, productInitials as p };
@@ -0,0 +1,85 @@
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+ import { a as ImageLoader } from './components-B5XhSH-m.js';
4
+
5
+ /** First letter(s) of the name for the fallback: two initials from two words, one otherwise. Empty when nothing usable. */
6
+ declare function productInitials(name: string | null | undefined): string;
7
+ type ProductThumbClassNames = Partial<Record<'root' | 'img' | 'fallback' | 'badge', string>>;
8
+ type ProductThumbImage = {
9
+ url: string;
10
+ alt?: string | null;
11
+ width?: number;
12
+ height?: number;
13
+ };
14
+ type ProductThumbProps = {
15
+ /** Picture of the product or variant. `null`/missing: shows the fallback. */
16
+ image?: ProductThumbImage | null;
17
+ /** Name of the product: alt of the picture (unless `decorative`) and source of the fallback initial. */
18
+ name?: string | null;
19
+ /** Side in px. Default 56. */
20
+ size?: number;
21
+ /** Aspect ratio of the box, `w/h`. Default `1/1`. */
22
+ ratio?: string;
23
+ /** `contain` (default) never crops; `cover` fills the box. */
24
+ fit?: 'contain' | 'cover';
25
+ /** Quantity badge, shown when greater than 1. */
26
+ quantity?: number;
27
+ /** The name sits next to it as text: the picture has `alt=""` and the fallback is hidden from screen readers. Default false. */
28
+ decorative?: boolean;
29
+ /** Slight zoom on hover (honours `prefers-reduced-motion`). Default true. */
30
+ zoom?: boolean;
31
+ /** Above the fold: eager load. */
32
+ priority?: boolean;
33
+ sizes?: string;
34
+ loader?: ImageLoader | null;
35
+ className?: string;
36
+ classNames?: ProductThumbClassNames;
37
+ };
38
+ /** The picture of a product with fixed ratio, shimmer, lazy loading and a fallback that always looks designed. */
39
+ declare function ProductThumb({ image, name, size, ratio, fit, quantity, decorative, zoom, priority, sizes, loader, className, classNames: cx, }: ProductThumbProps): react.JSX.Element;
40
+ type ProductPreviewClassNames = Partial<Record<'root' | 'media' | 'body' | 'name' | 'variant' | 'meta' | 'sku' | 'qty' | 'price' | 'total' | 'unit', string>> & {
41
+ thumb?: ProductThumbClassNames;
42
+ };
43
+ type ProductPreviewProps = Pick<ProductThumbProps, 'image' | 'size' | 'ratio' | 'fit' | 'zoom' | 'priority' | 'sizes' | 'loader'> & {
44
+ name: string;
45
+ /** "Rojo / L". Omit for default variants. */
46
+ variant?: string | null;
47
+ sku?: string | null;
48
+ /** Quantity: a badge on the picture (> 1) and "Cantidad N" under the name. */
49
+ quantity?: number;
50
+ /** Unit price, already formatted. Shown next to the quantity when it is greater than 1. */
51
+ unitPrice?: ReactNode;
52
+ /** Line total, already formatted: aligned right, the strongest number. */
53
+ total?: ReactNode;
54
+ /** Link on the name (product page). */
55
+ href?: string | null;
56
+ /** Rendered under the text (status, note, actions). */
57
+ children?: ReactNode;
58
+ /** Text of the quantity for screen readers and the meta line. Default `Cantidad {n}`. */
59
+ quantityLabel?: string;
60
+ /** Name shown when the line has none (product deleted). Default `Producto no disponible`. */
61
+ unavailableLabel?: string;
62
+ className?: string;
63
+ classNames?: ProductPreviewClassNames;
64
+ };
65
+ /**
66
+ * A product line: picture, name (2 lines), variant, SKU, quantity and prices with a clear hierarchy. Degrades to a
67
+ * designed placeholder when the product has no image or no longer exists (empty name).
68
+ */
69
+ declare function ProductPreview({ image, name, variant, sku, quantity, unitPrice, total, href, children, size, ratio, fit, zoom, priority, sizes, loader, quantityLabel, unavailableLabel, className, classNames: cx, }: ProductPreviewProps): react.JSX.Element;
70
+ type ProductThumbStackProps = {
71
+ items: ReadonlyArray<{
72
+ image?: ProductThumbImage | null;
73
+ name?: string | null;
74
+ }>;
75
+ /** Thumbs shown before "+N". Default 4. */
76
+ max?: number;
77
+ size?: number;
78
+ /** Screen reader text for the hidden remainder, `{n}` = how many. Default `y {n} más`. */
79
+ moreLabel?: string;
80
+ className?: string;
81
+ };
82
+ /** Overlapped thumbs with a "+N" counter: the products of an order at a glance. */
83
+ declare function ProductThumbStack({ items, max, size, moreLabel, className }: ProductThumbStackProps): react.JSX.Element | null;
84
+
85
+ export { ProductPreview as P, type ProductPreviewClassNames as a, type ProductPreviewProps as b, ProductThumb as c, type ProductThumbClassNames as d, type ProductThumbImage as e, type ProductThumbProps as f, ProductThumbStack as g, type ProductThumbStackProps as h, productInitials as p };
package/dist/ui.cjs CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  var react = require('react');
4
4
  var jsxRuntime = require('react/jsx-runtime');
5
+ require('@asteby/storefront');
5
6
 
6
7
  // src/ui/select.tsx
7
8
  var Chevron = ({ open = false, floating = false }) => /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", "data-select-chevron": floating ? "floating" : "", "data-open": open || void 0, children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m6 9 6 6 6-6" }) });
@@ -371,8 +372,208 @@ function InstallApp({ className = "", label = "Instalar app", iosHint = "Para in
371
372
  }
372
373
  return ios ? /* @__PURE__ */ jsxRuntime.jsx("p", { className, children: iosHint }) : null;
373
374
  }
375
+ var DEFAULT_IMAGE_WIDTHS = [320, 480, 640, 768, 1024, 1280, 1600];
376
+ var defaultImageLoader = (src, width, height) => {
377
+ try {
378
+ const isAbsolute = /^[a-z][a-z0-9+.-]*:/i.test(src);
379
+ const url = new URL(src, "http://relative.invalid");
380
+ url.searchParams.set("width", String(width));
381
+ if (height) url.searchParams.set("height", String(height));
382
+ return isAbsolute ? url.toString() : url.pathname + url.search + url.hash;
383
+ } catch {
384
+ return src;
385
+ }
386
+ };
387
+ function parseRatio(ratio) {
388
+ if (!ratio) return null;
389
+ const [w, h] = ratio.split(/[/:]/).map((n) => Number.parseFloat(n));
390
+ return w && h ? w / h : null;
391
+ }
392
+ function Image({
393
+ data,
394
+ sizes = "100vw",
395
+ widths = DEFAULT_IMAGE_WIDTHS,
396
+ loader = defaultImageLoader,
397
+ priority = false,
398
+ aspectRatio,
399
+ alt,
400
+ loading,
401
+ decoding,
402
+ ...rest
403
+ }) {
404
+ const ratio = parseRatio(aspectRatio) ?? (data.width && data.height ? data.width / data.height : null);
405
+ let candidates = [...widths].sort((a, b) => a - b);
406
+ if (data.width) {
407
+ const capped = candidates.filter((w) => w <= data.width);
408
+ candidates = capped.length ? capped : [data.width];
409
+ }
410
+ const srcSet = loader ? candidates.map((w) => `${loader(data.url, w, ratio ? Math.round(w / ratio) : void 0)} ${w}w`).join(", ") : void 0;
411
+ const width = data.width ?? candidates[candidates.length - 1];
412
+ const height = data.height ?? (ratio && width ? Math.round(width / ratio) : void 0);
413
+ return /* @__PURE__ */ jsxRuntime.jsx(
414
+ "img",
415
+ {
416
+ src: data.url,
417
+ alt: alt ?? data.alt ?? "",
418
+ ...srcSet ? { srcSet, sizes } : {},
419
+ ...width ? { width } : {},
420
+ ...height ? { height } : {},
421
+ loading: loading ?? (priority ? "eager" : "lazy"),
422
+ decoding: decoding ?? (priority ? "auto" : "async"),
423
+ ...priority ? { fetchPriority: "high" } : {},
424
+ ...rest
425
+ }
426
+ );
427
+ }
428
+ function productInitials(name) {
429
+ const words = (name ?? "").normalize("NFKC").split(/[\s\-_/.,()]+/).map((w) => Array.from(w).find((c) => /\p{L}|\p{N}/u.test(c)) ?? "").filter(Boolean);
430
+ if (words.length === 0) return "";
431
+ return (words.length > 1 ? words[0] + words[1] : words[0]).toLocaleUpperCase();
432
+ }
433
+ function ProductThumb({
434
+ image,
435
+ name,
436
+ size = 56,
437
+ ratio = "1/1",
438
+ fit = "contain",
439
+ quantity,
440
+ decorative = false,
441
+ zoom = true,
442
+ priority = false,
443
+ sizes,
444
+ loader,
445
+ className,
446
+ classNames: cx = {}
447
+ }) {
448
+ const url = image?.url || null;
449
+ const [state, setState] = react.useState(url ? "loading" : "fallback");
450
+ const box = react.useRef(null);
451
+ react.useEffect(() => {
452
+ const img = box.current?.querySelector("img");
453
+ setState(!url ? "fallback" : img?.complete ? img.naturalWidth > 0 ? "loaded" : "fallback" : "loading");
454
+ }, [url]);
455
+ const label = name?.trim() || "";
456
+ const initials = productInitials(label);
457
+ const showBadge = typeof quantity === "number" && quantity > 1;
458
+ const a11y = state === "fallback" && !decorative && label ? { role: "img", "aria-label": label } : { "aria-hidden": decorative || state === "fallback" ? true : void 0 };
459
+ return /* @__PURE__ */ jsxRuntime.jsxs(
460
+ "span",
461
+ {
462
+ ref: box,
463
+ className: [cx.root, className].filter(Boolean).join(" ") || void 0,
464
+ style: { ["--sf-preview-size"]: `${size}px`, ["--sf-preview-ratio"]: ratio.replace(":", "/") },
465
+ "data-sf-thumb": "",
466
+ "data-state": state,
467
+ "data-fit": fit,
468
+ "data-zoom": zoom || void 0,
469
+ ...a11y,
470
+ children: [
471
+ url && state !== "fallback" ? /* @__PURE__ */ jsxRuntime.jsx(
472
+ Image,
473
+ {
474
+ data: { url, alt: image?.alt ?? null, ...image?.width ? { width: image.width } : {}, ...image?.height ? { height: image.height } : {} },
475
+ alt: decorative ? "" : image?.alt ?? label,
476
+ aspectRatio: ratio,
477
+ sizes: sizes ?? `${size * 2}px`,
478
+ widths: [Math.round(size), Math.round(size * 2), Math.round(size * 3)],
479
+ ...loader !== void 0 ? { loader } : {},
480
+ priority,
481
+ className: cx.img,
482
+ onLoad: () => setState("loaded"),
483
+ onError: () => setState("fallback")
484
+ }
485
+ ) : null,
486
+ state === "fallback" ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: cx.fallback, "data-sf-thumb-fallback": "", "aria-hidden": "true", children: initials ? /* @__PURE__ */ jsxRuntime.jsx("span", { "data-sf-thumb-initial": "", children: initials }) : /* @__PURE__ */ jsxRuntime.jsx(PackageGlyph, {}) }) : null,
487
+ showBadge ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: cx.badge, "data-sf-thumb-qty": "", "aria-hidden": "true", children: quantity }) : null
488
+ ]
489
+ }
490
+ );
491
+ }
492
+ function PackageGlyph() {
493
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 24 24", width: "45%", height: "45%", fill: "none", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", strokeLinejoin: "round", focusable: "false", children: [
494
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M21 8 12 3 3 8v8l9 5 9-5V8Z" }),
495
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m3 8 9 5 9-5M12 13v8" })
496
+ ] });
497
+ }
498
+ function ProductPreview({
499
+ image,
500
+ name,
501
+ variant,
502
+ sku,
503
+ quantity,
504
+ unitPrice,
505
+ total,
506
+ href,
507
+ children,
508
+ size = 64,
509
+ ratio,
510
+ fit,
511
+ zoom,
512
+ priority,
513
+ sizes,
514
+ loader,
515
+ quantityLabel,
516
+ unavailableLabel = "Producto no disponible",
517
+ className,
518
+ classNames: cx = {}
519
+ }) {
520
+ const missing = !name.trim();
521
+ const title = missing ? unavailableLabel : name;
522
+ const hasQty = typeof quantity === "number" && Number.isFinite(quantity);
523
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: [cx.root, className].filter(Boolean).join(" ") || void 0, "data-sf-preview": "", "data-missing": missing || void 0, children: [
524
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: cx.media, "data-sf-preview-media": "", children: /* @__PURE__ */ jsxRuntime.jsx(
525
+ ProductThumb,
526
+ {
527
+ image,
528
+ name: missing ? null : name,
529
+ size,
530
+ decorative: true,
531
+ ...ratio ? { ratio } : {},
532
+ ...fit ? { fit } : {},
533
+ ...zoom !== void 0 ? { zoom } : {},
534
+ ...priority ? { priority } : {},
535
+ ...sizes ? { sizes } : {},
536
+ ...loader !== void 0 ? { loader } : {},
537
+ ...hasQty ? { quantity } : {},
538
+ ...cx.thumb ? { classNames: cx.thumb } : {}
539
+ }
540
+ ) }),
541
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: cx.body, "data-sf-preview-body": "", children: [
542
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: cx.name, "data-sf-preview-name": "", children: href && !missing ? /* @__PURE__ */ jsxRuntime.jsx("a", { href, children: title }) : title }),
543
+ variant ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: cx.variant, "data-sf-preview-variant": "", children: variant }) : null,
544
+ sku ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: cx.sku, "data-sf-preview-sku": "", children: [
545
+ "SKU ",
546
+ sku
547
+ ] }) : null,
548
+ hasQty ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: cx.meta, "data-sf-preview-meta": "", children: [
549
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: cx.qty, "data-sf-preview-qty": "", children: (quantityLabel ?? "Cantidad {n}").replace("{n}", String(quantity)) }),
550
+ unitPrice != null && quantity > 1 ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: cx.unit, "data-sf-preview-unit": "", children: [
551
+ " \xB7 ",
552
+ unitPrice
553
+ ] }) : null
554
+ ] }) : null,
555
+ children
556
+ ] }),
557
+ total != null ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: cx.total, "data-sf-preview-total": "", children: total }) : unitPrice != null && !(hasQty && quantity > 1) ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: cx.price, "data-sf-preview-total": "", children: unitPrice }) : null
558
+ ] });
559
+ }
560
+ function ProductThumbStack({ items, max = 4, size = 44, moreLabel = "y {n} m\xE1s", className }) {
561
+ if (items.length === 0) return null;
562
+ const shown = items.length > max ? items.slice(0, max - 1) : items;
563
+ const rest = items.length - shown.length;
564
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { className, "data-sf-thumb-stack": "", style: { ["--sf-preview-size"]: `${size}px` }, children: [
565
+ shown.map((it, i) => /* @__PURE__ */ jsxRuntime.jsx(ProductThumb, { image: it.image ?? null, name: it.name ?? null, size, zoom: false }, i)),
566
+ rest > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("span", { "data-sf-thumb-more": "", role: "img", "aria-label": moreLabel.replace("{n}", String(rest)), children: [
567
+ "+",
568
+ rest
569
+ ] }) : null
570
+ ] });
571
+ }
374
572
 
375
573
  exports.InstallApp = InstallApp;
574
+ exports.ProductPreview = ProductPreview;
575
+ exports.ProductThumb = ProductThumb;
576
+ exports.ProductThumbStack = ProductThumbStack;
376
577
  exports.Select = Select;
377
578
  exports.Swatch = Swatch;
378
579
  exports.THEME_INIT_SCRIPT = THEME_INIT_SCRIPT;
@@ -384,6 +585,7 @@ exports.normalizeSwatchName = normalize;
384
585
  exports.optionHasSwatches = optionHasSwatches;
385
586
  exports.parseSwatchAttribute = parseSwatchAttribute;
386
587
  exports.pastelBackground = pastelBackground;
588
+ exports.productInitials = productInitials;
387
589
  exports.resolveSwatch = resolveSwatch;
388
590
  exports.resolveSwatchSpec = resolveSwatchSpec;
389
591
  exports.swatchBackground = swatchBackground;