@asteby/storefront-react 0.25.0 → 0.26.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +35 -2
  2. package/dist/account-server.cjs +128 -19
  3. package/dist/account-server.cjs.map +1 -1
  4. package/dist/account-server.d.cts +67 -4
  5. package/dist/account-server.d.ts +67 -4
  6. package/dist/account-server.js +125 -20
  7. package/dist/account-server.js.map +1 -1
  8. package/dist/account.cjs +252 -7
  9. package/dist/account.cjs.map +1 -1
  10. package/dist/account.css +91 -0
  11. package/dist/account.d.cts +130 -6
  12. package/dist/account.d.ts +130 -6
  13. package/dist/account.js +247 -8
  14. package/dist/account.js.map +1 -1
  15. package/dist/checkout.cjs +162 -8
  16. package/dist/checkout.cjs.map +1 -1
  17. package/dist/checkout.d.cts +104 -4
  18. package/dist/checkout.d.ts +104 -4
  19. package/dist/checkout.js +158 -10
  20. package/dist/checkout.js.map +1 -1
  21. package/dist/chunk-Z4C4LKXF.js +25 -0
  22. package/dist/chunk-Z4C4LKXF.js.map +1 -0
  23. package/dist/fiscal-server.cjs +27 -2
  24. package/dist/fiscal-server.cjs.map +1 -1
  25. package/dist/fiscal-server.d.cts +10 -1
  26. package/dist/fiscal-server.d.ts +10 -1
  27. package/dist/fiscal-server.js +26 -3
  28. package/dist/fiscal-server.js.map +1 -1
  29. package/dist/fit-key-B9sYQVwG.d.cts +49 -0
  30. package/dist/fit-key-B9sYQVwG.d.ts +49 -0
  31. package/dist/fitment.cjs +54 -9
  32. package/dist/fitment.cjs.map +1 -1
  33. package/dist/fitment.d.cts +20 -2
  34. package/dist/fitment.d.ts +20 -2
  35. package/dist/fitment.js +54 -10
  36. package/dist/fitment.js.map +1 -1
  37. package/dist/settings-types-jxFQFFqP.d.cts +77 -0
  38. package/dist/settings-types-jxFQFFqP.d.ts +77 -0
  39. package/dist/subjects-server.cjs +248 -0
  40. package/dist/subjects-server.cjs.map +1 -0
  41. package/dist/subjects-server.d.cts +130 -0
  42. package/dist/subjects-server.d.ts +130 -0
  43. package/dist/subjects-server.js +217 -0
  44. package/dist/subjects-server.js.map +1 -0
  45. package/dist/subjects.cjs +501 -0
  46. package/dist/subjects.cjs.map +1 -0
  47. package/dist/subjects.css +166 -0
  48. package/dist/subjects.d.cts +178 -0
  49. package/dist/subjects.d.ts +178 -0
  50. package/dist/subjects.js +465 -0
  51. package/dist/subjects.js.map +1 -0
  52. package/package.json +23 -2
  53. package/dist/types-D7pVK0GK.d.cts +0 -39
  54. package/dist/types-D7pVK0GK.d.ts +0 -39
@@ -1,6 +1,6 @@
1
1
  import * as react from 'react';
2
- import { ReactNode } from 'react';
3
- import { Address, CartLine, CartCost, Money, ShippingRate, PickupOption } from '@asteby/storefront';
2
+ import { ReactNode, RefObject } from 'react';
3
+ import { Address, CartLine, CartCost, Money, ShippingRate, PickupOption, DeliveryOptions as DeliveryOptions$1 } from '@asteby/storefront';
4
4
  import { F as FieldErrors, g as FieldMessages, a as CheckoutFieldClassNames } from './fields-vnMUi2HB.cjs';
5
5
  export { C as CheckoutField, b as CheckoutFieldProps, c as CheckoutSelectProps, E as ErrorSummary, d as ErrorSummaryItem, e as ErrorSummaryProps, f as FieldErrorsState, U as UseFieldErrorsOptions, h as fieldMessages, u as useFieldErrors, v as validationMessage } from './fields-vnMUi2HB.cjs';
6
6
 
@@ -290,6 +290,11 @@ type DeliveryMessages = {
290
290
  /** The rates could not be quoted (`error`). */
291
291
  error: string;
292
292
  retry: string;
293
+ /** The order reaches the free-shipping minimum: nothing to choose. */
294
+ freeShipping: string;
295
+ freeShippingNote: string;
296
+ /** Screen-reader status when the rates are ready (`{n}` = how many). */
297
+ ratesReady: string;
293
298
  };
294
299
  /** Messages for a locale (`es*` default, `en*`), with optional overrides. */
295
300
  declare function deliveryMessages(locale?: string | null, overrides?: Partial<DeliveryMessages> | null): DeliveryMessages;
