@asteby/storefront-react 0.1.1 → 0.2.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/README.md CHANGED
@@ -158,6 +158,44 @@ metacore-ui components pick it up), fonts as `--font-<role>` and `--radius`.
158
158
  Unsafe values (`;`, `{}`, `<`, `url(`) are dropped. `<ThemeStyle theme />`
159
159
  renders it as a `<style>`; `themeFontsHref(theme)` gives a Google Fonts URL.
160
160
 
161
+ ## Sucursales (headless)
162
+
163
+ Primitivos sin estilos ni clases (render props), accesibles y SSR-safe. Con cero
164
+ sucursales todos renderizan `null`. Leen `shop.branches` del `StorefrontProvider`
165
+ si no recibes `locations`.
166
+
167
+ ```tsx
168
+ const [origin, setOrigin] = useState<GeoPoint | null>(null)
169
+ const cp = new URLSearchParams(search).get('cp')
170
+ const { locations } = useNearestLocations({ origin, postalCode: cp, limit: 3 })
171
+ const mine = useSelectedLocation({ initialId: urlSucursal }) // ?sucursal= + localStorage, nunca cookie
172
+
173
+ <NearestLocationForm action="/sucursales" onLocate={setOrigin} /> {/* GET ?cp=06000 sin JS */}
174
+ <LocationList locations={locations}>
175
+ {(l, { selected }) => (
176
+ <>
177
+ <strong>{l.name}</strong> <OpenStatus location={l} /> <DirectionsLink location={l} />
178
+ <PickupOption location={l} />
179
+ <button onClick={() => mine.select(l.id)} aria-pressed={selected}>Mi sucursal</button>
180
+ </>
181
+ )}
182
+ </LocationList>
183
+ ```
184
+
185
+ - `useGeolocation()`: solo pide la ubicación al llamar `request()` desde un gesto
186
+ del usuario; expone `supported`, `permission`, `status`, `position` y un error
187
+ tipado (`denied | unavailable | timeout | unsupported | insecure`). La posición
188
+ no se guarda ni se envía.
189
+ - `useNearestLocations({ origin, postalCode, locations, limit, radiusKm, remote })`:
190
+ con `origin` ordena en el navegador (haversine; las coordenadas no salen). Con
191
+ `postalCode` (o `remote`) pregunta al servidor (`client.locations`).
192
+ - `useSelectedLocation()`: "Mi sucursal" en `?sucursal=` y `localStorage`
193
+ (`sf_sucursal`); valida contra las sucursales y olvida ids obsoletos. Pasa
194
+ `initialId` desde el loader para que SSR e hidratación coincidan.
195
+ - `LocationList`, `LocationPicker` (radios nativos, `name="sucursal"`),
196
+ `OpenStatus` (calcula tras montar; `now` fija el instante), `DirectionsLink`,
197
+ `PickupOption` (muestra lo que manda OPS: costo y días), `NearestLocationForm`.
198
+
161
199
  ## API
162
200
 
163
201
  | Export | |
package/dist/index.cjs CHANGED
@@ -3,6 +3,7 @@
3
3
  var react = require('react');
4
4
  var storefront = require('@asteby/storefront');
5
5
  var jsxRuntime = require('react/jsx-runtime');
6
+ var geo = require('@asteby/storefront/geo');
6
7
 
7
8
  // src/context.tsx
8
9
  var StorefrontContext = react.createContext(null);
@@ -552,8 +553,6 @@ function AddToCartButton({
552
553
  }
553
554
  );
554
555
  }
555
-
556
- // src/seo.ts
557
556
  var clean = (s, max) => {
558
557
  if (!s) return void 0;
559
558
  const text = s.replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim();
@@ -687,6 +686,16 @@ function organizationJsonLd(shop, url) {
687
686
  contactPoint
688
687
  });
689
688
  }
