@asteby/storefront-react 0.16.1 → 0.18.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
@@ -525,6 +525,63 @@ export const pay = createEmbeddedPayHandlers({
525
525
  - `cashPointsVisible={4}` deja a la vista los primeros puntos de efectivo y manda el resto a «Ver más puntos de pago» (se abre solo si el punto recordado está ahí).
526
526
  - Botón: «Generar ficha de pago $X» con efectivo (`placeOrderCash`) y «Elige la entrega para continuar» mientras falta la entrega (`chooseDelivery`; al pulsarlo señala el campo que falta en vez de pagar).
527
527
 
528
+ ### Cobro resiliente: reintento, verificación, formulario estable y aviso con salida
529
+
530
+ Un cobro con tarjeta puede quedar **indeterminado** (la respuesta se perdió, el proxy devolvió HTML, OPS tardó). La regla que nunca cambia: **un resultado indeterminado jamás se cobra con otra clave de idempotencia**; se repite con la MISMA clave o se consulta el estado. Lo de abajo se adopta de forma opcional, pero **no es solo aditivo** (ver «Cambios que se notan» al final de esta sección), y está pensado para que ninguna tienda tenga que copiar código de otra.
531
+
532
+ **1. Servidor (`/payments/server`).** `createEmbeddedPayHandlers` ya responde SIEMPRE JSON `{ error: { code, message, retryable, kind } }` (clasificado con `classifyPaymentError` de `@asteby/storefront`) y devuelve `500` en lugar de `502-504` (Traefik/CDN los cambian por HTML y se pierde el `Set-Cookie` de la sesión de pago). `/estado` sin cookie `sf_pay` responde `200 { payment: { status: 'none' }, recoverable }` en vez de `404`: `none` no es un resultado, así que un cobro indeterminado sigue con la misma clave; `recoverable` dice si la tienda aún tiene el carrito del comprador. Para rutas propias: `jsonPayRoute(run, 'pay', { locale, messages })`. Los textos de los errores salen del catálogo `error.<kind>` y se reemplazan con `errorMessages` (o `messages`/`locale` de `jsonPayRoute`).
533
+
534
+ **2. Navegador.**
535
+
536
+ ```tsx
537
+ import { PaymentMethodSection, PaymentRecovery, alternativeChoices, createResilientTransport, pickChoice, usePaymentRecovery } from '@asteby/storefront-react/payments'
538
+
539
+ const recovery = usePaymentRecovery({ scope: cart.id, onRevalidate: revalidate })
540
+ const transport = useMemo(
541
+ () => createResilientTransport({ payUrl: '/pagar/cobrar', statusUrl: '/pagar/estado', fields: () => formFields(formRef.current), onIssue: recovery.onIssue }),
542
+ [],
543
+ )
544
+
545
+ <PaymentRecovery
546
+ notice={recovery.notice} alternatives={alternativeChoices(choices.choices)}
547
+ onRetry={recovery.retry} onRefresh={() => location.assign('/pagar')}
548
+ onPick={(c) => { recovery.clear(); pickChoice(formRef.current, c.value) }} />
549
+ <PaymentMethodSection key={recovery.sectionKey} transport={transport} brickProps={{ autoRetries: 0 }} onEvent={recovery.onEvent} ... />
550
+ ```
551
+
552
+ - `createResilientTransport({ payUrl, statusUrl, fields, onIssue, retries = 2, backoffMs = [800, 2000], statusPolls = 3, statusPollMs = 2000, headers, locale, messages, fetch, sleep })`: reintenta los fallos transitorios con la misma clave, consulta `/estado` y, si sigue sin resolverse, lanza `PaymentHttpError(503, 'network', …, { retryable: true, kind: 'transient' })` (el hook conserva el intento y deja «Reintentar»). Con `retryable: false` de OPS hace UNA petición. Un cuerpo que no es JSON se trata como transitorio. `PaymentHttpError` expone `retryable` y `kind`. Con `autoRetries: 0` en `brickProps` el hook no duplica la política del transporte.
553
+ - `usePaymentRecovery({ scope, storage, formAutoRetries = 2, onRevalidate })` devuelve `notice`, `sectionKey` (úsalo como `key` de la sección), `onIssue`, `onEvent`, `retry()` y `clear()`. `retry()` quita la clave del intento y monta una sección nueva SOLO tras un fallo definitivo; mientras el resultado es desconocido (confirmando, verificando, transitorio) no hace nada y devuelve `false`. Un formulario de tarjeta que no carga se vuelve a montar solo `formAutoRetries` veces antes de mostrar el aviso.
554
+ - `<PaymentRecovery notice alternatives onRetry onPick onRefresh locale messages alternativeLabel classNames testIds>`: nunca deja un panel vacío; un fallo del comercio (credenciales, configuración) no culpa al comprador y pone primero las alternativas, que salen de las `choices` publicadas (nada de marcas en los textos). Textos en español de México por omisión (`en*` disponible), todos reemplazables con `messages` (`notice.<tipo>.title|body`, `action.retry|retry_form|refresh|alternative` con `{label}`, `error.<tipo>`; ver `paymentRecoveryMessages`), clases con `classNames` (`root title body actions primary secondary`).
555
+ - **Consulta de estado tras «verificando»**: `useEmbeddedPayment` sigue leyendo `transport.refresh` con espera creciente (2 s, 4 s, 8 s… hasta 15 s) durante `verifyTimeoutMs` (120 s). Solo un resultado resuelto (pagado, rechazado, ficha, reto, redirección) lo termina; `pending` y `none` no. Expone `verifying` y la clave del intento no cambia. Opciones: `verifyTimeoutMs`, `verifyIntervalMs`.
556
+ - `checkoutShouldRevalidate({ paths: ['/pagar'] })`: `shouldRevalidate` para las rutas de ALREDEDOR (root, encabezado…): el `fetcher.submit` de la entrega a `/pagar` ya no vuelve a ejecutar todos los loaders. No lo uses en la ruta de checkout (su loader debe releer el total).
557
+
558
+ **3. Formulario de tarjeta estable.** El contenedor se monta UNA vez por vida del formulario:
559
+
560
+ - Un cambio de total NO remonta: el hook llama `MountedPaymentForm.update({ amount })` (en Mercado Pago, `controller.update` del Card Payment Brick). Si el proveedor no lo tiene o falla, se remonta UNA vez, en cola. Varios cambios seguidos se fusionan en el último.
561
+ - Nunca se actualiza ni remonta mientras `onSubmit` va en vuelo: se aplica después de la respuesta, y un `submit` espera a que termine la actualización en curso.
562
+ - Escribir el correo ya no remonta (se usa al crear el formulario).
563
+ - `PaymentMethodSection`: mientras `syncDelivery` no termina, el formulario se queda montado con la capa «Actualizando el total…» (`data-sf-pay-updating`, `payments.css`) y el botón bloqueado (`aria-disabled`).
564
+ - Cola de montajes (`mountQueued` / `withMountQueue`, la usa el hook con cualquier adaptador): uno a la vez por contenedor, `await unmount` antes del siguiente `create`, nodo interno con id único, cancelación (`signal` en `PaymentFormMountOptions`) del montaje viejo y de uno cuyo contenedor salió del DOM, y tiempo límite de `create` (`createTimeoutMs`, por omisión `sdkTimeoutMs`) para que la cadena no se cuelgue (cae a la alternativa de redirección).
565
+ - Adaptadores propios: implementa `update?` si el proveedor sabe cambiar el monto; si no, el hook remonta.
566
+
567
+ **4. `AddressFields` (checkout).** Un solo escritor en cola; no escribe en un campo con foco ni en uno que el comprador editó (evento `input` con `isTrusted`); la colonia solo se rellena si el código postal tiene UNA sola; los eventos `input` que dispara el autollenado llevan `event.sfSynthetic === true` (`isAddressFillEvent(event)`) para que la tienda los ignore.
568
+
569
+ **Migrar una tienda que copió esto** (p. ej. `store-protekauto`): borra su `pay-errors`, `pay-transport`, `pay-route.server`, `brick-adapter` y `pay-recovery`; usa `createResilientTransport`, `usePaymentRecovery`, `<PaymentRecovery>` y los handlers del servidor; quita el `key` de `PaymentBrick` si lo tenía y el adaptador envuelto. Los textos propios van en `messages`.
570
+
571
+ ### Cambios que se notan
572
+
573
+ Al subir el pin de `@asteby/storefront` / `@asteby/storefront-react` (en 0.x un caret no adopta un minor solo):
574
+
575
+ - Los handlers de pago responden **500** donde antes respondían 502-504 (y `payErrorResponse({})` pasa de 503 a 500). La señal de «indeterminado» viaja en el cuerpo (`retryable`, `kind`); un 5xx sin cuerpo legible se trata como indeterminado (misma clave), nunca como rechazo.
576
+ - Los mensajes salen del catálogo es-MX/en y no del texto de OPS para los tipos transitorio, en curso, límite de intentos, configuración, rechazo del proveedor, pedido ya registrado y tienda cerrada. Los textos propios de la tienda (`messages.tooManyRequests`, `fillFields`, etc.) se conservan.
577
+ - `/estado` sin sesión o con un checkout inexistente responde **200** `{ status: 'none', recoverable }` en lugar de 404. `'none'` se suma a `PaymentAttemptStatus`.
578
+ - Escribir el correo ya no remonta el formulario de tarjeta: un invitado que lo escribe después del montaje ve el campo de correo dentro del Brick.
579
+ - Un cambio de total actualiza el formulario en su sitio (no lo remonta) y el formulario queda dentro de un `div` interno: un contenedor propio de `useEmbeddedPayment` no debe tener hijos renderizados por React (`replaceChildren`).
580
+ - Con resultado indeterminado, PAGAR reintenta con los mismos datos y la misma clave (no vuelve a tokenizar), y un cambio de total espera a que se resuelva.
581
+ - `createTimeoutMs` toma por omisión `sdkTimeoutMs`.
582
+ - `AddressFields` rellena la colonia cuando el código postal tiene una sola, rellena un campo enfocado solo si está vacío y no lo editó el comprador, y marca sus eventos (`isAddressFillEvent`).
583
+ - Tipos: `PayErrorBody` exige `retryable` y `kind`; `paymentErrorView` trata un 5xx (salvo `retryable:false`) como `network`.
584
+
528
585
  ## Entrega y marcas (`@asteby/storefront-react/checkout`)
529
586
 
530
587
  Las tarifas de envío y las sucursales de «Entrega» como radios nativos, iguales en todas las tiendas: marca y nombre comercial de la paquetería (`fedex` → FedEx), servicio sin repetir la paquetería, llegada en días hábiles («Llega en 4 días hábiles»), insignias discretas de «Más económico» / «Más rápido» (solo si una tarifa lo es estrictamente) y el precio tal cual lo manda OPS. La vigencia de la cotización es UNA sola nota para toda la lista, en la zona horaria del comprador («Precios de envío vigentes hasta las 9:39 p.m.»), que aparece tras hidratar en una línea reservada (sin salto de diseño). `loading` pinta filas esqueleto al recotizar.
@@ -690,7 +747,7 @@ La cookie, el formulario y el redirect son de la tienda; `safeRedirect` valida e
690
747
  | `defineSection`, `SectionRegistry`, `createSectionRegistry`, `Sections`, `resolveSectionProps`, `sectionJsonSchema` | Content sections |
691
748
  | `themeToCss`, `themeToCssVariables`, `ThemeStyle`, `themeFontsHref`, `tokenName` | SSR theme |
692
749
  | `ProductImage`, `ProductMockup`, `MockupProvider`, `createMockupRegistry`, `registerMockup`, `BUILTIN_MOCKUPS`, `MockupFrame`, `MockupText`, `TireMockup`, `PartMockup`, `SupplementMockup`, `GenericMockup` | Mockups de respaldo (subpath `/mockups`) |
693
- | `PaymentMethodSection`, `buildPayChoices`, `createEmbeddedPayHandlers` (`/payments/server`), `PaymentBrick`, `useEmbeddedPayment`, `VoucherCard`, `PaymentChallenge`, `createFetchTransport`, `mercadoPagoBricksAdapter`, `loadPaymentSdk`, `paymentCsp`, `readPaymentTheme` | Pago integrado (subpath `/payments`) |
750
+ | `PaymentMethodSection`, `buildPayChoices`, `createEmbeddedPayHandlers` + `jsonPayRoute` (`/payments/server`), `createResilientTransport`, `usePaymentRecovery`, `PaymentRecovery`, `mountQueued`, `checkoutShouldRevalidate`, `PaymentBrick`, `useEmbeddedPayment`, `VoucherCard`, `PaymentChallenge`, `createFetchTransport`, `mercadoPagoBricksAdapter`, `loadPaymentSdk`, `paymentCsp`, `readPaymentTheme` | Pago integrado (subpath `/payments`) |
694
751
  | `PromoPopup`, `CartProgressBars`, `PromoProgressBar`, `CrossSellModal`, `OrderNote`, `isExcludedPath`, `frequencyAllows`, `isCrawler` | Promociones y venta cruzada (subpath `/promotions`) |
695
752
 
696
753
  ## `@asteby/storefront-react/ui`
package/dist/checkout.cjs CHANGED
@@ -236,6 +236,7 @@ var ES2 = {
236
236
  address2: "N\xFAmero interior o departamento",
237
237
  reference: "Referencias para la entrega",
238
238
  provincePlaceholder: "Elige tu estado",
239
+ neighborhoodPlaceholder: "Elige tu colonia",
239
240
  address1Hint: "Por ejemplo: Av. Insurgentes Sur 1234",
240
241
  referenceHint: "Entre qu\xE9 calles, color de la fachada\u2026",
241
242
  emailHint: "Te enviamos aqu\xED la confirmaci\xF3n y el seguimiento.",
@@ -255,6 +256,7 @@ var EN2 = {
255
256
  address2: "Apartment, suite",
256
257
  reference: "Delivery notes",
257
258
  provincePlaceholder: "Choose your state",
259
+ neighborhoodPlaceholder: "Choose your neighborhood",
258
260
  address1Hint: "For example: 1234 Main St",
259
261
  referenceHint: "Cross streets, building color\u2026",
260
262
  emailHint: "We send the confirmation and tracking here.",
@@ -297,6 +299,10 @@ function lockField(el) {
297
299
  el.readOnly = true;
298
300
  }
299
301
  }
302
+ var ADDRESS_FILL_EVENT_FLAG = "sfSynthetic";
303
+ function isAddressFillEvent(event) {
304
+ return event[ADDRESS_FILL_EVENT_FLAG] === true;
305
+ }
300
306
  function AddressFields(props) {
301
307
  const { defaults, names = {}, errors = {}, countryCode, provinces, postalCode, lookupPostalCode, lockProvinceOnFill, locale, messages, fieldMessages: fm, classNames: cx = {}, children } = props;
302
308
  const showNeighborhood = props.neighborhood ?? true;
@@ -307,10 +313,36 @@ function AddressFields(props) {
307
313
  const listId = react.useId();
308
314
  const [colonias, setColonias] = react.useState([]);
309
315
  const [filled, setFilled] = react.useState(false);
316
+ const [coloniaSelect, setColoniaSelect] = react.useState(false);
317
+ const coloniaSelectRef = react.useRef(false);
318
+ const coloniaCarry = react.useRef(null);
310
319
  const lastZip = react.useRef(null);
311
320
  const zipRef = react.useRef(null);
312
321
  const lookupRef = react.useRef(lookupPostalCode);
313
322
  lookupRef.current = lookupPostalCode;
323
+ const writer = react.useRef(Promise.resolve());
324
+ const enqueue = (task) => {
325
+ writer.current = writer.current.then(task).catch(() => {
326
+ });
327
+ };
328
+ react.useEffect(() => {
329
+ const form = zipRef.current?.closest("form");
330
+ if (!form) return;
331
+ const mark = (e) => {
332
+ if (!e.isTrusted) return;
333
+ const el = e.target;
334
+ if (el instanceof HTMLInputElement || el instanceof HTMLSelectElement) {
335
+ el.dataset.sfUserEdited = "1";
336
+ delete el.dataset.sfAutofilled;
337
+ }
338
+ };
339
+ form.addEventListener("input", mark, true);
340
+ form.addEventListener("change", mark, true);
341
+ return () => {
342
+ form.removeEventListener("input", mark, true);
343
+ form.removeEventListener("change", mark, true);
344
+ };
345
+ }, []);
314
346
  const runLookup = (zip) => {
315
347
  const lookup = lookupRef.current;
316
348
  if (!lookup || !zip || zip === lastZip.current) return;
@@ -319,28 +351,65 @@ function AddressFields(props) {
319
351
  const form = zipRef.current?.closest("form");
320
352
  void lookup(zip).then(
321
353
  (info) => {
322
- if (!info || lastZip.current !== zip) return;
323
- const fill2 = (name, value, lock) => {
324
- const el = form?.elements.namedItem(name);
325
- if (!el || !value || "length" in el && !(el instanceof HTMLSelectElement)) return false;
326
- if (el.dataset.sfLocked === "1") return false;
327
- if (el.value && el.dataset.sfAutofilled !== "1") return false;
328
- if (el instanceof HTMLSelectElement && ![...el.options].some((o) => o.value === value)) return false;
329
- el.value = value;
330
- el.dataset.sfAutofilled = "1";
331
- el.dispatchEvent(new Event("input", { bubbles: true }));
332
- if (lock) lockField(el);
333
- return true;
334
- };
335
- const a = fill2(n("province"), info.province, lockProvinceOnFill);
336
- const b = fill2(n("city"), info.city);
337
- setColonias(info.neighborhoods ?? []);
338
- setFilled(a || b);
354
+ if (!info) return;
355
+ enqueue(() => {
356
+ if (lastZip.current !== zip) return;
357
+ const fill2 = (name, value, lock) => {
358
+ const el2 = form?.elements.namedItem(name);
359
+ if (!el2 || !value || "length" in el2 && !(el2 instanceof HTMLSelectElement)) return false;
360
+ if (el2.dataset.sfLocked === "1") return false;
361
+ if (el2.dataset.sfUserEdited === "1" || el2.ownerDocument.activeElement === el2 && el2.value) return false;
362
+ if (el2.value && el2.dataset.sfAutofilled !== "1") return false;
363
+ if (el2 instanceof HTMLSelectElement && ![...el2.options].some((o) => o.value === value)) return false;
364
+ el2.value = value;
365
+ el2.dataset.sfAutofilled = "1";
366
+ const event = new Event("input", { bubbles: true });
367
+ Object.defineProperty(event, ADDRESS_FILL_EVENT_FLAG, { value: true });
368
+ el2.dispatchEvent(event);
369
+ if (lock) lockField(el2);
370
+ return true;
371
+ };
372
+ const a = fill2(n("province"), info.province, lockProvinceOnFill);
373
+ const b = fill2(n("city"), info.city);
374
+ const only = info.neighborhoods?.length === 1 ? info.neighborhoods[0] : null;
375
+ const c = showNeighborhood ? fill2(n("neighborhood"), only) : false;
376
+ setColonias(info.neighborhoods ?? []);
377
+ const list = info.neighborhoods ?? [];
378
+ const nameEl = form?.elements.namedItem(n("neighborhood"));
379
+ const el = nameEl && !("length" in nameEl && !(nameEl instanceof HTMLSelectElement)) ? nameEl : null;
380
+ const typed = el ? el.value : "";
381
+ const wantSelect = props.neighborhoodList === "select" && showNeighborhood !== false && list.length > 1 && (!typed || list.includes(typed));
382
+ let carried = false;
383
+ if (showNeighborhood && wantSelect !== coloniaSelectRef.current) {
384
+ const userEdited = el?.dataset.sfUserEdited === "1";
385
+ const value = only && !userEdited ? only : wantSelect ? list.includes(typed) ? typed : "" : typed;
386
+ coloniaCarry.current = { value, autofilled: !userEdited && (!!only || el?.dataset.sfAutofilled === "1"), userEdited };
387
+ carried = !!only && !userEdited;
388
+ }
389
+ coloniaSelectRef.current = wantSelect;
390
+ setColoniaSelect(wantSelect);
391
+ setFilled(a || b || c || carried);
392
+ });
339
393
  },
340
394
  () => {
341
395
  }
342
396
  );
343
397
  };
398
+ react.useEffect(() => {
399
+ const carry = coloniaCarry.current;
400
+ coloniaCarry.current = null;
401
+ if (!carry || !carry.value) return;
402
+ const form = zipRef.current?.closest("form");
403
+ const el = form?.elements.namedItem(n("neighborhood"));
404
+ if (!el || "length" in el && !(el instanceof HTMLSelectElement)) return;
405
+ if (el instanceof HTMLSelectElement && ![...el.options].some((o) => o.value === carry.value)) return;
406
+ el.value = carry.value;
407
+ if (carry.userEdited) el.dataset.sfUserEdited = "1";
408
+ else if (carry.autofilled) el.dataset.sfAutofilled = "1";
409
+ const event = new Event("input", { bubbles: true });
410
+ Object.defineProperty(event, ADDRESS_FILL_EVENT_FLAG, { value: true });
411
+ el.dispatchEvent(event);
412
+ }, [coloniaSelect]);
344
413
  react.useEffect(() => {
345
414
  const zip = defaults?.zip;
346
415
  if (zip) runLookup(zip);
@@ -374,7 +443,24 @@ function AddressFields(props) {
374
443
  provinces.map((p) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: p.name, children: p.name }, p.code ?? p.name))
375
444
  ] }) : /* @__PURE__ */ jsxRuntime.jsx(CheckoutField, { ...common, name: n("province"), label: m.province, autoComplete: "address-level1", defaultValue: defaults?.province ?? "", error: errors[n("province")] ?? null, classNames: field(cx, "half") }),
376
445
  /* @__PURE__ */ jsxRuntime.jsx(CheckoutField, { ...common, name: n("city"), label: m.city, autoComplete: "address-level2", defaultValue: defaults?.city ?? "", error: errors[n("city")] ?? null, classNames: field(cx, "half") }),
377
- showNeighborhood ? /* @__PURE__ */ jsxRuntime.jsx(
446
+ showNeighborhood && coloniaSelect ? /* @__PURE__ */ jsxRuntime.jsxs(
447
+ CheckoutField,
448
+ {
449
+ ...common,
450
+ as: "select",
451
+ name: n("neighborhood"),
452
+ label: m.neighborhood,
453
+ autoComplete: "address-level3",
454
+ optional: showNeighborhood !== "required",
455
+ defaultValue: colonias.includes(defaults?.neighborhood ?? "") ? defaults?.neighborhood ?? "" : "",
456
+ error: errors[n("neighborhood")] ?? null,
457
+ classNames: field(cx, "half"),
458
+ children: [
459
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", disabled: showNeighborhood === "required", children: m.neighborhoodPlaceholder }),
460
+ colonias.map((c) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: c, children: c }, c))
461
+ ]
462
+ }
463
+ ) : showNeighborhood ? /* @__PURE__ */ jsxRuntime.jsx(
378
464
  CheckoutField,
379
465
  {
380
466
  ...common,
@@ -385,10 +471,10 @@ function AddressFields(props) {
385
471
  defaultValue: defaults?.neighborhood ?? "",
386
472
  error: errors[n("neighborhood")] ?? null,
387
473
  classNames: field(cx, "half"),
388
- ...colonias.length ? { list: listId } : {}
474
+ ...colonias.length && !coloniaSelect ? { list: listId } : {}
389
475
  }
390
476
  ) : null,
391
- colonias.length ? /* @__PURE__ */ jsxRuntime.jsx("datalist", { id: listId, children: colonias.map((c) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: c }, c)) }) : null,
477
+ colonias.length && !coloniaSelect ? /* @__PURE__ */ jsxRuntime.jsx("datalist", { id: listId, children: colonias.map((c) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: c }, c)) }) : null,
392
478
  /* @__PURE__ */ jsxRuntime.jsx("p", { role: "status", "aria-live": "polite", "data-sf-zip-status": true, style: filled ? void 0 : { position: "absolute", width: 1, height: 1, overflow: "hidden", clip: "rect(0 0 0 0)", whiteSpace: "nowrap" }, className: filled ? join(cx.hint, cx.wide) : void 0, children: filled ? m.lookupFilled : "" }),
