@asteby/storefront-react 0.20.0 → 0.22.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.
package/dist/checkout.cjs CHANGED
@@ -241,7 +241,9 @@ var ES2 = {
241
241
  referenceHint: "Entre qu\xE9 calles, color de la fachada\u2026",
242
242
  emailHint: "Te enviamos aqu\xED la confirmaci\xF3n y el seguimiento.",
243
243
  phoneHint: "Solo para la paqueter\xEDa, por si necesita contactarte.",
244
- lookupFilled: "Completamos estado y ciudad con tu c\xF3digo postal."
244
+ lookupFilled: "Completamos estado y ciudad con tu c\xF3digo postal.",
245
+ provinceLocked: "Definido por tu c\xF3digo postal",
246
+ changePostalCode: "Cambiar c\xF3digo postal"
245
247
  };
246
248
  var EN2 = {
247
249
  email: "Email",
@@ -261,7 +263,9 @@ var EN2 = {
261
263
  referenceHint: "Cross streets, building color\u2026",
262
264
  emailHint: "We send the confirmation and tracking here.",
263
265
  phoneHint: "Only for the carrier, in case they need to reach you.",
264
- lookupFilled: "We filled in state and city from your postal code."
266
+ lookupFilled: "We filled in state and city from your postal code.",
267
+ provinceLocked: "Set by your postal code",
268
+ changePostalCode: "Change postal code"
265
269
  };
266
270
  function addressFieldsMessages(locale, overrides) {
267
271
  const base = (locale ?? "es-MX").toLowerCase().startsWith("en") ? EN2 : ES2;
@@ -287,8 +291,10 @@ function lockField(el) {
287
291
  if (el instanceof HTMLSelectElement) {
288
292
  const name = el.getAttribute("name");
289
293
  if (name) {
294
+ el.dataset.sfName = name;
290
295
  const hidden = document.createElement("input");
291
296
  hidden.type = "hidden";
297
+ hidden.dataset.sfLockMirror = "1";
292
298
  hidden.name = name;
293
299
  hidden.value = el.value;
294
300
  el.insertAdjacentElement("afterend", hidden);
@@ -299,12 +305,27 @@ function lockField(el) {
299
305
  el.readOnly = true;
300
306
  }
301
307
  }
308
+ function unlockField(el) {
309
+ if (el.dataset.sfLocked !== "1") return;
310
+ delete el.dataset.sfLocked;
311
+ el.removeAttribute("aria-readonly");
312
+ el.removeAttribute("title");
313
+ if (el instanceof HTMLSelectElement) {
314
+ const mirror = el.nextElementSibling;
315
+ if (mirror instanceof HTMLInputElement && mirror.dataset.sfLockMirror === "1") mirror.remove();
316
+ if (el.dataset.sfName) el.setAttribute("name", el.dataset.sfName);
317
+ delete el.dataset.sfName;
318
+ el.disabled = false;
319
+ } else {
320
+ el.readOnly = false;
321
+ }
322
+ }
302
323
  var ADDRESS_FILL_EVENT_FLAG = "sfSynthetic";
303
324
  function isAddressFillEvent(event) {
304
325
  return event[ADDRESS_FILL_EVENT_FLAG] === true;
305
326
  }
306
327
  function AddressFields(props) {
307
- const { defaults, names = {}, errors = {}, countryCode, provinces, postalCode, lookupPostalCode, lockProvinceOnFill, locale, messages, fieldMessages: fm, classNames: cx = {}, children } = props;
328
+ const { defaults, names = {}, errors = {}, countryCode, provinces, postalCode, lookupPostalCode, lockProvinceOnFill = true, locale, messages, fieldMessages: fm, classNames: cx = {}, children } = props;
308
329
  const showNeighborhood = props.neighborhood ?? true;
309
330
  const showReference = props.reference ?? true;
310
331
  const m = addressFieldsMessages(locale, messages);
@@ -313,6 +334,7 @@ function AddressFields(props) {
313
334
  const listId = react.useId();
314
335
  const [colonias, setColonias] = react.useState([]);
315
336
  const [filled, setFilled] = react.useState(false);
337
+ const [provinceLocked, setProvinceLocked] = react.useState(false);
316
338
  const [coloniaSelect, setColoniaSelect] = react.useState(false);
317
339
  const coloniaSelectRef = react.useRef(false);
318
340
  const coloniaCarry = react.useRef(null);
@@ -343,6 +365,31 @@ function AddressFields(props) {
343
365
  form.removeEventListener("change", mark, true);
344
366
  };
345
367
  }, []);
368
+ const releaseLookup = () => {
369
+ const form = zipRef.current?.closest("form");
370
+ if (!form) return;
371
+ const locked = form.querySelector('[data-sf-locked="1"]');
372
+ const touched = [];
373
+ if (locked) {
374
+ unlockField(locked);
375
+ touched.push(locked);
376
+ }
377
+ for (const key of ["province", "city", "neighborhood"]) {
378
+ const el = form.elements.namedItem(n(key));
379
+ if (el && !("length" in el && !(el instanceof HTMLSelectElement)) && !touched.includes(el)) touched.push(el);
380
+ }
381
+ for (const el of touched) {
382
+ if (el.dataset.sfAutofilled !== "1") continue;
383
+ el.value = "";
384
+ delete el.dataset.sfAutofilled;
385
+ const event = new Event("input", { bubbles: true });
386
+ Object.defineProperty(event, ADDRESS_FILL_EVENT_FLAG, { value: true });
387
+ el.dispatchEvent(event);
388
+ }
389
+ lastZip.current = null;
390
+ setProvinceLocked(false);
391
+ setFilled(false);
392
+ };
346
393
  const runLookup = (zip) => {
347
394
  const lookup = lookupRef.current;
348
395
  if (!lookup || !zip || zip === lastZip.current) return;
@@ -366,7 +413,11 @@ function AddressFields(props) {
366
413
  const event = new Event("input", { bubbles: true });
367
414
  Object.defineProperty(event, ADDRESS_FILL_EVENT_FLAG, { value: true });
368
415
  el2.dispatchEvent(event);
369
- if (lock) lockField(el2);
416
+ if (lock) {
417
+ lockField(el2);
418
+ el2.title = m.provinceLocked;
419
+ setProvinceLocked(true);
420
+ }
370
421
  return true;
371
422
  };
372
423
  const a = fill2(n("province"), info.province, lockProvinceOnFill);
@@ -414,6 +465,22 @@ function AddressFields(props) {
414
465
  const zip = defaults?.zip;
415
466
  if (zip) runLookup(zip);
416
467
  }, []);
468
+ const hasLock = (root) => !!root?.closest("form")?.querySelector('[data-sf-locked="1"]');
469
+ const changeZip = () => {
470
+ releaseLookup();
471
+ const zipEl = zipRef.current?.querySelector("input");
472
+ zipEl?.focus();
473
+ zipEl?.select();
474
+ };
475
+ const provinceHint = provinceLocked ? /* @__PURE__ */ jsxRuntime.jsxs("span", { "data-sf-province-locked": "", title: m.provinceLocked, children: [
476
+ /* @__PURE__ */ jsxRuntime.jsxs("svg", { "aria-hidden": "true", width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", style: { display: "inline-block", verticalAlign: "-1px", marginRight: 4 }, children: [
477
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "4", y: "11", width: "16", height: "10", rx: "2" }),
478
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M8 11V7a4 4 0 0 1 8 0v4" })
479
+ ] }),
480
+ m.provinceLocked,
481
+ " \xB7 ",
482
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", "data-sf-change-zip": "", onClick: changeZip, style: { background: "none", border: 0, padding: 0, font: "inherit", color: "inherit", textDecoration: "underline", cursor: "pointer" }, children: m.changePostalCode })
483
+ ] }) : void 0;
417
484
  const numericZip = postalCode?.inputMode === "numeric";
418
485
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
419
486
  /* @__PURE__ */ jsxRuntime.jsx("div", { ref: zipRef, className: join(cx.half), "data-sf-zip": true, children: /* @__PURE__ */ jsxRuntime.jsx(
@@ -433,15 +500,16 @@ function AddressFields(props) {
433
500
  classNames: { ...cx, root: cx.root },
434
501
  onInput: (e) => {
435
502
  const v = e.currentTarget.value.trim();
503
+ if (v !== lastZip.current && hasLock(zipRef.current)) releaseLookup();
436
504
  if (postalCode?.maxLength && v.length === postalCode.maxLength) runLookup(v);
437
505
  },
438
506
  onBlur: (e) => runLookup(e.currentTarget.value.trim())
439
507
  }
440
508
  ) }),
441
- provinces && provinces.length ? /* @__PURE__ */ jsxRuntime.jsxs(CheckoutField, { ...common, as: "select", name: n("province"), label: m.province, autoComplete: "address-level1", defaultValue: defaults?.province ?? "", error: errors[n("province")] ?? null, classNames: field(cx, "half"), children: [
509
+ provinces && provinces.length ? /* @__PURE__ */ jsxRuntime.jsxs(CheckoutField, { ...common, as: "select", name: n("province"), label: m.province, autoComplete: "address-level1", defaultValue: defaults?.province ?? "", error: errors[n("province")] ?? null, classNames: field(cx, "half"), hint: provinceHint, children: [
442
510
  /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", disabled: true, children: m.provincePlaceholder }),
443
511
  provinces.map((p) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: p.name, children: p.name }, p.code ?? p.name))
444
- ] }) : /* @__PURE__ */ jsxRuntime.jsx(CheckoutField, { ...common, name: n("province"), label: m.province, autoComplete: "address-level1", defaultValue: defaults?.province ?? "", error: errors[n("province")] ?? null, classNames: field(cx, "half") }),
512
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx(CheckoutField, { ...common, name: n("province"), label: m.province, autoComplete: "address-level1", defaultValue: defaults?.province ?? "", error: errors[n("province")] ?? null, classNames: field(cx, "half"), hint: provinceHint }),
445
513
  /* @__PURE__ */ jsxRuntime.jsx(CheckoutField, { ...common, name: n("city"), label: m.city, autoComplete: "address-level2", defaultValue: defaults?.city ?? "", error: errors[n("city")] ?? null, classNames: field(cx, "half") }),
446
514
  showNeighborhood && coloniaSelect ? /* @__PURE__ */ jsxRuntime.jsxs(
447
515
  CheckoutField,
@@ -493,6 +561,129 @@ function useResolvedLocale(locale) {
493
561
  const sf = useOptionalStorefront();
494
562
  return locale ?? sf?.locale ?? "es-MX";
495
563
  }
564
+ var DEFAULT_IMAGE_WIDTHS = [320, 480, 640, 768, 1024, 1280, 1600];
565
+ var defaultImageLoader = (src, width, height) => {
566
+ try {
567
+ const isAbsolute = /^[a-z][a-z0-9+.-]*:/i.test(src);
568
+ const url = new URL(src, "http://relative.invalid");
569
+ url.searchParams.set("width", String(width));
570
+ if (height) url.searchParams.set("height", String(height));
571
+ return isAbsolute ? url.toString() : url.pathname + url.search + url.hash;
572
+ } catch {
573
+ return src;
574
+ }
575
+ };
576
+ function parseRatio(ratio) {
577
+ if (!ratio) return null;
578
+ const [w, h] = ratio.split(/[/:]/).map((n) => Number.parseFloat(n));
579
+ return w && h ? w / h : null;
580
+ }
581
+ function Image({
582
+ data,
583
+ sizes = "100vw",
584
+ widths = DEFAULT_IMAGE_WIDTHS,
585
+ loader = defaultImageLoader,
586
+ priority = false,
587
+ aspectRatio,
588
+ alt,
589
+ loading,
590
+ decoding,
591
+ ...rest
592
+ }) {
593
+ const ratio = parseRatio(aspectRatio) ?? (data.width && data.height ? data.width / data.height : null);
594
+ let candidates = [...widths].sort((a, b) => a - b);
595
+ if (data.width) {
596
+ const capped = candidates.filter((w) => w <= data.width);
597
+ candidates = capped.length ? capped : [data.width];
598
+ }
599
+ const srcSet = loader ? candidates.map((w) => `${loader(data.url, w, ratio ? Math.round(w / ratio) : void 0)} ${w}w`).join(", ") : void 0;
600
+ const width = data.width ?? candidates[candidates.length - 1];
601
+ const height = data.height ?? (ratio && width ? Math.round(width / ratio) : void 0);
602
+ return /* @__PURE__ */ jsxRuntime.jsx(
603
+ "img",
604
+ {
605
+ src: data.url,
606
+ alt: alt ?? data.alt ?? "",
607
+ ...srcSet ? { srcSet, sizes } : {},
608
+ ...width ? { width } : {},
609
+ ...height ? { height } : {},
610
+ loading: loading ?? (priority ? "eager" : "lazy"),
611
+ decoding: decoding ?? (priority ? "auto" : "async"),
612
+ ...priority ? { fetchPriority: "high" } : {},
613
+ ...rest
614
+ }
615
+ );
616
+ }
617
+ function productInitials(name) {
618
+ const words = (name ?? "").normalize("NFKC").split(/[\s\-_/.,()]+/).map((w) => Array.from(w).find((c) => /\p{L}|\p{N}/u.test(c)) ?? "").filter(Boolean);
619
+ if (words.length === 0) return "";
620
+ return (words.length > 1 ? words[0] + words[1] : words[0]).toLocaleUpperCase();
621
+ }
622
+ function ProductThumb({
623
+ image,
624
+ name,
625
+ size = 56,
626
+ ratio = "1/1",
627
+ fit = "contain",
628
+ quantity,
629
+ decorative = false,
630
+ zoom = true,
631
+ priority = false,
632
+ sizes,
633
+ loader,
634
+ className,
635
+ classNames: cx = {}
636
+ }) {
637
+ const url = image?.url || null;
638
+ const [state, setState] = react.useState(url ? "loading" : "fallback");
639
+ const box = react.useRef(null);
640
+ react.useEffect(() => {
641
+ const img = box.current?.querySelector("img");
642
+ setState(!url ? "fallback" : img?.complete ? img.naturalWidth > 0 ? "loaded" : "fallback" : "loading");
643
+ }, [url]);
644
+ const label = name?.trim() || "";
645
+ const initials = productInitials(label);
646
+ const showBadge = typeof quantity === "number" && quantity > 1;
647
+ const a11y = state === "fallback" && !decorative && label ? { role: "img", "aria-label": label } : { "aria-hidden": decorative || state === "fallback" ? true : void 0 };
648
+ return /* @__PURE__ */ jsxRuntime.jsxs(
649
+ "span",
650
+ {
651
+ ref: box,
652
+ className: [cx.root, className].filter(Boolean).join(" ") || void 0,
653
+ style: { ["--sf-preview-size"]: `${size}px`, ["--sf-preview-ratio"]: ratio.replace(":", "/") },
654
+ "data-sf-thumb": "",
655
+ "data-state": state,
656
+ "data-fit": fit,
657
+ "data-zoom": zoom || void 0,
658
+ ...a11y,
659
+ children: [
660
+ url && state !== "fallback" ? /* @__PURE__ */ jsxRuntime.jsx(
661
+ Image,
662
+ {
663
+ data: { url, alt: image?.alt ?? null, ...image?.width ? { width: image.width } : {}, ...image?.height ? { height: image.height } : {} },
664
+ alt: decorative ? "" : image?.alt ?? label,
665
+ aspectRatio: ratio,
666
+ sizes: sizes ?? `${size * 2}px`,
667
+ widths: [Math.round(size), Math.round(size * 2), Math.round(size * 3)],
668
+ ...loader !== void 0 ? { loader } : {},
669
+ priority,
670
+ className: cx.img,
671
+ onLoad: () => setState("loaded"),
672
+ onError: () => setState("fallback")
673
+ }
674
+ ) : null,
675
+ 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,
676
+ showBadge ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: cx.badge, "data-sf-thumb-qty": "", "aria-hidden": "true", children: quantity }) : null
677
+ ]
678
+ }
679
+ );
680
+ }
681
+ function PackageGlyph() {
682
+ 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: [
683
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M21 8 12 3 3 8v8l9 5 9-5V8Z" }),
684
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m3 8 9 5 9-5M12 13v8" })
685
+ ] });
686
+ }
496
687
  var ES3 = {
497
688
  title: "Tu pedido",
498
689
  subtotal: "Subtotal",
@@ -527,13 +718,18 @@ function orderSummaryMessages(locale, overrides) {
527
718
  const base = (locale ?? "es-MX").toLowerCase().startsWith("en") ? EN3 : ES3;
528
719
  return overrides ? { ...base, ...overrides } : base;
529
720
  }
721
+ function defaultLineMedia(l) {
722
+ const img = l.merchandise.image ?? l.merchandise.product.featured_image ?? null;
723
+ return /* @__PURE__ */ jsxRuntime.jsx(ProductThumb, { image: img, name: l.merchandise.product.title, size: 56, decorative: true, zoom: false });
724
+ }
530
725
  var isZero = (m) => !!m && Number(m.amount) === 0;
531
726
  var positive = (m) => !!m && Number(m.amount) > 0;
532
727
  function OrderSummary(props) {
533
- const { lines, cost, updating = false, renderLineMedia, renderAmount, title, headingLevel = 2, classNames: cx = {}, children } = props;
728
+ const { lines, cost, updating = false, media = true, renderAmount, title, headingLevel = 2, classNames: cx = {}, children } = props;
534
729
  const locale = useResolvedLocale(props.locale);
535
730
  const m = orderSummaryMessages(locale, props.messages);
536
731
  const money = (v) => renderAmount ? renderAmount(v) : storefront.formatMoney(v, locale);
732
+ const renderLineMedia = props.renderLineMedia ?? (media ? defaultLineMedia : void 0);
537
733
  const H = headingLevel === 3 ? "h3" : "h2";
538
734
  const discounts = cost.discounts?.filter((d) => positive(d.amount)) ?? [];
539
735
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cx.root, "data-sf-summary": true, children: [