689
+ var SCHEMA_HOURS = /^(Mo|Tu|We|Th|Fr|Sa|Su)(-(Mo|Tu|We|Th|Fr|Sa|Su))?( \d{2}:\d{2}-\d{2}:\d{2})?$/;
690
+ function openingHoursJsonLd(hours) {
691
+ if (!hours) return void 0;
692
+ if (typeof hours !== "string") {
693
+ const list = hours.weekly ? geo.toSchemaOpeningHours(hours) : [];
694
+ return list.length ? list : void 0;
695
+ }
696
+ const tokens = hours.split(",").map((t) => t.trim());
697
+ return tokens.length && tokens.every((t) => SCHEMA_HOURS.test(t)) ? tokens : void 0;
698
+ }
690
699
  function localBusinessJsonLd(shop, options = {}) {
691
700
  const site = shopUrl(shop, options.url);
692
701
  return shop.branches.map(
@@ -700,8 +709,8 @@ function localBusinessJsonLd(shop, options = {}) {
700
709
  telephone: b.phone ?? shop.contact.phone ?? void 0,
701
710
  priceRange: options.priceRange,
702
711
  address: compact({ "@type": "PostalAddress", streetAddress: b.address, addressLocality: b.city, addressCountry: shop.country_code }),
703
- geo: b.lat !== null && b.lng !== null ? { "@type": "GeoCoordinates", latitude: b.lat, longitude: b.lng } : void 0,
704
- openingHours: b.hours ? b.hours.split(/\s*,\s*/).filter(Boolean) : void 0,
712
+ geo: geo.isValidPoint(b) ? { "@type": "GeoCoordinates", latitude: b.lat, longitude: b.lng } : void 0,
713
+ openingHours: openingHoursJsonLd(b.opening_hours ?? b.hours),
705
714
  parentOrganization: site ? { "@id": `${site}#organization` } : { "@type": "Organization", name: shop.name }
706
715
  })
707
716
  );