393
479
  /* @__PURE__ */ jsxRuntime.jsx(CheckoutField, { ...common, name: n("address1"), label: m.address1, autoComplete: "address-line1", defaultValue: defaults?.address1 ?? "", error: errors[n("address1")] ?? null, hint: m.address1Hint, classNames: field(cx, "wide") }),
394
480
  /* @__PURE__ */ jsxRuntime.jsx(CheckoutField, { ...common, name: n("address2"), label: m.address2, autoComplete: "address-line2", optional: true, defaultValue: defaults?.address2 ?? "", error: errors[n("address2")] ?? null, classNames: field(cx, "half") }),
@@ -1163,6 +1249,7 @@ function DeliveryOptions(props) {
1163
1249
  ] });
1164
1250
  }
1165
1251
 
1252
+ exports.ADDRESS_FILL_EVENT_FLAG = ADDRESS_FILL_EVENT_FLAG;
1166
1253
  exports.AddressFields = AddressFields;
1167
1254
  exports.BrandMark = BrandMark;
1168
1255
  exports.CheckoutField = CheckoutField;
@@ -1180,6 +1267,7 @@ exports.deliveryMessages = deliveryMessages;
1180
1267
  exports.fieldMessages = fieldMessages;
1181
1268
  exports.findBrand = findBrand;
1182
1269
  exports.flatRates = flatRates;
1270
+ exports.isAddressFillEvent = isAddressFillEvent;
1183
1271
  exports.isQuoteChange = isQuoteChange;
1184
1272
  exports.listBrands = listBrands;
1185
1273
  exports.matchRate = matchRate;