@asteby/storefront-react 0.1.2 → 0.3.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/index.d.cts CHANGED
@@ -1,6 +1,8 @@
1
1
  import * as react from 'react';
2
- import { ReactNode, ComponentPropsWithoutRef, ElementType, ComponentType } from 'react';
3
- import { StorefrontClient, Shop, StorefrontClientOptions, Cart, CartLineInput, CartLineMerchandise, CartLineUpdateInput, CartBuyerInput, AddressInput, DeliveryInput, DeliveryOptions, Image as Image$1, FormatMoneyOptions, Money as Money$1, Product, ProductVariant, Collection, Section, Theme } from '@asteby/storefront';
2
+ import { ReactNode, ComponentPropsWithoutRef, ComponentType, FormEvent } from 'react';
3
+ import { StorefrontClient, Shop, StorefrontClientOptions, Cart, CartLineInput, CartLineMerchandise, CartLineUpdateInput, CartBuyerInput, AddressInput, DeliveryInput, DeliveryOptions, ProductVariant, Product, Image, Collection, Section, Theme, GeoPoint, ShopBranch, Money } from '@asteby/storefront';
4
+ export { D as DEFAULT_IMAGE_WIDTHS, I as Image, a as ImageLoader, b as ImageProps, M as Money, c as MoneyProps, P as ProductPrice, d as ProductPriceProps, e as defaultImageLoader } from './components-B5XhSH-m.cjs';
5
+ import { OpeningHours, GeoPoint as GeoPoint$1, OpenStatus as OpenStatus$1 } from '@asteby/storefront/geo';
4
6
 