@@ -770,7 +779,7 @@ function resolveSectionProps(schema, raw, path = "") {
770
779
  for (const [name, spec] of Object.entries(schema)) {
771
780
  const key = path ? `${path}.${name}` : name;
772
781
  let value = input[name];
773
- const fail = (message) => {
782
+ const fail2 = (message) => {
774
783
  errors.push({ prop: key, message });
775
784
  if ("default" in spec) props[name] = cloneDefault(spec.default);
776
785
  };
@@ -781,37 +790,37 @@ function resolveSectionProps(schema, raw, path = "") {
781
790
  }
782
791
  if (STRING_KINDS.has(spec.kind)) {
783
792
  if (typeof value !== "string") {
784
- fail("must be a string");
793
+ fail2("must be a string");
785
794
  continue;
786
795
  }
787
796
  } else if (spec.kind === "number") {
788
797
  if (typeof value === "string" && value.trim() !== "" && Number.isFinite(Number(value))) value = Number(value);
789
798
  if (typeof value !== "number" || !Number.isFinite(value)) {
790
- fail("must be a number");
799
+ fail2("must be a number");
791
800
  continue;
792
801
  }
793
802
  if (spec.min !== void 0 && value < spec.min || spec.max !== void 0 && value > spec.max) {
794
- fail(`must be between ${spec.min ?? "-\u221E"} and ${spec.max ?? "\u221E"}`);
803
+ fail2(`must be between ${spec.min ?? "-\u221E"} and ${spec.max ?? "\u221E"}`);
795
804
  continue;
796
805
  }
797
806
  } else if (spec.kind === "boolean") {
798
807
  if (typeof value !== "boolean") {
799
- fail("must be a boolean");
808
+ fail2("must be a boolean");
800
809
  continue;
801
810
  }
802
811
  } else if (spec.kind === "select") {
803
812
  if (typeof value !== "string" || spec.options && !spec.options.some((o) => o.value === value)) {
804
- fail(`must be one of ${(spec.options ?? []).map((o) => o.value).join(", ")}`);
813
+ fail2(`must be one of ${(spec.options ?? []).map((o) => o.value).join(", ")}`);
805
814
  continue;
806
815
  }
807
816
  } else if (spec.kind === "products") {
808
817
  if (!Array.isArray(value) || value.some((v) => typeof v !== "string")) {
809
- fail("must be a list of product handles");
818
+ fail2("must be a list of product handles");
810
819
  continue;
811
820
  }
812
821
  } else if (spec.kind === "list") {
813
822
  if (!Array.isArray(value)) {
814
- fail("must be a list");
823
+ fail2("must be a list");
815
824
  continue;
816
825
  }
817
826
  const items = [];
@@ -982,15 +991,369 @@ function ThemeStyle({ theme, nonce, id = "storefront-theme", ...options }) {
982
991
  if (!css) return null;
983
992
  return /* @__PURE__ */ jsxRuntime.jsx("style", { id, nonce, "data-storefront-theme": "", dangerouslySetInnerHTML: { __html: css } });
984
993
  }
994
+ var MESSAGES = {
995
+ unsupported: "Tu navegador no permite obtener tu ubicaci\xF3n.",
996
+ insecure: "La ubicaci\xF3n solo est\xE1 disponible en conexiones seguras (HTTPS).",
997
+ denied: "No tenemos permiso para usar tu ubicaci\xF3n. Puedes activarlo en el navegador o escribir tu c\xF3digo postal.",
998
+ unavailable: "No pudimos determinar tu ubicaci\xF3n. Escribe tu c\xF3digo postal.",
999
+ timeout: "Tard\xF3 demasiado en obtener tu ubicaci\xF3n. Intenta de nuevo o escribe tu c\xF3digo postal."
1000
+ };
1001
+ var fail = (code) => ({ code, message: MESSAGES[code] });
1002
+ var hasGeolocation = () => typeof navigator !== "undefined" && !!navigator.geolocation;
1003
+ function useGeolocation(options = {}) {
1004
+ const { enableHighAccuracy = false, timeoutMs = 1e4, maximumAge = 3e5 } = options;
1005
+ const [supported, setSupported] = react.useState(false);
1006
+ const [permission, setPermission] = react.useState("unknown");
1007
+ const [status, setStatus] = react.useState("idle");
1008
+ const [position, setPosition] = react.useState(null);
1009
+ const [error, setError] = react.useState(null);
1010
+ const alive = react.useRef(true);
1011
+ react.useEffect(() => {
1012
+ alive.current = true;
1013
+ setSupported(hasGeolocation());
1014
+ let off = () => {
1015
+ };
1016
+ try {
1017
+ const perms = typeof navigator !== "undefined" ? navigator.permissions : void 0;
1018
+ void perms?.query({ name: "geolocation" }).then((p) => {
1019
+ if (!alive.current) return;
1020
+ const sync = () => setPermission(p.state);
1021
+ sync();
1022
+ p.addEventListener?.("change", sync);
1023
+ off = () => p.removeEventListener?.("change", sync);
1024
+ }, () => {
1025
+ });
1026
+ } catch {
1027
+ }
1028
+ return () => {
1029
+ alive.current = false;
1030
+ off();
1031
+ };
1032
+ }, []);
1033
+ const request = react.useCallback(() => {
1034
+ const failWith = (code) => {
1035
+ if (alive.current) {
1036
+ setStatus("error");
1037
+ setError(fail(code));
1038
+ }
1039
+ return null;
1040
+ };
1041
+ if (!hasGeolocation()) return Promise.resolve(failWith("unsupported"));
1042
+ if (typeof window !== "undefined" && window.isSecureContext === false) return Promise.resolve(failWith("insecure"));
1043
+ setStatus("locating");
1044
+ setError(null);
1045
+ return new Promise((resolve) => {
1046
+ navigator.geolocation.getCurrentPosition(
1047
+ (pos) => {
1048
+ const point = { lat: pos.coords.latitude, lng: pos.coords.longitude };
1049
+ if (alive.current) {
1050
+ setPosition(point);
1051
+ setStatus("success");
1052
+ setPermission("granted");
1053
+ }
1054
+ resolve(point);
1055
+ },
1056
+ (err) => resolve(failWith(err.code === 1 ? "denied" : err.code === 3 ? "timeout" : "unavailable")),
1057
+ { enableHighAccuracy, timeout: timeoutMs, maximumAge }
1058
+ );
1059
+ });
1060
+ }, [enableHighAccuracy, timeoutMs, maximumAge]);
1061
+ const reset = react.useCallback(() => {
1062
+ setStatus("idle");
1063
+ setPosition(null);
1064
+ setError(null);
1065
+ }, []);
1066
+ return { supported, permission, status, position, error, request, reset };
1067
+ }
1068
+ var EMPTY = [];
1069
+ var IDLE = { locations: [], status: "idle", error: null, source: null };
1070
+ function useNearestLocations(options = {}) {
1071
+ const { origin = null, postalCode = null, limit, radiusKm, remote = false } = options;
1072
+ const sf = useOptionalStorefront();
1073
+ const client = options.client ?? sf?.client ?? null;
1074
+ const branches = options.locations ?? sf?.shop?.branches ?? EMPTY;
1075
+ const lat = origin?.lat;
1076
+ const lng = origin?.lng;
1077
+ const hasOrigin = lat !== void 0 && lng !== void 0;
1078
+ const cp = postalCode?.trim() || null;
1079
+ const mode = react.useMemo(() => {
1080
+ if (cp) return client ? "remote" : null;
1081
+ if (!hasOrigin) return null;
1082
+ if (remote) return client ? "remote" : null;
1083
+ return branches.length ? "local" : null;
1084
+ }, [cp, hasOrigin, remote, client, branches.length]);
1085
+ const local = react.useMemo(() => {
1086
+ if (mode !== "local" || !hasOrigin) return [];
1087
+ return geo.rankByDistance(branches, { lat, lng }, { ...radiusKm !== void 0 ? { radiusKm } : {}, ...limit !== void 0 ? { limit } : {} }).map(({ item, distanceKm }) => ({ ...item, distance_km: distanceKm }));
1088
+ }, [mode, branches, lat, lng, radiusKm, limit, hasOrigin]);
1089
+ const [remoteState, setRemoteState] = react.useState(null);
1090
+ const key = mode === "remote" ? cp ? `cp:${cp}` : `ll:${lat},${lng}` : "";
1091
+ react.useEffect(() => {
1092
+ if (mode !== "remote" || !client) return;
1093
+ const ctrl = new AbortController();
1094
+ const req = cp ? client.locations.list({ postal_code: cp }, { signal: ctrl.signal }) : client.locations.nearest({ lat, lng }, { signal: ctrl.signal });
1095
+ req.then(
1096
+ (res) => !ctrl.signal.aborted && setRemoteState({ key, nodes: res.nodes }),
1097
+ (err) => !ctrl.signal.aborted && setRemoteState({ key, error: err instanceof Error ? err : new Error(String(err)) })
1098
+ );
1099
+ return () => ctrl.abort();
1100
+ }, [mode, client, key, cp, lat, lng]);
1101
+ return react.useMemo(() => {
1102
+ if (mode === "local") return { locations: local, status: "success", error: null, source: "local" };
1103
+ if (mode !== "remote") return IDLE;
1104
+ if (!remoteState || remoteState.key !== key) return { locations: [], status: "loading", error: null, source: "remote" };
1105
+ if ("error" in remoteState) return { locations: [], status: "error", error: remoteState.error, source: "remote" };
1106
+ let nodes = remoteState.nodes;
1107
+ if (radiusKm !== void 0) nodes = nodes.filter((n) => n.distance_km == null || n.distance_km <= radiusKm);
1108
+ if (limit !== void 0) nodes = nodes.slice(0, Math.max(0, limit));
1109
+ return { locations: nodes, status: "success", error: null, source: "remote" };
1110
+ }, [mode, local, remoteState, key, radiusKm, limit]);
1111
+ }
1112
+ var SELECTED_LOCATION_PARAM = "sucursal";
1113
+ var SELECTED_LOCATION_STORAGE_KEY = "sf_sucursal";
1114
+ var CHANGE_EVENT = "storefront:selected-location";
1115
+ var storage = () => {
1116
+ try {
1117
+ return typeof localStorage === "undefined" ? null : localStorage;
1118
+ } catch {
1119
+ return null;
1120
+ }
1121
+ };
1122
+ function readSelectedLocationId({ search = typeof location === "undefined" ? "" : location.search, store = storage(), param = SELECTED_LOCATION_PARAM, storageKey = SELECTED_LOCATION_STORAGE_KEY } = {}) {
1123
+ const fromUrl = new URLSearchParams(search).get(param);
1124
+ if (fromUrl) return fromUrl;
1125
+ try {
1126
+ return store?.getItem(storageKey) || null;
1127
+ } catch {
1128
+ return null;
1129
+ }
1130
+ }
1131
+ var EMPTY2 = [];
1132
+ function useSelectedLocation(options = {}) {
1133
+ const { initialId = null, param = SELECTED_LOCATION_PARAM, storageKey = SELECTED_LOCATION_STORAGE_KEY, syncUrl = true } = options;
1134
+ const sf = useOptionalStorefront();
1135
+ const locations = options.locations ?? sf?.shop?.branches ?? EMPTY2;
1136
+ const [raw, setRaw] = react.useState(initialId);
1137
+ const [ready, setReady] = react.useState(false);
1138
+ react.useEffect(() => {
1139
+ const read = () => setRaw(readSelectedLocationId({ param, storageKey }));
1140
+ read();
1141
+ setReady(true);
1142
+ window.addEventListener(CHANGE_EVENT, read);
1143
+ window.addEventListener("storage", read);
1144
+ window.addEventListener("popstate", read);
1145
+ return () => {
1146
+ window.removeEventListener(CHANGE_EVENT, read);
1147
+ window.removeEventListener("storage", read);
1148
+ window.removeEventListener("popstate", read);
1149
+ };
1150
+ }, [param, storageKey]);
1151
+ const location2 = react.useMemo(() => raw ? locations.find((l) => l.id === raw) ?? null : null, [raw, locations]);
1152
+ const id = location2?.id ?? null;
1153
+ react.useEffect(() => {
1154
+ if (!ready || !raw || !locations.length) return;
1155
+ try {
1156
+ if (location2) storage()?.setItem(storageKey, raw);
1157
+ else storage()?.removeItem(storageKey);
1158
+ } catch {
1159
+ }
1160
+ }, [ready, raw, locations, location2, storageKey]);
1161
+ const write = react.useCallback(
1162
+ (next) => {
1163
+ try {
1164
+ if (next) storage()?.setItem(storageKey, next);
1165
+ else storage()?.removeItem(storageKey);
1166
+ } catch {
1167
+ }
1168
+ if (syncUrl && typeof history !== "undefined") {
1169
+ const url = new URL(window.location.href);
1170
+ if (next) url.searchParams.set(param, next);
1171
+ else url.searchParams.delete(param);
1172
+ history.replaceState(history.state, "", url);
1173
+ }
1174
+ setRaw(next);
1175
+ window.dispatchEvent(new Event(CHANGE_EVENT));
1176
+ },
1177
+ [param, storageKey, syncUrl]
1178
+ );
1179
+ const select = react.useCallback((next) => write(next), [write]);
1180
+ const clear = react.useCallback(() => write(null), [write]);
1181
+ return { id, location: location2, ready, select, clear };
1182
+ }
1183
+ var EMPTY3 = [];
1184
+ function useLocationsOrShop(locations) {
1185
+ const sf = useOptionalStorefront();
1186
+ return locations ?? (sf?.shop?.branches ?? EMPTY3);
1187
+ }
1188
+ function LocationList({ locations, selectedId, children, ...rest }) {
1189
+ const list = useLocationsOrShop(locations);
1190
+ if (!list.length) return null;
1191
+ return /* @__PURE__ */ jsxRuntime.jsx("ul", { ...rest, children: list.map((loc, index) => {
1192
+ const selected = loc.id === selectedId;
1193
+ return /* @__PURE__ */ jsxRuntime.jsx("li", { "data-selected": selected || void 0, "aria-current": selected || void 0, children: children ? children(loc, { index, selected }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1194
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: loc.name }),
1195
+ " ",
1196
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: loc.address })
1197
+ ] }) }, loc.id);
1198
+ }) });
1199
+ }
1200
+ function LocationPicker({
1201
+ legend,
1202
+ locations,
1203
+ value,
1204
+ defaultValue = null,
1205
+ onChange,
1206
+ name = SELECTED_LOCATION_PARAM,
1207
+ children,
1208
+ ...rest
1209
+ }) {
1210
+ const list = useLocationsOrShop(locations);
1211
+ const [inner, setInner] = react.useState(defaultValue);
1212
+ if (!list.length) return null;
1213
+ const current = value !== void 0 ? value : inner;
1214
+ return /* @__PURE__ */ jsxRuntime.jsxs("fieldset", { ...rest, children: [
1215
+ /* @__PURE__ */ jsxRuntime.jsx("legend", { children: legend }),
1216
+ list.map((loc) => {
1217
+ const checked = loc.id === current;
1218
+ return /* @__PURE__ */ jsxRuntime.jsxs("label", { "data-checked": checked || void 0, children: [
1219
+ /* @__PURE__ */ jsxRuntime.jsx(
1220
+ "input",
1221
+ {
1222
+ type: "radio",
1223
+ name,
1224
+ value: loc.id,
1225
+ checked,
1226
+ onChange: () => {
1227
+ if (value === void 0) setInner(loc.id);
1228
+ onChange?.(loc.id, loc);
1229
+ }
1230
+ }
1231
+ ),
1232
+ " ",
1233
+ children ? children(loc, { checked }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1234
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: loc.name }),
1235
+ " ",
1236
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: loc.address })
1237
+ ] })
1238
+ ] }, loc.id);
1239
+ })
1240
+ ] });
1241
+ }
1242
+ var DEFAULT_TZ = "America/Mexico_City";
1243
+ function OpenStatus({ location: location2, hours, timezone, now, labels, children, ...rest }) {
1244
+ const h = hours ?? location2?.opening_hours;
1245
+ const tz = timezone ?? location2?.timezone ?? DEFAULT_TZ;
1246
+ const [tick, setTick] = react.useState(null);
1247
+ react.useEffect(() => {
1248
+ if (now) return;
1249
+ setTick(/* @__PURE__ */ new Date());
1250
+ const t = setInterval(() => setTick(/* @__PURE__ */ new Date()), 6e4);
1251
+ return () => clearInterval(t);
1252
+ }, [now]);
1253
+ const at = now ?? tick;
1254
+ if (!h || !at) return null;
1255
+ let status;
1256
+ try {
1257
+ status = geo.openStatus(h, tz, at);
1258
+ } catch {
1259
+ return null;
1260
+ }
1261
+ const text = { open: "Abierto", closed: "Cerrado", ...labels };
1262
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { "data-state": status.open ? "open" : "closed", ...rest, children: children ? children(status) : status.open ? text.open : text.closed });
1263
+ }
1264
+ function DirectionsLink({ location: location2, provider, children = "C\xF3mo llegar", ...rest }) {
1265
+ const href = geo.directionsUrl(location2, provider ? { provider } : {});
1266
+ if (!href) return null;
1267
+ return /* @__PURE__ */ jsxRuntime.jsx("a", { target: "_blank", rel: "noopener noreferrer", ...rest, href, children });
1268
+ }
1269
+ var readyText = (days) => days <= 0 ? "listo hoy" : days === 1 ? "listo en 1 d\xEDa" : `listo en ${days} d\xEDas`;
1270
+ function PickupOption({ location: location2, locale, children, ...rest }) {
1271
+ const sf = useOptionalStorefront();
1272
+ const p = location2.pickup;
1273
+ if (!p?.enabled) return null;
1274
+ const free = !p.fee || Number(p.fee.amount) === 0;
1275
+ const feeText = free ? null : storefront.formatMoney(p.fee, locale ?? sf?.locale);
1276
+ const info = { location: location2, fee: free ? null : p.fee, free, feeText, prepDays: p.prep_days };
1277
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { ...rest, children: children ? children(info) : `Recoge en sucursal \xB7 ${free ? "gratis" : feeText} \xB7 ${readyText(p.prep_days)}` });
1278
+ }
1279
+ function NearestLocationForm({
1280
+ locations,
1281
+ action,
1282
+ name = "cp",
1283
+ defaultValue,
1284
+ onSubmitPostalCode,
1285
+ onLocate,
1286
+ labels,
1287
+ children,
1288
+ ...rest
1289
+ }) {
1290
+ const list = useLocationsOrShop(locations);
1291
+ const id = react.useId();
1292
+ const geo = useGeolocation();
1293
+ if (!list.length) return null;
1294
+ const text = { postalCode: "C\xF3digo postal", submit: "Buscar sucursales", locate: "Usar mi ubicaci\xF3n", locating: "Buscando\u2026", ...labels };
1295
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1296
+ "form",
1297
+ {
1298
+ method: "get",
1299
+ ...action !== void 0 ? { action } : {},
1300
+ ...rest,
1301
+ onSubmit: onSubmitPostalCode ? (e) => {
1302
+ e.preventDefault();
1303
+ const value = new FormData(e.currentTarget).get(name);
1304
+ onSubmitPostalCode(typeof value === "string" ? value.trim() : "", e);
1305
+ } : void 0,
1306
+ children: [
1307
+ /* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: id, children: text.postalCode }),
1308
+ " ",
1309
+ /* @__PURE__ */ jsxRuntime.jsx(
1310
+ "input",
1311
+ {
1312
+ id,
1313
+ name,
1314
+ type: "text",
1315
+ inputMode: "numeric",
1316
+ autoComplete: "postal-code",
1317
+ pattern: "[0-9]{5}",
1318
+ maxLength: 5,
1319
+ required: true,
1320
+ ...defaultValue !== void 0 ? { defaultValue } : {}
1321
+ }
1322
+ ),
1323
+ " ",
1324
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "submit", children: text.submit }),
1325
+ onLocate && geo.supported && /* @__PURE__ */ jsxRuntime.jsx(
1326
+ "button",
1327
+ {
1328
+ type: "button",
1329
+ disabled: geo.status === "locating",
1330
+ onClick: () => void geo.request().then((p) => p && onLocate(p)),
1331
+ children: geo.status === "locating" ? text.locating : text.locate
1332
+ }
1333
+ ),
1334
+ geo.error && /* @__PURE__ */ jsxRuntime.jsx("span", { role: "alert", children: geo.error.message }),
1335
+ children
1336
+ ]
1337
+ }
1338
+ );
1339
+ }
985
1340
 