@@ -342,7 +347,7 @@ declare function isQuoteChange(err: {
342
347
  */
343
348
  declare function matchRate<R extends Pick<ShippingRate, 'id' | 'carrier' | 'service_code' | 'title'>>(previous: Pick<ShippingRate, 'carrier' | 'service_code' | 'title'> | null | undefined, rates: readonly R[]): R | null;
344
349
 
345
- type DeliveryOptionsClassNames = Partial<Record<'root' | 'option' | 'radio' | 'mark' | 'body' | 'title' | 'carrier' | 'service' | 'eta' | 'badges' | 'badge' | 'price' | 'free' | 'note' | 'partial' | 'skeleton' | 'more' | 'moreSummary' | 'group' | 'groupTitle' | 'error' | 'retry', string>>;
350
+ type DeliveryOptionsClassNames = Partial<Record<'root' | 'option' | 'radio' | 'mark' | 'body' | 'title' | 'carrier' | 'service' | 'eta' | 'badges' | 'badge' | 'price' | 'free' | 'freeBanner' | 'note' | 'partial' | 'skeleton' | 'more' | 'moreSummary' | 'group' | 'groupTitle' | 'error' | 'retry', string>>;
346
351
  type DeliveryOptionsProps = {
347
352
  rates: readonly ShippingRate[];
348
353
  pickup?: readonly PickupOption[];
@@ -363,6 +368,14 @@ type DeliveryOptionsProps = {
363
368
  requoted?: boolean;
364
369
  /** Rates are being quoted again: skeleton rows (no rates yet) or the current rows dimmed, `aria-busy`. */
365
370
  loading?: boolean;
371
+ /**
372
+ * The order reaches the free-shipping minimum (`DeliveryOptions.free`, configured by the organization): no carrier
373
+ * selector, just "Free shipping" and the rate is submitted by itself so the buyer goes straight to payment.
374
+ * With pickup branches the plain rows stay (delivery or pickup is still a real choice).
375
+ */
376
+ free?: boolean;
377
+ /** Rates just arrived by themselves (auto quote): a polite screen-reader status says how many. */
378
+ announceReady?: boolean;
366
379
  /** Quote deadline note: `always` (default), only when it is `soon`, or `never`. */
367
380
  quoteNote?: 'always' | 'soon' | 'never';
368
381
  /** Time zone of the note. Default: the buyer's browser. */
@@ -475,4 +488,91 @@ type FlatRatesOptions = {
475
488
  */
476
489
  declare function flatRates<R extends ShippingRate>(input: readonly R[], options?: FlatRatesOptions): RecommendedRate<R>[];
477
490
 
478
- export { ADDRESS_FILL_EVENT_FLAG, type AddressFieldKey, AddressFields, type AddressFieldsMessages, type AddressFieldsProps, type Brand, type BrandKind, BrandMark, type BrandMarkProps, type BrandMarkSource, CheckoutFieldClassNames, type ContactFieldKey, ContactFields, type ContactFieldsProps, type DeliveryMessages, DeliveryOptions, type DeliveryOptionsClassNames, type DeliveryOptionsProps, FieldErrors, type FieldLayoutClassNames, FieldMessages, type FlatRatesOptions, OrderSummary, type OrderSummaryClassNames, OrderSummaryDisclosure, type OrderSummaryDisclosureProps, type OrderSummaryMessages, type OrderSummaryProps, type PostalCodeInfo, type QuoteNote, type RateBadge, type RateGroup, type RateTag, type RecommendRatesOptions, type RecommendedRate, type RecommendedRates, addressFieldsMessages, brandKey, brandLogo, brandName, dedupeRates, deliveryMessages, findBrand, flatRates, isAddressFillEvent, isQuoteChange, listBrands, matchRate, orderSummaryMessages, quoteDeadline, quoteNote, rateBadges, rateCarrier, rateEta, rateService, recommendRates };
491
+ /**
492
+ * One-step delivery: as soon as the address is complete the shipping rates load by themselves (debounced), the default
493
+ * rate is preselected and payment is ready. No "continue" button, no choosing again.
494
+ *
495
+ * The hook is transport-agnostic: the shop gives `quote(address)` (save the address in the cart, read the delivery
496
+ * options) and renders the result with `<DeliveryOptions>`. Only the fields that change the price re-quote (country,
497
+ * postal code, state, city): editing the street number, the phone or the reference does not. Free shipping and fixed
498
+ * prices come from the server (`DeliveryOptions.free` / `.fixed`, configured per organization): nothing here knows an
499
+ * amount.
500
+ */
501
+
502
+ /** What the shop reads from the address fields of the form. Keys follow the `name` of each input. */
503
+ type QuoteAddress = {
504
+ country_code: string;
505
+ zip: string;
506
+ province: string;
507
+ city: string;
508
+ neighborhood: string;
509
+ address1: string;
510
+ [key: string]: string;
511
+ };
512
+ type AutoQuoteStatus =
513
+ /** Address incomplete: nothing is quoted yet. */
514
+ 'idle'
515
+ /** Complete address, waiting for the buyer to stop typing. */
516
+ | 'waiting' | 'loading' | 'ready' | 'error';
517
+ type UseAutoQuoteOptions = {
518
+ /** The form that holds the address inputs (events are listened to on it). */
519
+ formRef: RefObject<HTMLFormElement | null>;
520
+ /**
521
+ * Saves the address and returns the delivery options for it. Receives an `AbortSignal` that fires when a newer
522
+ * quote replaces this one: ignore its result then (the hook already does).
523
+ *
524
+ * Only price fields re-quote, so the address saved here can lag behind the form (street, number, reference): the
525
+ * shop's submit must save the full address again before choosing the delivery and paying. The server validates the
526
+ * chosen rate against the saved address (a rate of another postal code is refused), so a stale quote never sets a price.
527
+ */
528
+ quote: (address: QuoteAddress, signal: AbortSignal) => Promise<DeliveryOptions$1>;
529
+ /** `name` of each address input. Default: `country_code`, `zip`, `province`, `city`, `neighborhood`, `address1`. */
530
+ names?: Partial<Record<'country_code' | 'zip' | 'province' | 'city' | 'neighborhood' | 'address1', string>>;
531
+ /** Fields that must be filled to quote. Default: all of the six names above except `country_code`. */
532
+ required?: ReadonlyArray<keyof QuoteAddress>;
533
+ /** A postal code is valid to quote. Default: 5 digits. */
534
+ validPostalCode?: (zip: string) => boolean;
535
+ /** Country used when the form has no country input. Default `MX`. */
536
+ defaultCountry?: string;
537
+ /** Quiet time after the last keystroke. Default 600 ms. */
538
+ debounceMs?: number;
539
+ /** Picks the preselected rate. Default: {@link defaultRate} (the cheapest). */
540
+ pickDefault?: (delivery: DeliveryOptions$1) => ShippingRate | null;
541
+ /** The rate (`rate:<id>`) already saved in the cart: kept when the quote still offers it. */
542
+ savedValue?: string | null;
543
+ enabled?: boolean;
544
+ /** Fires with the options of every successful quote. */
545
+ onQuoted?: (delivery: DeliveryOptions$1) => void;
546
+ };
547
+ type UseAutoQuoteResult = {
548
+ status: AutoQuoteStatus;
549
+ /** The last good quote (kept while a new one loads, so the list does not flash). `null` before the first. */
550
+ delivery: DeliveryOptions$1 | null;
551
+ /** `rate:<id>` to preselect (the saved rate if still offered, else the default), `null` when none. */
552
+ defaultValue: string | null;
553
+ /** The address the current `delivery` was quoted for. */
554
+ quotedFor: QuoteAddress | null;
555
+ /** Quote again now (retry button). */
556
+ retry: () => void;
557
+ /** Plain-language status for a live region ("Buscando…", "Listo", …) keys, not text. */
558
+ announce: 'searching' | 'ready' | 'free' | 'error' | null;
559
+ };
560
+ declare const DEFAULT_QUOTE_NAMES: {
561
+ readonly country_code: "country_code";
562
+ readonly zip: "zip";
563
+ readonly province: "province";
564
+ readonly city: "city";
565
+ readonly neighborhood: "neighborhood";
566
+ readonly address1: "address1";
567
+ };
568
+ /** Only these change the price of a shipment; any other field of the form (street, phone, reference…) never re-quotes. */
569
+ declare const QUOTE_KEYS: readonly ["country_code", "zip", "province", "city"];
570
+ /** Identity of a quote: equal fingerprints share the same rates. */
571
+ declare function quoteFingerprint(a: Pick<QuoteAddress, (typeof QUOTE_KEYS)[number]>): string;
572
+ /** The default rate: the cheapest one that is not a pickup (ties keep the server order, which already puts the best first). */
573
+ declare function defaultRate(delivery: Pick<DeliveryOptions$1, 'rates'>): ShippingRate | null;
574
+ /** Reads the address from the form; `null` when it is not complete yet. */
575
+ declare function readQuoteAddress(form: HTMLFormElement | null, opts?: Pick<UseAutoQuoteOptions, 'names' | 'required' | 'validPostalCode' | 'defaultCountry'>): QuoteAddress | null;
576
+ declare function useAutoQuote(options: UseAutoQuoteOptions): UseAutoQuoteResult;
577
+
578
+ export { ADDRESS_FILL_EVENT_FLAG, type AddressFieldKey, AddressFields, type AddressFieldsMessages, type AddressFieldsProps, type AutoQuoteStatus, type Brand, type BrandKind, BrandMark, type BrandMarkProps, type BrandMarkSource, CheckoutFieldClassNames, type ContactFieldKey, ContactFields, type ContactFieldsProps, DEFAULT_QUOTE_NAMES, type DeliveryMessages, DeliveryOptions, type DeliveryOptionsClassNames, type DeliveryOptionsProps, FieldErrors, type FieldLayoutClassNames, FieldMessages, type FlatRatesOptions, OrderSummary, type OrderSummaryClassNames, OrderSummaryDisclosure, type OrderSummaryDisclosureProps, type OrderSummaryMessages, type OrderSummaryProps, type PostalCodeInfo, QUOTE_KEYS, type QuoteAddress, type QuoteNote, type RateBadge, type RateGroup, type RateTag, type RecommendRatesOptions, type RecommendedRate, type RecommendedRates, type UseAutoQuoteOptions, type UseAutoQuoteResult, addressFieldsMessages, brandKey, brandLogo, brandName, dedupeRates, defaultRate, deliveryMessages, findBrand, flatRates, isAddressFillEvent, isQuoteChange, listBrands, matchRate, orderSummaryMessages, quoteDeadline, quoteFingerprint, quoteNote, rateBadges, rateCarrier, rateEta, rateService, readQuoteAddress, recommendRates, useAutoQuote };
@@ -1,6 +1,6 @@
1
1
  import * as react from 'react';
2
- import { ReactNode } from 'react';
3
- import { Address, CartLine, CartCost, Money, ShippingRate, PickupOption } from '@asteby/storefront';
2
+ import { ReactNode, RefObject } from 'react';
3
+ import { Address, CartLine, CartCost, Money, ShippingRate, PickupOption, DeliveryOptions as DeliveryOptions$1 } from '@asteby/storefront';
4
4
  import { F as FieldErrors, g as FieldMessages, a as CheckoutFieldClassNames } from './fields-vnMUi2HB.js';
5
5
  export { C as CheckoutField, b as CheckoutFieldProps, c as CheckoutSelectProps, E as ErrorSummary, d as ErrorSummaryItem, e as ErrorSummaryProps, f as FieldErrorsState, U as UseFieldErrorsOptions, h as fieldMessages, u as useFieldErrors, v as validationMessage } from './fields-vnMUi2HB.js';
6
6
 
@@ -290,6 +290,11 @@ type DeliveryMessages = {
290
290
  /** The rates could not be quoted (`error`). */
291
291
  error: string;
292
292
  retry: string;
293
+ /** The order reaches the free-shipping minimum: nothing to choose. */
294
+ freeShipping: string;
295
+ freeShippingNote: string;
296
+ /** Screen-reader status when the rates are ready (`{n}` = how many). */
297
+ ratesReady: string;
293
298
  };
294
299
  /** Messages for a locale (`es*` default, `en*`), with optional overrides. */
295
300
  declare function deliveryMessages(locale?: string | null, overrides?: Partial<DeliveryMessages> | null): DeliveryMessages;
@@ -342,7 +347,7 @@ declare function isQuoteChange(err: {
342
347
  */
343
348
  declare function matchRate<R extends Pick<ShippingRate, 'id' | 'carrier' | 'service_code' | 'title'>>(previous: Pick<ShippingRate, 'carrier' | 'service_code' | 'title'> | null | undefined, rates: readonly R[]): R | null;
344
349
 
345
- type DeliveryOptionsClassNames = Partial<Record<'root' | 'option' | 'radio' | 'mark' | 'body' | 'title' | 'carrier' | 'service' | 'eta' | 'badges' | 'badge' | 'price' | 'free' | 'note' | 'partial' | 'skeleton' | 'more' | 'moreSummary' | 'group' | 'groupTitle' | 'error' | 'retry', string>>;
350
+ type DeliveryOptionsClassNames = Partial<Record<'root' | 'option' | 'radio' | 'mark' | 'body' | 'title' | 'carrier' | 'service' | 'eta' | 'badges' | 'badge' | 'price' | 'free' | 'freeBanner' | 'note' | 'partial' | 'skeleton' | 'more' | 'moreSummary' | 'group' | 'groupTitle' | 'error' | 'retry', string>>;
346
351
  type DeliveryOptionsProps = {
347
352
  rates: readonly ShippingRate[];
348
353
  pickup?: readonly PickupOption[];
@@ -363,6 +368,14 @@ type DeliveryOptionsProps = {
363
368
  requoted?: boolean;
364
369
  /** Rates are being quoted again: skeleton rows (no rates yet) or the current rows dimmed, `aria-busy`. */
365
370
  loading?: boolean;
371
+ /**
372
+ * The order reaches the free-shipping minimum (`DeliveryOptions.free`, configured by the organization): no carrier
373
+ * selector, just "Free shipping" and the rate is submitted by itself so the buyer goes straight to payment.
374
+ * With pickup branches the plain rows stay (delivery or pickup is still a real choice).
375
+ */
376
+ free?: boolean;
377
+ /** Rates just arrived by themselves (auto quote): a polite screen-reader status says how many. */
378
+ announceReady?: boolean;
366
379
  /** Quote deadline note: `always` (default), only when it is `soon`, or `never`. */
367
380
  quoteNote?: 'always' | 'soon' | 'never';
368
381
  /** Time zone of the note. Default: the buyer's browser. */
@@ -475,4 +488,91 @@ type FlatRatesOptions = {
475
488
  */
476
489
  declare function flatRates<R extends ShippingRate>(input: readonly R[], options?: FlatRatesOptions): RecommendedRate<R>[];
477
490
 
478
- export { ADDRESS_FILL_EVENT_FLAG, type AddressFieldKey, AddressFields, type AddressFieldsMessages, type AddressFieldsProps, type Brand, type BrandKind, BrandMark, type BrandMarkProps, type BrandMarkSource, CheckoutFieldClassNames, type ContactFieldKey, ContactFields, type ContactFieldsProps, type DeliveryMessages, DeliveryOptions, type DeliveryOptionsClassNames, type DeliveryOptionsProps, FieldErrors, type FieldLayoutClassNames, FieldMessages, type FlatRatesOptions, OrderSummary, type OrderSummaryClassNames, OrderSummaryDisclosure, type OrderSummaryDisclosureProps, type OrderSummaryMessages, type OrderSummaryProps, type PostalCodeInfo, type QuoteNote, type RateBadge, type RateGroup, type RateTag, type RecommendRatesOptions, type RecommendedRate, type RecommendedRates, addressFieldsMessages, brandKey, brandLogo, brandName, dedupeRates, deliveryMessages, findBrand, flatRates, isAddressFillEvent, isQuoteChange, listBrands, matchRate, orderSummaryMessages, quoteDeadline, quoteNote, rateBadges, rateCarrier, rateEta, rateService, recommendRates };
491
+ /**
492
+ * One-step delivery: as soon as the address is complete the shipping rates load by themselves (debounced), the default
493
+ * rate is preselected and payment is ready. No "continue" button, no choosing again.
494
+ *
495
+ * The hook is transport-agnostic: the shop gives `quote(address)` (save the address in the cart, read the delivery
496
+ * options) and renders the result with `<DeliveryOptions>`. Only the fields that change the price re-quote (country,
497
+ * postal code, state, city): editing the street number, the phone or the reference does not. Free shipping and fixed
498
+ * prices come from the server (`DeliveryOptions.free` / `.fixed`, configured per organization): nothing here knows an
499
+ * amount.
500
+ */
501
+
502
+ /** What the shop reads from the address fields of the form. Keys follow the `name` of each input. */
503
+ type QuoteAddress = {
504
+ country_code: string;
505
+ zip: string;
506
+ province: string;
507
+ city: string;
508
+ neighborhood: string;
509
+ address1: string;
510
+ [key: string]: string;
511
+ };
512
+ type AutoQuoteStatus =
513
+ /** Address incomplete: nothing is quoted yet. */
514
+ 'idle'
515
+ /** Complete address, waiting for the buyer to stop typing. */
516
+ | 'waiting' | 'loading' | 'ready' | 'error';
517
+ type UseAutoQuoteOptions = {
518
+ /** The form that holds the address inputs (events are listened to on it). */
519
+ formRef: RefObject<HTMLFormElement | null>;
520
+ /**
521
+ * Saves the address and returns the delivery options for it. Receives an `AbortSignal` that fires when a newer
522
+ * quote replaces this one: ignore its result then (the hook already does).
523
+ *
524
+ * Only price fields re-quote, so the address saved here can lag behind the form (street, number, reference): the
525
+ * shop's submit must save the full address again before choosing the delivery and paying. The server validates the
526
+ * chosen rate against the saved address (a rate of another postal code is refused), so a stale quote never sets a price.
527
+ */
528
+ quote: (address: QuoteAddress, signal: AbortSignal) => Promise<DeliveryOptions$1>;
529
+ /** `name` of each address input. Default: `country_code`, `zip`, `province`, `city`, `neighborhood`, `address1`. */
530
+ names?: Partial<Record<'country_code' | 'zip' | 'province' | 'city' | 'neighborhood' | 'address1', string>>;
531
+ /** Fields that must be filled to quote. Default: all of the six names above except `country_code`. */
532
+ required?: ReadonlyArray<keyof QuoteAddress>;
533
+ /** A postal code is valid to quote. Default: 5 digits. */
534
+ validPostalCode?: (zip: string) => boolean;
535
+ /** Country used when the form has no country input. Default `MX`. */
536
+ defaultCountry?: string;
537
+ /** Quiet time after the last keystroke. Default 600 ms. */
538
+ debounceMs?: number;
539
+ /** Picks the preselected rate. Default: {@link defaultRate} (the cheapest). */
540
+ pickDefault?: (delivery: DeliveryOptions$1) => ShippingRate | null;
541
+ /** The rate (`rate:<id>`) already saved in the cart: kept when the quote still offers it. */
542
+ savedValue?: string | null;
543
+ enabled?: boolean;
544
+ /** Fires with the options of every successful quote. */
545
+ onQuoted?: (delivery: DeliveryOptions$1) => void;
546
+ };
547
+ type UseAutoQuoteResult = {
548
+ status: AutoQuoteStatus;
549
+ /** The last good quote (kept while a new one loads, so the list does not flash). `null` before the first. */
550
+ delivery: DeliveryOptions$1 | null;
551
+ /** `rate:<id>` to preselect (the saved rate if still offered, else the default), `null` when none. */
552
+ defaultValue: string | null;
553
+ /** The address the current `delivery` was quoted for. */
554
+ quotedFor: QuoteAddress | null;
555
+ /** Quote again now (retry button). */
556
+ retry: () => void;
557
+ /** Plain-language status for a live region ("Buscando…", "Listo", …) keys, not text. */
558
+ announce: 'searching' | 'ready' | 'free' | 'error' | null;
559
+ };
560
+ declare const DEFAULT_QUOTE_NAMES: {
561
+ readonly country_code: "country_code";
562
+ readonly zip: "zip";
563
+ readonly province: "province";
564
+ readonly city: "city";
565
+ readonly neighborhood: "neighborhood";
566
+ readonly address1: "address1";
567
+ };
568
+ /** Only these change the price of a shipment; any other field of the form (street, phone, reference…) never re-quotes. */
569
+ declare const QUOTE_KEYS: readonly ["country_code", "zip", "province", "city"];
570
+ /** Identity of a quote: equal fingerprints share the same rates. */
571
+ declare function quoteFingerprint(a: Pick<QuoteAddress, (typeof QUOTE_KEYS)[number]>): string;
572
+ /** The default rate: the cheapest one that is not a pickup (ties keep the server order, which already puts the best first). */
573
+ declare function defaultRate(delivery: Pick<DeliveryOptions$1, 'rates'>): ShippingRate | null;
574
+ /** Reads the address from the form; `null` when it is not complete yet. */
575
+ declare function readQuoteAddress(form: HTMLFormElement | null, opts?: Pick<UseAutoQuoteOptions, 'names' | 'required' | 'validPostalCode' | 'defaultCountry'>): QuoteAddress | null;
576
+ declare function useAutoQuote(options: UseAutoQuoteOptions): UseAutoQuoteResult;
577
+
578
+ export { ADDRESS_FILL_EVENT_FLAG, type AddressFieldKey, AddressFields, type AddressFieldsMessages, type AddressFieldsProps, type AutoQuoteStatus, type Brand, type BrandKind, BrandMark, type BrandMarkProps, type BrandMarkSource, CheckoutFieldClassNames, type ContactFieldKey, ContactFields, type ContactFieldsProps, DEFAULT_QUOTE_NAMES, type DeliveryMessages, DeliveryOptions, type DeliveryOptionsClassNames, type DeliveryOptionsProps, FieldErrors, type FieldLayoutClassNames, FieldMessages, type FlatRatesOptions, OrderSummary, type OrderSummaryClassNames, OrderSummaryDisclosure, type OrderSummaryDisclosureProps, type OrderSummaryMessages, type OrderSummaryProps, type PostalCodeInfo, QUOTE_KEYS, type QuoteAddress, type QuoteNote, type RateBadge, type RateGroup, type RateTag, type RecommendRatesOptions, type RecommendedRate, type RecommendedRates, type UseAutoQuoteOptions, type UseAutoQuoteResult, addressFieldsMessages, brandKey, brandLogo, brandName, dedupeRates, defaultRate, deliveryMessages, findBrand, flatRates, isAddressFillEvent, isQuoteChange, listBrands, matchRate, orderSummaryMessages, quoteDeadline, quoteFingerprint, quoteNote, rateBadges, rateCarrier, rateEta, rateService, readQuoteAddress, recommendRates, useAutoQuote };
package/dist/checkout.js CHANGED
@@ -6,7 +6,7 @@ import { ProductThumb } from './chunk-VHEY5AU3.js';
6
6
  import './chunk-6CVKW7F3.js';
7
7
  import { useResolvedLocale } from './chunk-T52H5HL3.js';
8
8
  import './chunk-DQBL2DFJ.js';
9
- import { useId, useState, useRef, useEffect, useMemo, useSyncExternalStore } from 'react';
9
+ import { useId, useState, useRef, useEffect, useMemo, useCallback, useSyncExternalStore } from 'react';
10
10
  import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
11
11
  import { formatMoney } from '@asteby/storefront';
12
12
 
@@ -563,7 +563,10 @@ var ES3 = {
563
563
  moreOptions: "Ver m\xE1s opciones ({n})",
564
564
  otherCarriers: "Otras opciones",
565
565
  error: "No pudimos consultar las opciones de env\xEDo en este momento.",
566
- retry: "Intentar de nuevo"
566
+ retry: "Intentar de nuevo",
567
+ freeShipping: "Env\xEDo gratis",
568
+ freeShippingNote: "Tu pedido ya incluye el env\xEDo: no tienes que elegir paqueter\xEDa.",
569
+ ratesReady: "Opciones de env\xEDo listas ({n})."
567
570
  };
568
571
  var EN3 = {
569
572
  etaOne: "Arrives in 1 business day",
@@ -586,7 +589,10 @@ var EN3 = {
586
589
  moreOptions: "Show more options ({n})",
587
590
  otherCarriers: "Other options",
588
591
  error: "We couldn't get delivery options right now.",
589
- retry: "Try again"
592
+ retry: "Try again",
593
+ freeShipping: "Free shipping",
594
+ freeShippingNote: "Shipping is included in your order: there is no carrier to choose.",
595
+ ratesReady: "Delivery options ready ({n})."
590
596
  };
591
597
  function deliveryMessages(locale, overrides) {
592
598
  const base = (locale ?? "es-MX").toLowerCase().startsWith("en") ? EN3 : ES3;
@@ -679,13 +685,13 @@ function isQuoteChange(err) {
679
685
  }
680
686
  function matchRate(previous, rates) {
681
687
  if (!previous) return null;
682
- const norm2 = (s) => (s ?? "").trim().toLowerCase();
683
- const sameCarrier = (r) => norm2(r.carrier) === norm2(previous.carrier);
688
+ const norm3 = (s) => (s ?? "").trim().toLowerCase();
689
+ const sameCarrier = (r) => norm3(r.carrier) === norm3(previous.carrier);
684
690
  if (previous.service_code) {
685
- const hit = rates.find((r) => sameCarrier(r) && norm2(r.service_code) === norm2(previous.service_code));
691
+ const hit = rates.find((r) => sameCarrier(r) && norm3(r.service_code) === norm3(previous.service_code));
686
692
  if (hit) return hit;
687
693
  }
688
- return rates.find((r) => sameCarrier(r) && norm2(r.title) === norm2(previous.title)) ?? null;
694
+ return rates.find((r) => sameCarrier(r) && norm3(r.title) === norm3(previous.title)) ?? null;
689
695
  }
690
696
  var SR_ONLY = { position: "absolute", width: 1, height: 1, overflow: "hidden", clip: "rect(0 0 0 0)", whiteSpace: "nowrap" };
691
697
  var useHydrated = () => useSyncExternalStore(
@@ -722,7 +728,8 @@ function DeliveryOptions(props) {
722
728
  children,
723
729
  layout = "list",
724
730
  error,
725
- onRetry
731
+ onRetry,
732
+ free = false
726
733
  } = props;
727
734
  const locale = useResolvedLocale(props.locale);
728
735
  const m = useMemo(() => deliveryMessages(locale, props.messages), [locale, props.messages]);
@@ -790,17 +797,24 @@ function DeliveryOptions(props) {
790
797
  ] }, rate.id);
791
798
  };
792
799
  const errorText = error ? typeof error === "string" ? error : m.error : null;
800
+ const freeRate = free && !loading && pickup.length === 0 ? rates.find((r) => !r.pickup) ?? null : null;
793
801
  return /* @__PURE__ */ jsxs("div", { className: cx.root, "data-sf-delivery": true, "data-layout": layout, "aria-busy": loading || void 0, children: [
794
802
  partial && !errorText ? /* @__PURE__ */ jsx("p", { role: "status", className: cx.partial, "data-sf-delivery-partial": true, children: m.partial }) : null,
795
803
  requoted ? /* @__PURE__ */ jsx("p", { role: "status", className: cx.partial, "data-sf-delivery-requoted": true, children: m.requoted }) : null,
796
804
  loading ? /* @__PURE__ */ jsx("span", { style: SR_ONLY, role: "status", "data-sf-delivery-loading": true, children: m.loading }) : null,
805
+ props.announceReady && !loading && rates.length > 0 ? /* @__PURE__ */ jsx("span", { style: SR_ONLY, role: "status", "data-sf-delivery-ready": true, children: freeRate ? m.freeShipping : m.ratesReady.replace("{n}", String(rates.length)) }) : null,
797
806
  errorText && rates.length === 0 && !loading ? /* @__PURE__ */ jsxs("div", { role: "alert", className: cx.error, "data-sf-delivery-error": true, children: [
798
807
  /* @__PURE__ */ jsx("p", { children: errorText }),
799
808
  onRetry ? /* @__PURE__ */ jsx("button", { type: "button", className: cx.retry, onClick: onRetry, "data-sf-delivery-retry": true, children: m.retry }) : null
800
809
  ] }) : null,
810
+ freeRate ? /* @__PURE__ */ jsxs("div", { className: cx.freeBanner, role: props.announceReady ? void 0 : "status", "data-sf-delivery-free-banner": true, children: [
811
+ /* @__PURE__ */ jsx("input", { type: "hidden", name, value: rateValue(freeRate), ...testId ? { "data-testid": testId } : {} }),
812
+ /* @__PURE__ */ jsx("p", { className: cx.free, "data-sf-delivery-free-title": true, children: /* @__PURE__ */ jsx("strong", { children: m.freeShipping }) }),
813
+ /* @__PURE__ */ jsx("p", { className: cx.note, children: m.freeShippingNote })
814
+ ] }) : null,
801
815
  children,
802
816
  loading && rates.length === 0 ? [0, 1, 2].map((i) => /* @__PURE__ */ jsx("div", { className: cx.skeleton, "data-sf-delivery-skeleton": true, "aria-hidden": "true" }, i)) : null,
803
- flat ? flat.map(({ rate, tags }) => rateRow(rate, { tags: showBadges ? tags : [] })) : plan ? plan.recommended.map(({ rate, tags }) => rateRow(rate, { tags: showBadges ? tags : [] })) : rates.map((rate) => rateRow(rate, { tags: badges.get(rate.id) ?? [] })),
817
+ freeRate ? null : flat ? flat.map(({ rate, tags }) => rateRow(rate, { tags: showBadges ? tags : [] })) : plan ? plan.recommended.map(({ rate, tags }) => rateRow(rate, { tags: showBadges ? tags : [] })) : rates.map((rate) => rateRow(rate, { tags: badges.get(rate.id) ?? [] })),
804
818
  plan && plan.moreCount > 0 ? /* @__PURE__ */ jsxs("details", { className: cx.more, "data-sf-delivery-more": true, ...inMore ? { open: true } : {}, children: [
805
819
  /* @__PURE__ */ jsx("summary", { className: cx.moreSummary, "data-sf-delivery-more-summary": true, children: m.moreOptions.replace("{n}", String(plan.moreCount)) }),
806
820
  /* @__PURE__ */ jsx("div", { "data-sf-delivery-groups": true, children: plan.more.map((g) => {
@@ -827,7 +841,141 @@ function DeliveryOptions(props) {
827
841
  deadline ? /* @__PURE__ */ jsx("p", { className: cx.note, "data-sf-delivery-note": true, "data-soon": shownNote?.soon || void 0, "aria-live": "polite", children: shownNote?.text ?? "" }) : null
828
842
  ] });
829
843
  }
844
+ var DEFAULT_QUOTE_NAMES = {
845
+ country_code: "country_code",
846
+ zip: "zip",
847
+ province: "province",
848
+ city: "city",
849
+ neighborhood: "neighborhood",
850
+ address1: "address1"
851
+ };
852
+ var DEFAULT_REQUIRED = ["zip", "province", "city", "neighborhood", "address1"];
853
+ var QUOTE_KEYS = ["country_code", "zip", "province", "city"];
854
+ var norm2 = (v) => v.normalize("NFD").replace(/[̀-ͯ]/g, "").trim().toLowerCase();
855
+ function quoteFingerprint(a) {
856
+ return QUOTE_KEYS.map((k) => norm2(a[k] ?? "")).join("|");
857
+ }
858
+ function defaultRate(delivery) {
859
+ let best = null;
860
+ for (const r of delivery.rates) {
861
+ if (r.pickup) continue;
862
+ if (!best || Number(r.cost.amount) < Number(best.cost.amount)) best = r;
863
+ }
864
+ return best;
865
+ }
866
+ function readQuoteAddress(form, opts = {}) {
867
+ if (!form) return null;
868
+ const names = { ...DEFAULT_QUOTE_NAMES, ...opts.names };
869
+ const data = new FormData(form);
870
+ const get = (name) => String(data.get(name) ?? "").trim();
871
+ const address = {
872
+ country_code: get(names.country_code) || opts.defaultCountry || "MX",
873
+ zip: get(names.zip),
874
+ province: get(names.province),
875
+ city: get(names.city),
876
+ neighborhood: get(names.neighborhood),
877
+ address1: get(names.address1)
878
+ };
879
+ const valid = opts.validPostalCode ?? ((z) => /^\d{5}$/.test(z));
880
+ if (!valid(address.zip)) return null;
881
+ for (const k of opts.required ?? DEFAULT_REQUIRED) if (!address[k]) return null;
882
+ return address;
883
+ }
884
+ function useAutoQuote(options) {
885
+ const { formRef, debounceMs = 600, enabled = true } = options;
886
+ const [status, setStatus] = useState("idle");
887
+ const [delivery, setDelivery] = useState(null);
888
+ const [quotedFor, setQuotedFor] = useState(null);
889
+ const opts = useRef(options);
890
+ opts.current = options;
891
+ const timer = useRef(null);
892
+ const inflight = useRef(null);
893
+ const quotedFp = useRef(null);
894
+ const pendingFp = useRef(null);
895
+ const run = useCallback((address, fp) => {
896
+ inflight.current?.abort();
897
+ const ctl = new AbortController();
898
+ inflight.current = ctl;
899
+ pendingFp.current = fp;
900
+ setStatus("loading");
901
+ opts.current.quote(address, ctl.signal).then((d) => {
902
+ if (ctl.signal.aborted) return;
903
+ quotedFp.current = fp;
904
+ pendingFp.current = null;
905
+ setDelivery(d);
906
+ setQuotedFor(address);
907
+ setStatus("ready");
908
+ opts.current.onQuoted?.(d);
909
+ }).catch(() => {
910
+ if (ctl.signal.aborted) return;
911
+ pendingFp.current = null;
912
+ quotedFp.current = null;
913
+ setStatus("error");
914
+ });
915
+ }, []);
916
+ const evaluate = useCallback(
917
+ (force) => {
918
+ const address = readQuoteAddress(formRef.current, opts.current);
919
+ if (!address) {
920
+ if (timer.current) clearTimeout(timer.current);
921
+ timer.current = null;
922
+ inflight.current?.abort();
923
+ pendingFp.current = null;
924
+ setStatus("idle");
925
+ return;
926
+ }
927
+ const fp = quoteFingerprint(address);
928
+ if (!force && (fp === quotedFp.current || fp === pendingFp.current)) {
929
+ if (timer.current) clearTimeout(timer.current);
930
+ timer.current = null;
931
+ if (fp === quotedFp.current) setStatus("ready");
932
+ return;
933
+ }
934
+ if (timer.current) clearTimeout(timer.current);
935
+ if (force) {
936
+ timer.current = null;
937
+ run(address, fp);
938
+ return;
939
+ }
940
+ setStatus("waiting");
941
+ timer.current = setTimeout(() => {
942
+ timer.current = null;
943
+ const latest = readQuoteAddress(formRef.current, opts.current);
944
+ if (latest) run(latest, quoteFingerprint(latest));
945
+ }, debounceMs);
946
+ },
947
+ [debounceMs, formRef, run]
948
+ );
949
+ useEffect(() => {
950
+ const form = formRef.current;
951
+ if (!enabled || !form) return;
952
+ const onChange = () => evaluate(false);
953
+ form.addEventListener("input", onChange);
954
+ form.addEventListener("change", onChange);
955
+ const first = setTimeout(onChange, 0);
956
+ return () => {
957
+ clearTimeout(first);
958
+ form.removeEventListener("input", onChange);
959
+ form.removeEventListener("change", onChange);
960
+ if (timer.current) clearTimeout(timer.current);
961
+ inflight.current?.abort();
962
+ };
963
+ }, [enabled, evaluate, formRef]);
964
+ const retry = useCallback(() => evaluate(true), [evaluate]);
965
+ const defaultValue = useMemo(() => {
966
+ if (!delivery) return null;
967
+ const saved = opts.current.savedValue;
968
+ if (saved?.startsWith("rate:")) {
969
+ const hit = delivery.rates.find((r) => `rate:${r.id}` === saved);
970
+ if (hit) return `rate:${hit.id}`;
971
+ }
972
+ const pick = (opts.current.pickDefault ?? defaultRate)(delivery);
973
+ return pick ? `rate:${pick.id}` : null;
974
+ }, [delivery, options.savedValue]);
975
+ const announce = status === "loading" || status === "waiting" ? "searching" : status === "error" ? "error" : status === "ready" ? delivery?.free ? "free" : "ready" : null;
976
+ return { status, delivery, defaultValue, quotedFor, retry, announce };
977
+ }
830
978
 
831
- export { ADDRESS_FILL_EVENT_FLAG, AddressFields, ContactFields, DeliveryOptions, OrderSummary, OrderSummaryDisclosure, addressFieldsMessages, dedupeRates, deliveryMessages, flatRates, isAddressFillEvent, isQuoteChange, matchRate, orderSummaryMessages, quoteDeadline, quoteNote, rateBadges, rateCarrier, rateEta, rateService, recommendRates };
979
+ export { ADDRESS_FILL_EVENT_FLAG, AddressFields, ContactFields, DEFAULT_QUOTE_NAMES, DeliveryOptions, OrderSummary, OrderSummaryDisclosure, QUOTE_KEYS, addressFieldsMessages, dedupeRates, defaultRate, deliveryMessages, flatRates, isAddressFillEvent, isQuoteChange, matchRate, orderSummaryMessages, quoteDeadline, quoteFingerprint, quoteNote, rateBadges, rateCarrier, rateEta, rateService, readQuoteAddress, recommendRates, useAutoQuote };
832
980
  //# sourceMappingURL=checkout.js.map
833
981
  //# sourceMappingURL=checkout.js.map