5
7
  type StorefrontContextValue = {
6
8
  /** Client for browser-side calls (use a public `pk_` token). */
@@ -123,55 +125,6 @@ declare function useCart(): CartContextValue;
123
125
  /** Cart context or `null` outside a provider. */
124
126
  declare function useOptionalCart(): CartContextValue | null;
125
127
 
126
- type MoneyProps<As extends ElementType = 'span'> = FormatMoneyOptions & {
127
- data: Money$1;
128
- /** Element to render (default `span`). */
129
- as?: As;
130
- /** Overrides the provider locale. */
131
- locale?: string;
132
- } & Omit<ComponentPropsWithoutRef<As>, 'children' | 'as'>;
133
- /** Formats `Money` with the provider locale (or `locale`). */
134
- declare function Money<As extends ElementType = 'span'>({ data, as, locale, withoutTrailingZeros, withoutCurrency, currencyDisplay, ...rest }: MoneyProps<As>): react.ReactElement<any, string | react.JSXElementConstructor<any>>;
135
- declare const DEFAULT_IMAGE_WIDTHS: readonly [320, 480, 640, 768, 1024, 1280, 1600];
136
- /** Builds the URL for one srcset width. */
137
- type ImageLoader = (src: string, width: number, height?: number) => string;
138
- /**
139
- * Default loader: adds `?width=<w>` (the convention of most image CDNs and of
140
- * the OPS image proxy). URLs that cannot be parsed are returned unchanged.
141
- */
142
- declare const defaultImageLoader: ImageLoader;
143
- type ImageProps = Omit<ComponentPropsWithoutRef<'img'>, 'src' | 'srcSet' | 'width' | 'height'> & {
144
- data: Image$1;
145
- /** `sizes` attribute (default `100vw`). */
146
- sizes?: string;
147
- /** Candidate widths for `srcset` (capped at the intrinsic width when known). */
148
- widths?: readonly number[];
149
- /** URL builder per width (default adds `?width=`). Pass `null` to disable srcset. */
150
- loader?: ImageLoader | null;
151
- /** Above-the-fold image: eager loading and `fetchpriority="high"`. */
152
- priority?: boolean;
153
- /** Forces an aspect ratio, e.g. `1/1` or `4/3`, used for width/height and srcset heights. */
154
- aspectRatio?: string;
155
- };
156
- /** Responsive `<img>` with `srcset`, lazy by default, intrinsic size to avoid layout shift. */
157
- declare function Image({ data, sizes, widths, loader, priority, aspectRatio, alt, loading, decoding, ...rest }: ImageProps): react.JSX.Element;
158
- type ProductPriceProps = Omit<ComponentPropsWithoutRef<'div'>, 'children'> & {
159
- product?: Pick<Product, 'price_range' | 'compare_at_price_range'> | null;
160
- variant?: Pick<ProductVariant, 'price' | 'compare_at_price'> | null;
161
- /** Prefix shown when the product has a price range and no variant is selected (default `Desde`). */
162
- fromLabel?: ReactNode;
163
- /** Accessible label for the compare-at price (default `Precio anterior`). */
164
- compareAtLabel?: string;
165
- locale?: string;
166
- withoutTrailingZeros?: boolean;
167
- };
168
- /**
169
- * Price with optional compare-at (strikethrough). With a `variant` it shows
170
- * that variant; otherwise the product's minimum price (prefixed with
171
- * `fromLabel` when prices vary).
172
- */
173
- declare function ProductPrice({ product, variant, fromLabel, compareAtLabel, locale, withoutTrailingZeros, ...rest }: ProductPriceProps): react.JSX.Element | null;
174
-
175
128
  type ProductOptionValueState = {
176
129
  value: string;
177
130
  /** Part of the current selection. */
@@ -284,7 +237,7 @@ type SeoMetaInput = {
284
237
  description?: string | null;
285
238
  /** Canonical absolute URL. */
286
239
  url?: string | null;
287
- image?: Image$1 | string | null;
240
+ image?: Image | string | null;
288
241
  /** `og:type` (default `website`). */
289
242
  type?: string;
290
243
  siteName?: string | null;
@@ -310,7 +263,17 @@ type LocalBusinessOptions = {
310
263
  url?: string;
311
264
  priceRange?: string;
312
265
  };
313
- /** One schema.org `LocalBusiness` (default `Store`) per shop branch. */
266
+ /**
267
+ * schema.org `openingHours` values from structured hours or a legacy string.
268
+ * A string is accepted only if EVERY comma-separated token is strict schema.org
269
+ * syntax (`Mo-Fr 09:00-19:00`); free text such as `Lun-Sáb 8:00 a 18:00` yields
270
+ * `undefined` so no invalid markup is ever emitted.
271
+ */
272
+ declare function openingHoursJsonLd(hours: OpeningHours | string | null | undefined): string[] | undefined;
273
+ /**
274
+ * One schema.org `LocalBusiness` (default `Store`) per shop branch. Hours come
275
+ * from `opening_hours` when present, else from the legacy `hours` string.
276
+ */
314
277
  declare function localBusinessJsonLd(shop: Shop, options?: LocalBusinessOptions): JsonLdObject[];
315
278
  type BreadcrumbItem = {
316
279
  name: string;
@@ -481,4 +444,215 @@ type ThemeStyleProps = ThemeCssOptions & {
481
444
  /** Emits the theme variables as a `<style>` (render it in `<head>` from the root route). */
482
445
  declare function ThemeStyle({ theme, nonce, id, ...options }: ThemeStyleProps): react.JSX.Element | null;
483
446
 
484
- export { AddToCartButton, type AddToCartButtonProps, type BreadcrumbItem, CART_COOKIE_NAME, type CartContextValue, type CartCookieOptions, type CartLineAddInput, CartProvider, type CartProviderProps, type CartStatus, DEFAULT_IMAGE_WIDTHS, type FaqItem, Image, type ImageLoader, type ImageProps, type InferSectionProps, JsonLd, type JsonLdObject, type JsonLdProps, type LocalBusinessOptions, type MetaDescriptor, Money, type MoneyProps, type ProductOptionState, type ProductOptionValueState, type ProductOptionsState, ProductPrice, type ProductPriceProps, type SectionComponentProps, type SectionDefinition, type SectionPropError, type SectionPropKind, type SectionPropSchema, SectionRegistry, type SectionSchema, Sections, type SectionsProps, type SeoMetaInput, type StorefrontContextValue, StorefrontProvider, type StorefrontProviderProps, type ThemeCssOptions, ThemeStyle, type ThemeStyleProps, type UseProductOptionsConfig, VariantSelector, type VariantSelectorProps, type VariantSelectorRenderProps, breadcrumbJsonLd, collectionJsonLd, collectionMeta, createSectionRegistry, defaultImageLoader, defineSection, faqJsonLd, getCartIdFromCookie, isSafeCssValue, localBusinessJsonLd, optimisticCart, organizationJsonLd, productJsonLd, productMeta, readCartCookie, resolveSectionProps, sectionJsonSchema, seoMeta, serializeCartCookie, serializeJsonLd, themeFontsHref, themeToCss, themeToCssVariables, toMerchandise, tokenName, useCart, useOptionalCart, useOptionalStorefront, useProductOptions, useStorefront, useStorefrontClient, writeCartCookie };
447
+ type GeolocationErrorCode = 'unsupported' | 'insecure' | 'denied' | 'unavailable' | 'timeout';
448
+ /** Typed failure of `request()`. `message` is a ready-to-show es-MX text. */
449
+ type GeolocationFailure = {
450
+ code: GeolocationErrorCode;
451
+ message: string;
452
+ };
453
+ /** State of the browser permission. `unknown` when the Permissions API is missing. */
454
+ type GeolocationPermission = 'granted' | 'denied' | 'prompt' | 'unknown';
455
+ type UseGeolocationOptions = {
456
+ enableHighAccuracy?: boolean;
457
+ /** Give up after this long (default 10 s). */
458
+ timeoutMs?: number;
459
+ /** Accept a cached position up to this old (default 5 min). */
460
+ maximumAge?: number;
461
+ };
462
+ type UseGeolocationResult = {
463
+ /** `false` on the server and until mounted, so SSR and hydration agree. */
464
+ supported: boolean;
465
+ permission: GeolocationPermission;
466
+ status: 'idle' | 'locating' | 'success' | 'error';
467
+ position: GeoPoint | null;
468
+ error: GeolocationFailure | null;
469
+ /**
470
+ * Asks the browser for the position. Call it ONLY from a user gesture (a button's onClick): the hook never
471
+ * asks by itself. Resolves to the point, or `null` on failure (see `error`).
472
+ */
473
+ request: () => Promise<GeoPoint | null>;
474
+ reset: () => void;
475
+ };
476
+ /**
477
+ * Browser geolocation that only runs under a user action. Reading the permission state (when the Permissions
478
+ * API exists) does not prompt. The point stays in React state: it is never stored or sent anywhere here.
479
+ */
480
+ declare function useGeolocation(options?: UseGeolocationOptions): UseGeolocationResult;
481
+
482
+ type NearbyLocation<L extends ShopBranch = ShopBranch> = L & {
483
+ distance_km: number | null;
484
+ };
485
+ type UseNearestLocationsOptions<L extends ShopBranch = ShopBranch> = {
486
+ /** The buyer's point (from `useGeolocation().request()`). */
487
+ origin?: GeoPoint$1 | null;
488
+ /** Postal code typed by the buyer (5 digits). The server resolves it to coordinates, so this always asks the server. */
489
+ postalCode?: string | null;
490
+ /** Branches to rank. Defaults to `shop.branches` from the provider. */
491
+ locations?: readonly L[];
492
+ /** Keep the closest N. */
493
+ limit?: number;
494
+ /** Keep those within this many km. */
495
+ radiusKm?: number;
496
+ /** Force the server (`locations.nearest`) for `origin` instead of ranking `locations` in the browser. */
497
+ remote?: boolean;
498
+ /** Defaults to the provider's client. */
499
+ client?: StorefrontClient;
500
+ };
501
+ type UseNearestLocationsResult<L extends ShopBranch = ShopBranch> = {
502
+ locations: NearbyLocation<L>[];
503
+ status: 'idle' | 'loading' | 'success' | 'error';
504
+ error: Error | null;
505
+ /** `local`: ranked in the browser, the point never leaves it. `remote`: answered by the server. */
506
+ source: 'local' | 'remote' | null;
507
+ };
508
+ /**
509
+ * Branches ordered by distance. With an `origin` and branches at hand (explicit or `shop.branches`) the ranking
510
+ * runs in the browser with haversine and the coordinates are sent nowhere. `postalCode` (or `remote`) asks the
511
+ * server, which rounds and never stores the point. With zero branches, or neither origin nor postal code, the
512
+ * result is empty and `idle`.
513
+ */
514
+ declare function useNearestLocations<L extends ShopBranch = ShopBranch>(options?: UseNearestLocationsOptions<L>): UseNearestLocationsResult<L>;
515
+
516
+ /** Query param that carries the chosen branch (also the default `name` of `<LocationPicker>`). */
517
+ declare const SELECTED_LOCATION_PARAM = "sucursal";
518
+ /** localStorage key of "Mi sucursal". Never a cookie: the choice is per browser and is not sent to the server. */
519
+ declare const SELECTED_LOCATION_STORAGE_KEY = "sf_sucursal";
520
+ /**
521
+ * The saved branch id: `?sucursal=` wins over localStorage. Pure helper for loaders and tests; on the server
522
+ * pass the request's search string (`new URL(request.url).search`) and no storage.
523
+ */
524
+ declare function readSelectedLocationId({ search, store, param, storageKey }?: {
525
+ search?: string;
526
+ store?: Pick<Storage, 'getItem'> | null;
527
+ param?: string;
528
+ storageKey?: string;
529
+ }): string | null;
530
+ type UseSelectedLocationOptions = {
531
+ /** Branches to validate against. Defaults to `shop.branches` from `<StorefrontProvider>`. */
532
+ locations?: readonly ShopBranch[];
533
+ /** Id already known on the server (from `?sucursal=`), so SSR and hydration render the same. */
534
+ initialId?: string | null;
535
+ param?: string;
536
+ storageKey?: string;
537
+ /** Mirror the choice in the address bar with `history.replaceState` (default true). */
538
+ syncUrl?: boolean;
539
+ };
540
+ type UseSelectedLocationResult<L extends ShopBranch = ShopBranch> = {
541
+ id: string | null;
542
+ location: L | null;
543
+ /** `false` until the saved choice has been read on the client. */
544
+ ready: boolean;
545
+ select: (id: string) => void;
546
+ clear: () => void;
547
+ };
548
+ /**
549
+ * "Mi sucursal": the chosen branch lives in the URL (`?sucursal=<id>`, shareable) and in localStorage. It is
550
+ * never written to a cookie, so it does not travel with every request. An id that is not among `locations`
551
+ * is ignored and forgotten; with zero branches nothing is ever selected.
552
+ */
553
+ declare function useSelectedLocation<L extends ShopBranch = ShopBranch>(options?: UseSelectedLocationOptions & {
554
+ locations?: readonly L[];
555
+ }): UseSelectedLocationResult<L>;
556
+
557
+ type LocationItemState = {
558
+ index: number;
559
+ selected: boolean;
560
+ };
561
+ type LocationListProps<L extends ShopBranch = ShopBranch> = {
562
+ locations?: readonly L[];
563
+ /** Marks one item with `data-selected` / `aria-current`. */
564
+ selectedId?: string | null;
565
+ /** Renders each item's content. Defaults to name and address. */
566
+ children?: (location: L, state: LocationItemState) => ReactNode;
567
+ } & Omit<ComponentPropsWithoutRef<'ul'>, 'children'>;
568
+ /** `<ul>` of branches; the content of every `<li>` is yours (render prop). */
569
+ declare function LocationList<L extends ShopBranch = ShopBranch>({ locations, selectedId, children, ...rest }: LocationListProps<L>): react.JSX.Element | null;
570
+ type LocationPickerProps<L extends ShopBranch = ShopBranch> = {
571
+ /** Accessible name of the group (rendered as `<legend>`). */
572
+ legend: ReactNode;
573
+ locations?: readonly L[];
574
+ /** Controlled value (branch id). */
575
+ value?: string | null;
576
+ defaultValue?: string | null;
577
+ onChange?: (id: string, location: L) => void;
578
+ /** Radio `name` (default `sucursal`, the same param `useSelectedLocation` reads). */
579
+ name?: string;
580
+ /** Content of each option next to its radio. Defaults to name and address. */
581
+ children?: (location: L, state: {
582
+ checked: boolean;
583
+ }) => ReactNode;
584
+ } & Omit<ComponentPropsWithoutRef<'fieldset'>, 'children' | 'onChange' | 'defaultValue'>;
585
+ /**
586
+ * `<fieldset>` of native radio buttons: keyboard and screen-reader behaviour comes from the browser, and inside
587
+ * a GET `<form>` it submits `?sucursal=<id>` without JavaScript.
588
+ */
589
+ declare function LocationPicker<L extends ShopBranch = ShopBranch>({ legend, locations, value, defaultValue, onChange, name, children, ...rest }: LocationPickerProps<L>): react.JSX.Element | null;
590
+ type OpenStatusLabels = {
591
+ open: string;
592
+ closed: string;
593
+ };
594
+ type OpenStatusProps = {
595
+ /** Reads `opening_hours` and `timezone` from it. */
596
+ location?: Pick<ShopBranch, 'opening_hours' | 'timezone'>;
597
+ hours?: OpeningHours;
598
+ /** IANA zone (default: the branch's, else `America/Mexico_City`). */
599
+ timezone?: string;
600
+ /** Fixed instant, for tests and SSR snapshots. Without it the status is computed after mount and refreshed every minute. */
601
+ now?: Date;
602
+ labels?: Partial<OpenStatusLabels>;
603
+ children?: (status: OpenStatus$1) => ReactNode;
604
+ } & Omit<ComponentPropsWithoutRef<'span'>, 'children'>;
605
+ /**
606
+ * "Abierto" / "Cerrado" from `openStatus`. The clock makes server and client disagree, so without `now` it
607
+ * renders nothing until mounted. Renders `null` without hours or with an unknown time zone.
608
+ */
609
+ declare function OpenStatus({ location, hours, timezone, now, labels, children, ...rest }: OpenStatusProps): react.JSX.Element | null;
610
+ type DirectionsLinkProps = {
611
+ location: Pick<ShopBranch, 'lat' | 'lng' | 'address' | 'city'>;
612
+ provider?: 'google' | 'apple' | 'geo';
613
+ children?: ReactNode;
614
+ } & Omit<ComponentPropsWithoutRef<'a'>, 'href' | 'children'>;
615
+ /** Link to directions in the maps app. `null` when the branch has neither coordinates nor address. */
616
+ declare function DirectionsLink({ location, provider, children, ...rest }: DirectionsLinkProps): react.JSX.Element | null;
617
+ type PickupInfo<L extends ShopBranch = ShopBranch> = {
618
+ location: L;
619
+ /** `null` = free. Amount as sent by OPS; nothing is calculated here. */
620
+ fee: Money | null;
621
+ free: boolean;
622
+ /** Formatted `fee` (`null` when free). */
623
+ feeText: string | null;
624
+ prepDays: number;
625
+ };
626
+ type PickupOptionProps<L extends ShopBranch = ShopBranch> = {
627
+ location: L;
628
+ locale?: string;
629
+ children?: (info: PickupInfo<L>) => ReactNode;
630
+ } & Omit<ComponentPropsWithoutRef<'span'>, 'children'>;
631
+ /** "Recoge en sucursal" terms: fee and preparation time. `null` when the branch does not offer pickup. */
632
+ declare function PickupOption<L extends ShopBranch = ShopBranch>({ location, locale, children, ...rest }: PickupOptionProps<L>): react.JSX.Element | null;
633
+ type NearestLocationFormLabels = {
634
+ postalCode: string;
635
+ submit: string;
636
+ locate: string;
637
+ locating: string;
638
+ };
639
+ type NearestLocationFormProps<L extends ShopBranch = ShopBranch> = {
640
+ locations?: readonly L[];
641
+ /** Destination of the GET (default: the current page). */
642
+ action?: string;
643
+ /** Query param and input name (default `cp`). */
644
+ name?: string;
645
+ defaultValue?: string;
646
+ /** With JavaScript: intercept the submit and receive the postal code. Without it the browser navigates to `?cp=`. */
647
+ onSubmitPostalCode?: (postalCode: string, event: FormEvent<HTMLFormElement>) => void;
648
+ /** Shows a "use my location" button (only when the browser supports it); it asks permission on click. */
649
+ onLocate?: (point: GeoPoint$1) => void;
650
+ labels?: Partial<NearestLocationFormLabels>;
651
+ } & Omit<ComponentPropsWithoutRef<'form'>, 'action' | 'method' | 'onSubmit' | 'defaultValue'>;
652
+ /**
653
+ * `<form method="get">` with a postal-code field: works without JavaScript (navigates to `?cp=06000`). Add
654
+ * `onSubmitPostalCode` / `onLocate` to enhance it. Renders `null` when there are no branches.
655
+ */
656
+ declare function NearestLocationForm<L extends ShopBranch = ShopBranch>({ locations, action, name, defaultValue, onSubmitPostalCode, onLocate, labels, children, ...rest }: NearestLocationFormProps<L>): react.JSX.Element | null;
657
+
658
+ export { AddToCartButton, type AddToCartButtonProps, type BreadcrumbItem, CART_COOKIE_NAME, type CartContextValue, type CartCookieOptions, type CartLineAddInput, CartProvider, type CartProviderProps, type CartStatus, DirectionsLink, type DirectionsLinkProps, type FaqItem, type GeolocationErrorCode, type GeolocationFailure, type GeolocationPermission, type InferSectionProps, JsonLd, type JsonLdObject, type JsonLdProps, type LocalBusinessOptions, type LocationItemState, LocationList, type LocationListProps, LocationPicker, type LocationPickerProps, type MetaDescriptor, type NearbyLocation, NearestLocationForm, type NearestLocationFormLabels, type NearestLocationFormProps, OpenStatus, type OpenStatusLabels, type OpenStatusProps, type PickupInfo, PickupOption, type PickupOptionProps, type ProductOptionState, type ProductOptionValueState, type ProductOptionsState, SELECTED_LOCATION_PARAM, SELECTED_LOCATION_STORAGE_KEY, type SectionComponentProps, type SectionDefinition, type SectionPropError, type SectionPropKind, type SectionPropSchema, SectionRegistry, type SectionSchema, Sections, type SectionsProps, type SeoMetaInput, type StorefrontContextValue, StorefrontProvider, type StorefrontProviderProps, type ThemeCssOptions, ThemeStyle, type ThemeStyleProps, type UseGeolocationOptions, type UseGeolocationResult, type UseNearestLocationsOptions, type UseNearestLocationsResult, type UseProductOptionsConfig, type UseSelectedLocationOptions, type UseSelectedLocationResult, VariantSelector, type VariantSelectorProps, type VariantSelectorRenderProps, breadcrumbJsonLd, collectionJsonLd, collectionMeta, createSectionRegistry, defineSection, faqJsonLd, getCartIdFromCookie, isSafeCssValue, localBusinessJsonLd, openingHoursJsonLd, optimisticCart, organizationJsonLd, productJsonLd, productMeta, readCartCookie, readSelectedLocationId, resolveSectionProps, sectionJsonSchema, seoMeta, serializeCartCookie, serializeJsonLd, themeFontsHref, themeToCss, themeToCssVariables, toMerchandise, tokenName, useCart, useGeolocation, useNearestLocations, useOptionalCart, useOptionalStorefront, useProductOptions, useSelectedLocation, useStorefront, useStorefrontClient, writeCartCookie };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import * as react from 'react';
2
- import { ReactNode, ComponentPropsWithoutRef, ElementType, ComponentType } from 'react';
3
- import { StorefrontClient, Shop, StorefrontClientOptions, Cart, CartLineInput, CartLineMerchandise, CartLineUpdateInput, CartBuyerInput, AddressInput, DeliveryInput, DeliveryOptions, Image as Image$1, FormatMoneyOptions, Money as Money$1, Product, ProductVariant, Collection, Section, Theme } from '@asteby/storefront';
2
+ import { ReactNode, ComponentPropsWithoutRef, ComponentType, FormEvent } from 'react';
3
+ import { StorefrontClient, Shop, StorefrontClientOptions, Cart, CartLineInput, CartLineMerchandise, CartLineUpdateInput, CartBuyerInput, AddressInput, DeliveryInput, DeliveryOptions, ProductVariant, Product, Image, Collection, Section, Theme, GeoPoint, ShopBranch, Money } from '@asteby/storefront';
4
+ export { D as DEFAULT_IMAGE_WIDTHS, I as Image, a as ImageLoader, b as ImageProps, M as Money, c as MoneyProps, P as ProductPrice, d as ProductPriceProps, e as defaultImageLoader } from './components-B5XhSH-m.js';
5
+ import { OpeningHours, GeoPoint as GeoPoint$1, OpenStatus as OpenStatus$1 } from '@asteby/storefront/geo';
4
6
 
5
7
  type StorefrontContextValue = {
6
8
  /** Client for browser-side calls (use a public `pk_` token). */
@@ -123,55 +125,6 @@ declare function useCart(): CartContextValue;
123
125
  /** Cart context or `null` outside a provider. */
124
126
  declare function useOptionalCart(): CartContextValue | null;
125
127
 
126
- type MoneyProps<As extends ElementType = 'span'> = FormatMoneyOptions & {
127
- data: Money$1;
128
- /** Element to render (default `span`). */
129
- as?: As;
130
- /** Overrides the provider locale. */
131
- locale?: string;
132
- } & Omit<ComponentPropsWithoutRef<As>, 'children' | 'as'>;
133
- /** Formats `Money` with the provider locale (or `locale`). */
134
- declare function Money<As extends ElementType = 'span'>({ data, as, locale, withoutTrailingZeros, withoutCurrency, currencyDisplay, ...rest }: MoneyProps<As>): react.ReactElement<any, string | react.JSXElementConstructor<any>>;
135
- declare const DEFAULT_IMAGE_WIDTHS: readonly [320, 480, 640, 768, 1024, 1280, 1600];
136
- /** Builds the URL for one srcset width. */
137
- type ImageLoader = (src: string, width: number, height?: number) => string;
138
- /**
139
- * Default loader: adds `?width=<w>` (the convention of most image CDNs and of
140
- * the OPS image proxy). URLs that cannot be parsed are returned unchanged.
141
- */
142
- declare const defaultImageLoader: ImageLoader;
143
- type ImageProps = Omit<ComponentPropsWithoutRef<'img'>, 'src' | 'srcSet' | 'width' | 'height'> & {
144
- data: Image$1;
145
- /** `sizes` attribute (default `100vw`). */
146
- sizes?: string;
147
- /** Candidate widths for `srcset` (capped at the intrinsic width when known). */
148
- widths?: readonly number[];
149
- /** URL builder per width (default adds `?width=`). Pass `null` to disable srcset. */
150
- loader?: ImageLoader | null;
151
- /** Above-the-fold image: eager loading and `fetchpriority="high"`. */
152
- priority?: boolean;
153
- /** Forces an aspect ratio, e.g. `1/1` or `4/3`, used for width/height and srcset heights. */
154
- aspectRatio?: string;
155
- };
156
- /** Responsive `<img>` with `srcset`, lazy by default, intrinsic size to avoid layout shift. */
157
- declare function Image({ data, sizes, widths, loader, priority, aspectRatio, alt, loading, decoding, ...rest }: ImageProps): react.JSX.Element;
158
- type ProductPriceProps = Omit<ComponentPropsWithoutRef<'div'>, 'children'> & {
159
- product?: Pick<Product, 'price_range' | 'compare_at_price_range'> | null;
160
- variant?: Pick<ProductVariant, 'price' | 'compare_at_price'> | null;
161
- /** Prefix shown when the product has a price range and no variant is selected (default `Desde`). */
162
- fromLabel?: ReactNode;
163
- /** Accessible label for the compare-at price (default `Precio anterior`). */
164
- compareAtLabel?: string;
165
- locale?: string;
166
- withoutTrailingZeros?: boolean;
167
- };
168
- /**
169
- * Price with optional compare-at (strikethrough). With a `variant` it shows
170
- * that variant; otherwise the product's minimum price (prefixed with
171
- * `fromLabel` when prices vary).
172
- */
173
- declare function ProductPrice({ product, variant, fromLabel, compareAtLabel, locale, withoutTrailingZeros, ...rest }: ProductPriceProps): react.JSX.Element | null;
174
-
175
128
  type ProductOptionValueState = {
176
129
  value: string;
177
130
  /** Part of the current selection. */
@@ -284,7 +237,7 @@ type SeoMetaInput = {
284
237
  description?: string | null;
285
238
  /** Canonical absolute URL. */
286
239
  url?: string | null;
287
- image?: Image$1 | string | null;
240
+ image?: Image | string | null;
288
241
  /** `og:type` (default `website`). */
289
242
  type?: string;
290
243
  siteName?: string | null;
@@ -310,7 +263,17 @@ type LocalBusinessOptions = {
310
263
  url?: string;
311
264
  priceRange?: string;
312
265
  };
313
- /** One schema.org `LocalBusiness` (default `Store`) per shop branch. */
266
+ /**
267
+ * schema.org `openingHours` values from structured hours or a legacy string.
268
+ * A string is accepted only if EVERY comma-separated token is strict schema.org
269
+ * syntax (`Mo-Fr 09:00-19:00`); free text such as `Lun-Sáb 8:00 a 18:00` yields
270
+ * `undefined` so no invalid markup is ever emitted.
271
+ */
272
+ declare function openingHoursJsonLd(hours: OpeningHours | string | null | undefined): string[] | undefined;
273
+ /**
274
+ * One schema.org `LocalBusiness` (default `Store`) per shop branch. Hours come
275
+ * from `opening_hours` when present, else from the legacy `hours` string.
276
+ */
314
277
  declare function localBusinessJsonLd(shop: Shop, options?: LocalBusinessOptions): JsonLdObject[];
315
278
  type BreadcrumbItem = {
316
279
  name: string;
@@ -481,4 +444,215 @@ type ThemeStyleProps = ThemeCssOptions & {
481
444
  /** Emits the theme variables as a `<style>` (render it in `<head>` from the root route). */
482
445
  declare function ThemeStyle({ theme, nonce, id, ...options }: ThemeStyleProps): react.JSX.Element | null;
483
446
 
484
- export { AddToCartButton, type AddToCartButtonProps, type BreadcrumbItem, CART_COOKIE_NAME, type CartContextValue, type CartCookieOptions, type CartLineAddInput, CartProvider, type CartProviderProps, type CartStatus, DEFAULT_IMAGE_WIDTHS, type FaqItem, Image, type ImageLoader, type ImageProps, type InferSectionProps, JsonLd, type JsonLdObject, type JsonLdProps, type LocalBusinessOptions, type MetaDescriptor, Money, type MoneyProps, type ProductOptionState, type ProductOptionValueState, type ProductOptionsState, ProductPrice, type ProductPriceProps, type SectionComponentProps, type SectionDefinition, type SectionPropError, type SectionPropKind, type SectionPropSchema, SectionRegistry, type SectionSchema, Sections, type SectionsProps, type SeoMetaInput, type StorefrontContextValue, StorefrontProvider, type StorefrontProviderProps, type ThemeCssOptions, ThemeStyle, type ThemeStyleProps, type UseProductOptionsConfig, VariantSelector, type VariantSelectorProps, type VariantSelectorRenderProps, breadcrumbJsonLd, collectionJsonLd, collectionMeta, createSectionRegistry, defaultImageLoader, defineSection, faqJsonLd, getCartIdFromCookie, isSafeCssValue, localBusinessJsonLd, optimisticCart, organizationJsonLd, productJsonLd, productMeta, readCartCookie, resolveSectionProps, sectionJsonSchema, seoMeta, serializeCartCookie, serializeJsonLd, themeFontsHref, themeToCss, themeToCssVariables, toMerchandise, tokenName, useCart, useOptionalCart, useOptionalStorefront, useProductOptions, useStorefront, useStorefrontClient, writeCartCookie };
447
+ type GeolocationErrorCode = 'unsupported' | 'insecure' | 'denied' | 'unavailable' | 'timeout';
448
+ /** Typed failure of `request()`. `message` is a ready-to-show es-MX text. */
449
+ type GeolocationFailure = {
450
+ code: GeolocationErrorCode;
451
+ message: string;
452
+ };
453
+ /** State of the browser permission. `unknown` when the Permissions API is missing. */
454
+ type GeolocationPermission = 'granted' | 'denied' | 'prompt' | 'unknown';
455
+ type UseGeolocationOptions = {
456
+ enableHighAccuracy?: boolean;
457
+ /** Give up after this long (default 10 s). */
458
+ timeoutMs?: number;
459
+ /** Accept a cached position up to this old (default 5 min). */
460
+ maximumAge?: number;
461
+ };
462
+ type UseGeolocationResult = {
463
+ /** `false` on the server and until mounted, so SSR and hydration agree. */
464
+ supported: boolean;
465
+ permission: GeolocationPermission;
466
+ status: 'idle' | 'locating' | 'success' | 'error';
467
+ position: GeoPoint | null;
468
+ error: GeolocationFailure | null;
469
+ /**
470
+ * Asks the browser for the position. Call it ONLY from a user gesture (a button's onClick): the hook never
471
+ * asks by itself. Resolves to the point, or `null` on failure (see `error`).
472
+ */
473
+ request: () => Promise<GeoPoint | null>;
474
+ reset: () => void;
475
+ };
476
+ /**
477
+ * Browser geolocation that only runs under a user action. Reading the permission state (when the Permissions
478
+ * API exists) does not prompt. The point stays in React state: it is never stored or sent anywhere here.
479
+ */
480
+ declare function useGeolocation(options?: UseGeolocationOptions): UseGeolocationResult;
481
+
482
+ type NearbyLocation<L extends ShopBranch = ShopBranch> = L & {
483
+ distance_km: number | null;
484
+ };
485
+ type UseNearestLocationsOptions<L extends ShopBranch = ShopBranch> = {
486
+ /** The buyer's point (from `useGeolocation().request()`). */
487
+ origin?: GeoPoint$1 | null;
488
+ /** Postal code typed by the buyer (5 digits). The server resolves it to coordinates, so this always asks the server. */
489
+ postalCode?: string | null;
490
+ /** Branches to rank. Defaults to `shop.branches` from the provider. */
491
+ locations?: readonly L[];
492
+ /** Keep the closest N. */
493
+ limit?: number;
494
+ /** Keep those within this many km. */
495
+ radiusKm?: number;
496
+ /** Force the server (`locations.nearest`) for `origin` instead of ranking `locations` in the browser. */
497
+ remote?: boolean;
498
+ /** Defaults to the provider's client. */
499
+ client?: StorefrontClient;
500
+ };
501
+ type UseNearestLocationsResult<L extends ShopBranch = ShopBranch> = {
502
+ locations: NearbyLocation<L>[];
503
+ status: 'idle' | 'loading' | 'success' | 'error';
504
+ error: Error | null;
505
+ /** `local`: ranked in the browser, the point never leaves it. `remote`: answered by the server. */
506
+ source: 'local' | 'remote' | null;
507
+ };
508
+ /**
509
+ * Branches ordered by distance. With an `origin` and branches at hand (explicit or `shop.branches`) the ranking
510
+ * runs in the browser with haversine and the coordinates are sent nowhere. `postalCode` (or `remote`) asks the
511
+ * server, which rounds and never stores the point. With zero branches, or neither origin nor postal code, the
512
+ * result is empty and `idle`.
513
+ */
514
+ declare function useNearestLocations<L extends ShopBranch = ShopBranch>(options?: UseNearestLocationsOptions<L>): UseNearestLocationsResult<L>;
515
+
516
+ /** Query param that carries the chosen branch (also the default `name` of `<LocationPicker>`). */
517
+ declare const SELECTED_LOCATION_PARAM = "sucursal";
518
+ /** localStorage key of "Mi sucursal". Never a cookie: the choice is per browser and is not sent to the server. */
519
+ declare const SELECTED_LOCATION_STORAGE_KEY = "sf_sucursal";
520
+ /**
521
+ * The saved branch id: `?sucursal=` wins over localStorage. Pure helper for loaders and tests; on the server
522
+ * pass the request's search string (`new URL(request.url).search`) and no storage.
523
+ */
524
+ declare function readSelectedLocationId({ search, store, param, storageKey }?: {
525
+ search?: string;
526
+ store?: Pick<Storage, 'getItem'> | null;
527
+ param?: string;
528
+ storageKey?: string;
529
+ }): string | null;
530
+ type UseSelectedLocationOptions = {
531
+ /** Branches to validate against. Defaults to `shop.branches` from `<StorefrontProvider>`. */
532
+ locations?: readonly ShopBranch[];
533
+ /** Id already known on the server (from `?sucursal=`), so SSR and hydration render the same. */
534
+ initialId?: string | null;
535
+ param?: string;
536
+ storageKey?: string;
537
+ /** Mirror the choice in the address bar with `history.replaceState` (default true). */
538
+ syncUrl?: boolean;
539
+ };
540
+ type UseSelectedLocationResult<L extends ShopBranch = ShopBranch> = {
541
+ id: string | null;
542
+ location: L | null;
543
+ /** `false` until the saved choice has been read on the client. */
544
+ ready: boolean;
545
+ select: (id: string) => void;
546
+ clear: () => void;
547
+ };
548
+ /**
549
+ * "Mi sucursal": the chosen branch lives in the URL (`?sucursal=<id>`, shareable) and in localStorage. It is
550
+ * never written to a cookie, so it does not travel with every request. An id that is not among `locations`
551
+ * is ignored and forgotten; with zero branches nothing is ever selected.
552
+ */
553
+ declare function useSelectedLocation<L extends ShopBranch = ShopBranch>(options?: UseSelectedLocationOptions & {
554
+ locations?: readonly L[];
555
+ }): UseSelectedLocationResult<L>;
556
+
557
+ type LocationItemState = {
558
+ index: number;
559
+ selected: boolean;
560
+ };
561
+ type LocationListProps<L extends ShopBranch = ShopBranch> = {
562
+ locations?: readonly L[];
563
+ /** Marks one item with `data-selected` / `aria-current`. */
564
+ selectedId?: string | null;
565
+ /** Renders each item's content. Defaults to name and address. */
566
+ children?: (location: L, state: LocationItemState) => ReactNode;
567
+ } & Omit<ComponentPropsWithoutRef<'ul'>, 'children'>;
568
+ /** `<ul>` of branches; the content of every `<li>` is yours (render prop). */
569
+ declare function LocationList<L extends ShopBranch = ShopBranch>({ locations, selectedId, children, ...rest }: LocationListProps<L>): react.JSX.Element | null;
570
+ type LocationPickerProps<L extends ShopBranch = ShopBranch> = {
571
+ /** Accessible name of the group (rendered as `<legend>`). */
572
+ legend: ReactNode;
573
+ locations?: readonly L[];
574
+ /** Controlled value (branch id). */
575
+ value?: string | null;
576
+ defaultValue?: string | null;
577
+ onChange?: (id: string, location: L) => void;
578
+ /** Radio `name` (default `sucursal`, the same param `useSelectedLocation` reads). */
579
+ name?: string;
580
+ /** Content of each option next to its radio. Defaults to name and address. */
581
+ children?: (location: L, state: {
582
+ checked: boolean;
583
+ }) => ReactNode;
584
+ } & Omit<ComponentPropsWithoutRef<'fieldset'>, 'children' | 'onChange' | 'defaultValue'>;
585
+ /**
586
+ * `<fieldset>` of native radio buttons: keyboard and screen-reader behaviour comes from the browser, and inside
587
+ * a GET `<form>` it submits `?sucursal=<id>` without JavaScript.
588
+ */
589
+ declare function LocationPicker<L extends ShopBranch = ShopBranch>({ legend, locations, value, defaultValue, onChange, name, children, ...rest }: LocationPickerProps<L>): react.JSX.Element | null;
590
+ type OpenStatusLabels = {
591
+ open: string;
592
+ closed: string;
593
+ };
594
+ type OpenStatusProps = {
595
+ /** Reads `opening_hours` and `timezone` from it. */
596
+ location?: Pick<ShopBranch, 'opening_hours' | 'timezone'>;
597
+ hours?: OpeningHours;
598
+ /** IANA zone (default: the branch's, else `America/Mexico_City`). */
599
+ timezone?: string;
600
+ /** Fixed instant, for tests and SSR snapshots. Without it the status is computed after mount and refreshed every minute. */
601
+ now?: Date;
602
+ labels?: Partial<OpenStatusLabels>;
603
+ children?: (status: OpenStatus$1) => ReactNode;
604
+ } & Omit<ComponentPropsWithoutRef<'span'>, 'children'>;
605
+ /**
606
+ * "Abierto" / "Cerrado" from `openStatus`. The clock makes server and client disagree, so without `now` it
607
+ * renders nothing until mounted. Renders `null` without hours or with an unknown time zone.
608
+ */
609
+ declare function OpenStatus({ location, hours, timezone, now, labels, children, ...rest }: OpenStatusProps): react.JSX.Element | null;
610
+ type DirectionsLinkProps = {
611
+ location: Pick<ShopBranch, 'lat' | 'lng' | 'address' | 'city'>;
612
+ provider?: 'google' | 'apple' | 'geo';
613
+ children?: ReactNode;
614
+ } & Omit<ComponentPropsWithoutRef<'a'>, 'href' | 'children'>;
615
+ /** Link to directions in the maps app. `null` when the branch has neither coordinates nor address. */
616
+ declare function DirectionsLink({ location, provider, children, ...rest }: DirectionsLinkProps): react.JSX.Element | null;
617
+ type PickupInfo<L extends ShopBranch = ShopBranch> = {
618
+ location: L;
619
+ /** `null` = free. Amount as sent by OPS; nothing is calculated here. */
620
+ fee: Money | null;
621
+ free: boolean;
622
+ /** Formatted `fee` (`null` when free). */
623
+ feeText: string | null;
624
+ prepDays: number;
625
+ };
626
+ type PickupOptionProps<L extends ShopBranch = ShopBranch> = {
627
+ location: L;
628
+ locale?: string;
629
+ children?: (info: PickupInfo<L>) => ReactNode;
630
+ } & Omit<ComponentPropsWithoutRef<'span'>, 'children'>;
631
+ /** "Recoge en sucursal" terms: fee and preparation time. `null` when the branch does not offer pickup. */
632
+ declare function PickupOption<L extends ShopBranch = ShopBranch>({ location, locale, children, ...rest }: PickupOptionProps<L>): react.JSX.Element | null;
633
+ type NearestLocationFormLabels = {
634
+ postalCode: string;
635
+ submit: string;
636
+ locate: string;
637
+ locating: string;
638
+ };
639
+ type NearestLocationFormProps<L extends ShopBranch = ShopBranch> = {
640
+ locations?: readonly L[];
641
+ /** Destination of the GET (default: the current page). */
642
+ action?: string;
643
+ /** Query param and input name (default `cp`). */
644
+ name?: string;
645
+ defaultValue?: string;
646
+ /** With JavaScript: intercept the submit and receive the postal code. Without it the browser navigates to `?cp=`. */
647
+ onSubmitPostalCode?: (postalCode: string, event: FormEvent<HTMLFormElement>) => void;
648
+ /** Shows a "use my location" button (only when the browser supports it); it asks permission on click. */
649
+ onLocate?: (point: GeoPoint$1) => void;
650
+ labels?: Partial<NearestLocationFormLabels>;
651
+ } & Omit<ComponentPropsWithoutRef<'form'>, 'action' | 'method' | 'onSubmit' | 'defaultValue'>;
652
+ /**
653
+ * `<form method="get">` with a postal-code field: works without JavaScript (navigates to `?cp=06000`). Add
654
+ * `onSubmitPostalCode` / `onLocate` to enhance it. Renders `null` when there are no branches.
655
+ */
656
+ declare function NearestLocationForm<L extends ShopBranch = ShopBranch>({ locations, action, name, defaultValue, onSubmitPostalCode, onLocate, labels, children, ...rest }: NearestLocationFormProps<L>): react.JSX.Element | null;
657
+
658
+ export { AddToCartButton, type AddToCartButtonProps, type BreadcrumbItem, CART_COOKIE_NAME, type CartContextValue, type CartCookieOptions, type CartLineAddInput, CartProvider, type CartProviderProps, type CartStatus, DirectionsLink, type DirectionsLinkProps, type FaqItem, type GeolocationErrorCode, type GeolocationFailure, type GeolocationPermission, type InferSectionProps, JsonLd, type JsonLdObject, type JsonLdProps, type LocalBusinessOptions, type LocationItemState, LocationList, type LocationListProps, LocationPicker, type LocationPickerProps, type MetaDescriptor, type NearbyLocation, NearestLocationForm, type NearestLocationFormLabels, type NearestLocationFormProps, OpenStatus, type OpenStatusLabels, type OpenStatusProps, type PickupInfo, PickupOption, type PickupOptionProps, type ProductOptionState, type ProductOptionValueState, type ProductOptionsState, SELECTED_LOCATION_PARAM, SELECTED_LOCATION_STORAGE_KEY, type SectionComponentProps, type SectionDefinition, type SectionPropError, type SectionPropKind, type SectionPropSchema, SectionRegistry, type SectionSchema, Sections, type SectionsProps, type SeoMetaInput, type StorefrontContextValue, StorefrontProvider, type StorefrontProviderProps, type ThemeCssOptions, ThemeStyle, type ThemeStyleProps, type UseGeolocationOptions, type UseGeolocationResult, type UseNearestLocationsOptions, type UseNearestLocationsResult, type UseProductOptionsConfig, type UseSelectedLocationOptions, type UseSelectedLocationResult, VariantSelector, type VariantSelectorProps, type VariantSelectorRenderProps, breadcrumbJsonLd, collectionJsonLd, collectionMeta, createSectionRegistry, defineSection, faqJsonLd, getCartIdFromCookie, isSafeCssValue, localBusinessJsonLd, openingHoursJsonLd, optimisticCart, organizationJsonLd, productJsonLd, productMeta, readCartCookie, readSelectedLocationId, resolveSectionProps, sectionJsonSchema, seoMeta, serializeCartCookie, serializeJsonLd, themeFontsHref, themeToCss, themeToCssVariables, toMerchandise, tokenName, useCart, useGeolocation, useNearestLocations, useOptionalCart, useOptionalStorefront, useProductOptions, useSelectedLocation, useStorefront, useStorefrontClient, writeCartCookie };