986
1341
  exports.AddToCartButton = AddToCartButton;
987
1342
  exports.CART_COOKIE_NAME = CART_COOKIE_NAME;
988
1343
  exports.CartProvider = CartProvider;
989
1344
  exports.DEFAULT_IMAGE_WIDTHS = DEFAULT_IMAGE_WIDTHS;
1345
+ exports.DirectionsLink = DirectionsLink;
990
1346
  exports.Image = Image;
991
1347
  exports.JsonLd = JsonLd;
1348
+ exports.LocationList = LocationList;
1349
+ exports.LocationPicker = LocationPicker;
992
1350
  exports.Money = Money;
1351
+ exports.NearestLocationForm = NearestLocationForm;
1352
+ exports.OpenStatus = OpenStatus;
1353
+ exports.PickupOption = PickupOption;
993
1354
  exports.ProductPrice = ProductPrice;
1355
+ exports.SELECTED_LOCATION_PARAM = SELECTED_LOCATION_PARAM;
1356
+ exports.SELECTED_LOCATION_STORAGE_KEY = SELECTED_LOCATION_STORAGE_KEY;
994
1357
  exports.SectionRegistry = SectionRegistry;
995
1358
  exports.Sections = Sections;
996
1359
  exports.StorefrontProvider = StorefrontProvider;
@@ -1006,11 +1369,13 @@ exports.faqJsonLd = faqJsonLd;
1006
1369
  exports.getCartIdFromCookie = getCartIdFromCookie;
1007
1370
  exports.isSafeCssValue = isSafeCssValue;
1008
1371
  exports.localBusinessJsonLd = localBusinessJsonLd;
1372
+ exports.openingHoursJsonLd = openingHoursJsonLd;
1009
1373
  exports.optimisticCart = optimisticCart;
1010
1374
  exports.organizationJsonLd = organizationJsonLd;
1011
1375
  exports.productJsonLd = productJsonLd;
1012
1376
  exports.productMeta = productMeta;
1013
1377
  exports.readCartCookie = readCartCookie;
1378
+ exports.readSelectedLocationId = readSelectedLocationId;
1014
1379
  exports.resolveSectionProps = resolveSectionProps;
1015
1380
  exports.sectionJsonSchema = sectionJsonSchema;
1016
1381
  exports.seoMeta = seoMeta;
@@ -1022,9 +1387,12 @@ exports.themeToCssVariables = themeToCssVariables;
1022
1387
  exports.toMerchandise = toMerchandise;
1023
1388
  exports.tokenName = tokenName;
1024
1389
  exports.useCart = useCart;
1390
+ exports.useGeolocation = useGeolocation;
1391
+ exports.useNearestLocations = useNearestLocations;
1025
1392
  exports.useOptionalCart = useOptionalCart;
1026
1393
  exports.useOptionalStorefront = useOptionalStorefront;
1027
1394
  exports.useProductOptions = useProductOptions;
1395
+ exports.useSelectedLocation = useSelectedLocation;
1028
1396
  exports.useStorefront = useStorefront;
1029
1397
  exports.useStorefrontClient = useStorefrontClient;
1030
1398
  exports.writeCartCookie = writeCartCookie;