@aranova/tracking-react 1.8.0 → 1.9.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
@@ -366,6 +366,11 @@ await submit({
366
366
  form's own questions with `formSubmit.extraFields`; a name the booking already reports
367
367
  (`phone`, `email`, ...) is dropped.
368
368
  - Ask for the last name separately, and a mobile number (the shop's confirmation text goes to it).
369
+ - Details after booking (when `booking.rules.vehicleDeferred`, or for extra notes):
370
+ `updateBooking({ vehicle, notes })` from the same hook. It resolves `updated` / `queued` /
371
+ `notified_owner` (all saved) or `invalid`, `mismatch`, `conflict`, `not_found`, `failed`
372
+ (only `failed` is worth a retry). To survive a reload, save `booked` (`{ bookingId, attemptKey }`)
373
+ in `sessionStorage` and pass it back: `updateBooking(details, saved)`.
369
374
  - Reports the `booking_integration` capability.
370
375
 
371
376
  ## On-site conversion firing
@@ -61,6 +61,8 @@ interface BookingRulesConfig {
61
61
  /** `YYYY-MM-DD` shop dates whose start hours replace their weekday's; never also closed. */
62
62
  special_hours: Record<string, BookingDayWindows>;
63
63
  vehicle_required: boolean;
64
+ /** False from an API older than the deferred vehicle. */
65
+ vehicle_deferred: boolean;
64
66
  /** Empty when the shop hides the drop-off question. */
65
67
  drop_off_options: string[];
66
68
  }
@@ -270,8 +272,43 @@ declare function uuidV4(): string;
270
272
  /** Developer-only outcomes for a simulated create; the production API ignores them. */
271
273
  declare const BOOKING_SIMULATIONS: readonly ["created", "rejected", "unknown", "failed"];
272
274
  declare const BOOKING_DROP_OFF_OPTIONS: readonly ["waitForIt", "dropOff"];
275
+ declare const BOOKING_DETAILS_STATUSES: readonly ["updated", "queued", "notified_owner"];
273
276
  type BookingDropOff = (typeof BOOKING_DROP_OFF_OPTIONS)[number];
274
277
  type BookingSimulation = (typeof BOOKING_SIMULATIONS)[number];
278
+ type BookingDetailsStatus = (typeof BOOKING_DETAILS_STATUSES)[number];
279
+ /** Added after the booking: the vehicle (when the rules defer it), notes, or both. */
280
+ interface BookingDetailsInput {
281
+ vehicle?: {
282
+ year: number;
283
+ make: string;
284
+ model: string;
285
+ } | null;
286
+ notes?: string | null;
287
+ }
288
+ interface BookingDetailsOptions {
289
+ /** The `attemptKey` the booking was submitted with; only that submit may add to it. */
290
+ attemptKey: string;
291
+ }
292
+ type BookingDetailsOutcome =
293
+ /**
294
+ * `updated`: on the shop's request. `queued`: still on its way. `notified_owner`: the shop's
295
+ * request could no longer take them, so the owner was texted. All three mean "saved".
296
+ */
297
+ {
298
+ status: BookingDetailsStatus;
299
+ id: string;
300
+ }
301
+ /**
302
+ * Not saved. `invalid`: a bad vehicle or note. `mismatch`: not this booking's attempt key.
303
+ * `conflict`: other details were already saved. `not_found`: no such booking for this key.
304
+ * `failed`: the network or the API failed; worth a retry.
305
+ */
306
+ | {
307
+ status: BookingDetailsFailure;
308
+ id: string;
309
+ message: string;
310
+ };
311
+ type BookingDetailsFailure = "invalid" | "mismatch" | "conflict" | "not_found" | "failed";
275
312
  /** What the booking form collected. Dates and hours are the shop's wall time. */
276
313
  interface BookingInput {
277
314
  /** `YYYY-MM-DD`, one of `bookableSlots(...)`'s dates. */
@@ -378,8 +415,13 @@ interface SalesBookingsClient {
378
415
  submit(input: BookingInput, options: BookingSubmitOptions): Promise<BookingOutcome>;
379
416
  /** A booking's current status by the id `submit` returned. */
380
417
  status(id: string): Promise<BookingOutcome>;
418
+ /**
419
+ * Add the vehicle or notes after booking (e.g. `rules.vehicleDeferred`). Pass the booking's
420
+ * own `attemptKey`. Never rejects: they reach the shop's request, or the owner by text.
421
+ */
422
+ updateBooking(bookingId: string, details: BookingDetailsInput, options: BookingDetailsOptions): Promise<BookingDetailsOutcome>;
381
423
  }
382
424
  /** A fresh per-submit key. Keep it for retries of the same submit; mint a new one after. */
383
425
  declare const newBookingAttemptKey: typeof uuidV4;
384
426
 
385
- export { type BookingDropOff as B, type ConversionConfig as C, type FormSubmitField as F, type SalesBookingsClient as S, type TrackingConfigReference as T, type BookingFormSubmit as a, type BookingInput as b, type BookingMode as c, type BookingOutcome as d, type BookingSimulation as e, type BookingSubmitOptions as f, type ConversionConfigStore as g, getTrackingConfigRuntime as h, resolveTrackingConfigUrl as i, type BookingDayWindows as j, type ConversionIntegrations as k, TrackingConfigRuntime as l, newBookingAttemptKey as n, resolveConversionConfig as r };
427
+ export { type BookingDetailsFailure as B, type ConversionConfig as C, type FormSubmitField as F, type SalesBookingsClient as S, type TrackingConfigReference as T, type BookingDetailsInput as a, type BookingDetailsOptions as b, type BookingDetailsOutcome as c, type BookingDetailsStatus as d, type BookingDropOff as e, type BookingFormSubmit as f, type BookingInput as g, type BookingMode as h, type BookingOutcome as i, type BookingSimulation as j, type BookingSubmitOptions as k, type ConversionConfigStore as l, getTrackingConfigRuntime as m, newBookingAttemptKey as n, resolveTrackingConfigUrl as o, type BookingDayWindows as p, type ConversionIntegrations as q, resolveConversionConfig as r, TrackingConfigRuntime as s };
@@ -61,6 +61,8 @@ interface BookingRulesConfig {
61
61
  /** `YYYY-MM-DD` shop dates whose start hours replace their weekday's; never also closed. */
62
62
  special_hours: Record<string, BookingDayWindows>;
63
63
  vehicle_required: boolean;
64
+ /** False from an API older than the deferred vehicle. */
65
+ vehicle_deferred: boolean;
64
66
  /** Empty when the shop hides the drop-off question. */
65
67
  drop_off_options: string[];
66
68
  }
@@ -270,8 +272,43 @@ declare function uuidV4(): string;
270
272
  /** Developer-only outcomes for a simulated create; the production API ignores them. */
271
273
  declare const BOOKING_SIMULATIONS: readonly ["created", "rejected", "unknown", "failed"];
272
274
  declare const BOOKING_DROP_OFF_OPTIONS: readonly ["waitForIt", "dropOff"];
275
+ declare const BOOKING_DETAILS_STATUSES: readonly ["updated", "queued", "notified_owner"];
273
276
  type BookingDropOff = (typeof BOOKING_DROP_OFF_OPTIONS)[number];
274
277
  type BookingSimulation = (typeof BOOKING_SIMULATIONS)[number];
278
+ type BookingDetailsStatus = (typeof BOOKING_DETAILS_STATUSES)[number];
279
+ /** Added after the booking: the vehicle (when the rules defer it), notes, or both. */
280
+ interface BookingDetailsInput {
281
+ vehicle?: {
282
+ year: number;
283
+ make: string;
284
+ model: string;
285
+ } | null;
286
+ notes?: string | null;
287
+ }
288
+ interface BookingDetailsOptions {
289
+ /** The `attemptKey` the booking was submitted with; only that submit may add to it. */
290
+ attemptKey: string;
291
+ }
292
+ type BookingDetailsOutcome =
293
+ /**
294
+ * `updated`: on the shop's request. `queued`: still on its way. `notified_owner`: the shop's
295
+ * request could no longer take them, so the owner was texted. All three mean "saved".
296
+ */
297
+ {
298
+ status: BookingDetailsStatus;
299
+ id: string;
300
+ }
301
+ /**
302
+ * Not saved. `invalid`: a bad vehicle or note. `mismatch`: not this booking's attempt key.
303
+ * `conflict`: other details were already saved. `not_found`: no such booking for this key.
304
+ * `failed`: the network or the API failed; worth a retry.
305
+ */
306
+ | {
307
+ status: BookingDetailsFailure;
308
+ id: string;
309
+ message: string;
310
+ };
311
+ type BookingDetailsFailure = "invalid" | "mismatch" | "conflict" | "not_found" | "failed";
275
312
  /** What the booking form collected. Dates and hours are the shop's wall time. */
276
313
  interface BookingInput {
277
314
  /** `YYYY-MM-DD`, one of `bookableSlots(...)`'s dates. */
@@ -378,8 +415,13 @@ interface SalesBookingsClient {
378
415
  submit(input: BookingInput, options: BookingSubmitOptions): Promise<BookingOutcome>;
379
416
  /** A booking's current status by the id `submit` returned. */
380
417
  status(id: string): Promise<BookingOutcome>;
418
+ /**
419
+ * Add the vehicle or notes after booking (e.g. `rules.vehicleDeferred`). Pass the booking's
420
+ * own `attemptKey`. Never rejects: they reach the shop's request, or the owner by text.
421
+ */
422
+ updateBooking(bookingId: string, details: BookingDetailsInput, options: BookingDetailsOptions): Promise<BookingDetailsOutcome>;
381
423
  }
382
424
  /** A fresh per-submit key. Keep it for retries of the same submit; mint a new one after. */
383
425
  declare const newBookingAttemptKey: typeof uuidV4;
384
426
 
385
- export { type BookingDropOff as B, type ConversionConfig as C, type FormSubmitField as F, type SalesBookingsClient as S, type TrackingConfigReference as T, type BookingFormSubmit as a, type BookingInput as b, type BookingMode as c, type BookingOutcome as d, type BookingSimulation as e, type BookingSubmitOptions as f, type ConversionConfigStore as g, getTrackingConfigRuntime as h, resolveTrackingConfigUrl as i, type BookingDayWindows as j, type ConversionIntegrations as k, TrackingConfigRuntime as l, newBookingAttemptKey as n, resolveConversionConfig as r };
427
+ export { type BookingDetailsFailure as B, type ConversionConfig as C, type FormSubmitField as F, type SalesBookingsClient as S, type TrackingConfigReference as T, type BookingDetailsInput as a, type BookingDetailsOptions as b, type BookingDetailsOutcome as c, type BookingDetailsStatus as d, type BookingDropOff as e, type BookingFormSubmit as f, type BookingInput as g, type BookingMode as h, type BookingOutcome as i, type BookingSimulation as j, type BookingSubmitOptions as k, type ConversionConfigStore as l, getTrackingConfigRuntime as m, newBookingAttemptKey as n, resolveTrackingConfigUrl as o, type BookingDayWindows as p, type ConversionIntegrations as q, resolveConversionConfig as r, TrackingConfigRuntime as s };
package/dist/client.d.mts CHANGED
@@ -1,8 +1,8 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode, CSSProperties, InputHTMLAttributes, ChangeEvent, FocusEvent, AnchorHTMLAttributes } from 'react';
3
3
  import { G as GtagEnvironmentMap, M as MetaPixelEnvironmentMap, h as ConsentState, f as ConsentChoiceState, g as ConsentSource, T as TrackingParams, b as TrackingEnvironment, j as PhoneConfig, P as ParsedPhone, k as PhoneDisplayFormat } from './phone-utils-CM8eUPz8.mjs';
4
- import { T as TrackingConfigReference, C as ConversionConfig, d as BookingOutcome, b as BookingInput, S as SalesBookingsClient, a as BookingFormSubmit, e as BookingSimulation } from './bookings-n8UUzrak.mjs';
5
- import { X as TriggerRegistryConfig, Z as TypedTrackingClient, a as BookingHandoffPrefill, b as BookingIntegration, B as BookableDay } from './ingest-typed-ABH28Ips.mjs';
4
+ import { T as TrackingConfigReference, C as ConversionConfig, i as BookingOutcome, g as BookingInput, a as BookingDetailsInput, c as BookingDetailsOutcome, S as SalesBookingsClient, f as BookingFormSubmit, j as BookingSimulation } from './bookings-DtcwdM_I.mjs';
5
+ import { X as TriggerRegistryConfig, Z as TypedTrackingClient, a as BookingHandoffPrefill, b as BookingIntegration, B as BookableDay } from './ingest-typed-C2OvZ7dp.mjs';
6
6
  import { CountryCode } from 'libphonenumber-js';
7
7
  import './form-submit-BLb28aCk.mjs';
8
8
  import 'zod/mini';
@@ -403,11 +403,25 @@ declare function BookingHandoffLink({ trackingConfig, fallbackUrl, prefill, chil
403
403
  */
404
404
  declare function useBookingSlots(trackingConfig: TrackingConfigReference | undefined): BookableDay[];
405
405
  type BookingSubmitStatus = "idle" | "submitting" | BookingOutcome["status"];
406
+ interface BookedAttempt {
407
+ bookingId: string;
408
+ attemptKey: string;
409
+ }
406
410
  interface BookingSubmitState {
407
411
  status: BookingSubmitStatus;
408
412
  outcome: BookingOutcome | null;
409
413
  /** Send the booking. A retry of an unanswered or refused submit reuses its attempt key. */
410
414
  submit(input: BookingInput): Promise<BookingOutcome>;
415
+ /**
416
+ * The recorded booking and the key it was sent with; persist it (e.g. `sessionStorage`) to add
417
+ * details after a reload. Null until the server has the booking.
418
+ */
419
+ booked: BookedAttempt | null;
420
+ /**
421
+ * Add the vehicle or notes to `booked`, or to `target` (a persisted one). Resolves `not_found`
422
+ * when there is neither.
423
+ */
424
+ updateBooking(details: BookingDetailsInput, target?: BookedAttempt): Promise<BookingDetailsOutcome>;
411
425
  /** Back to `idle` with a fresh attempt key, e.g. "book another car". */
412
426
  reset(): void;
413
427
  }
@@ -423,4 +437,4 @@ declare function useBookingSubmit(bookings: SalesBookingsClient, options?: {
423
437
  simulate?: BookingSimulation;
424
438
  }): BookingSubmitState;
425
439
 
426
- export { AdPlatformTracking, type AdPlatformTrackingProps, BookingHandoffLink, type BookingHandoffLinkProps, type BookingHandoffOptions, type BookingSubmitState, type BookingSubmitStatus, ConsentBanner, type ConsentBannerProps, type CreateTrackingOptions, type CreateTrackingResult, GoogleAdsTracking, type GoogleAdsTrackingProps, PhoneField, type PhoneFieldApi, type PhoneFieldProps, type PhoneInputProps, type TrackingProviderProps, type UseConsentResult, type UseCookiePreferencesOptions, type UseCookiePreferencesResult, type UsePhoneFieldOptions, createTracking, useBookingHandoffUrl, useBookingIntegration, useBookingSlots, useBookingSubmit, useConsent, useConsentState, useCookiePreferences, useGclid, usePhoneConfig, usePhoneField, useTrackingParams };
440
+ export { AdPlatformTracking, type AdPlatformTrackingProps, type BookedAttempt, BookingHandoffLink, type BookingHandoffLinkProps, type BookingHandoffOptions, type BookingSubmitState, type BookingSubmitStatus, ConsentBanner, type ConsentBannerProps, type CreateTrackingOptions, type CreateTrackingResult, GoogleAdsTracking, type GoogleAdsTrackingProps, PhoneField, type PhoneFieldApi, type PhoneFieldProps, type PhoneInputProps, type TrackingProviderProps, type UseConsentResult, type UseCookiePreferencesOptions, type UseCookiePreferencesResult, type UsePhoneFieldOptions, createTracking, useBookingHandoffUrl, useBookingIntegration, useBookingSlots, useBookingSubmit, useConsent, useConsentState, useCookiePreferences, useGclid, usePhoneConfig, usePhoneField, useTrackingParams };
package/dist/client.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode, CSSProperties, InputHTMLAttributes, ChangeEvent, FocusEvent, AnchorHTMLAttributes } from 'react';
3
3
  import { G as GtagEnvironmentMap, M as MetaPixelEnvironmentMap, h as ConsentState, f as ConsentChoiceState, g as ConsentSource, T as TrackingParams, b as TrackingEnvironment, j as PhoneConfig, P as ParsedPhone, k as PhoneDisplayFormat } from './phone-utils-l8ey3Zdm.js';
4
- import { T as TrackingConfigReference, C as ConversionConfig, d as BookingOutcome, b as BookingInput, S as SalesBookingsClient, a as BookingFormSubmit, e as BookingSimulation } from './bookings-B50X7GSG.js';
5
- import { X as TriggerRegistryConfig, Z as TypedTrackingClient, a as BookingHandoffPrefill, b as BookingIntegration, B as BookableDay } from './ingest-typed-vP-DqpxO.js';
4
+ import { T as TrackingConfigReference, C as ConversionConfig, i as BookingOutcome, g as BookingInput, a as BookingDetailsInput, c as BookingDetailsOutcome, S as SalesBookingsClient, f as BookingFormSubmit, j as BookingSimulation } from './bookings-Bx7totlS.js';
5
+ import { X as TriggerRegistryConfig, Z as TypedTrackingClient, a as BookingHandoffPrefill, b as BookingIntegration, B as BookableDay } from './ingest-typed-B-BgEZdW.js';
6
6
  import { CountryCode } from 'libphonenumber-js';
7
7
  import './form-submit-BLb28aCk.js';
8
8
  import 'zod/mini';
@@ -403,11 +403,25 @@ declare function BookingHandoffLink({ trackingConfig, fallbackUrl, prefill, chil
403
403
  */
404
404
  declare function useBookingSlots(trackingConfig: TrackingConfigReference | undefined): BookableDay[];
405
405
  type BookingSubmitStatus = "idle" | "submitting" | BookingOutcome["status"];
406
+ interface BookedAttempt {
407
+ bookingId: string;
408
+ attemptKey: string;
409
+ }
406
410
  interface BookingSubmitState {
407
411
  status: BookingSubmitStatus;
408
412
  outcome: BookingOutcome | null;
409
413
  /** Send the booking. A retry of an unanswered or refused submit reuses its attempt key. */
410
414
  submit(input: BookingInput): Promise<BookingOutcome>;
415
+ /**
416
+ * The recorded booking and the key it was sent with; persist it (e.g. `sessionStorage`) to add
417
+ * details after a reload. Null until the server has the booking.
418
+ */
419
+ booked: BookedAttempt | null;
420
+ /**
421
+ * Add the vehicle or notes to `booked`, or to `target` (a persisted one). Resolves `not_found`
422
+ * when there is neither.
423
+ */
424
+ updateBooking(details: BookingDetailsInput, target?: BookedAttempt): Promise<BookingDetailsOutcome>;
411
425
  /** Back to `idle` with a fresh attempt key, e.g. "book another car". */
412
426
  reset(): void;
413
427
  }
@@ -423,4 +437,4 @@ declare function useBookingSubmit(bookings: SalesBookingsClient, options?: {
423
437
  simulate?: BookingSimulation;
424
438
  }): BookingSubmitState;
425
439
 
426
- export { AdPlatformTracking, type AdPlatformTrackingProps, BookingHandoffLink, type BookingHandoffLinkProps, type BookingHandoffOptions, type BookingSubmitState, type BookingSubmitStatus, ConsentBanner, type ConsentBannerProps, type CreateTrackingOptions, type CreateTrackingResult, GoogleAdsTracking, type GoogleAdsTrackingProps, PhoneField, type PhoneFieldApi, type PhoneFieldProps, type PhoneInputProps, type TrackingProviderProps, type UseConsentResult, type UseCookiePreferencesOptions, type UseCookiePreferencesResult, type UsePhoneFieldOptions, createTracking, useBookingHandoffUrl, useBookingIntegration, useBookingSlots, useBookingSubmit, useConsent, useConsentState, useCookiePreferences, useGclid, usePhoneConfig, usePhoneField, useTrackingParams };
440
+ export { AdPlatformTracking, type AdPlatformTrackingProps, type BookedAttempt, BookingHandoffLink, type BookingHandoffLinkProps, type BookingHandoffOptions, type BookingSubmitState, type BookingSubmitStatus, ConsentBanner, type ConsentBannerProps, type CreateTrackingOptions, type CreateTrackingResult, GoogleAdsTracking, type GoogleAdsTrackingProps, PhoneField, type PhoneFieldApi, type PhoneFieldProps, type PhoneInputProps, type TrackingProviderProps, type UseConsentResult, type UseCookiePreferencesOptions, type UseCookiePreferencesResult, type UsePhoneFieldOptions, createTracking, useBookingHandoffUrl, useBookingIntegration, useBookingSlots, useBookingSubmit, useConsent, useConsentState, useCookiePreferences, useGclid, usePhoneConfig, usePhoneField, useTrackingParams };
package/dist/client.js CHANGED
@@ -1028,6 +1028,7 @@ function parseBookingRules(value) {
1028
1028
  closed_dates: isStringList(r.closed_dates) ? r.closed_dates : [],
1029
1029
  special_hours: parseWindowsMap(r.special_hours),
1030
1030
  vehicle_required: r.vehicle_required === true,
1031
+ vehicle_deferred: r.vehicle_deferred === true,
1031
1032
  drop_off_options: isStringList(r.drop_off_options) ? r.drop_off_options : []
1032
1033
  };
1033
1034
  }
@@ -1763,6 +1764,7 @@ function camelRules(rules) {
1763
1764
  closedDates: rules.closed_dates,
1764
1765
  specialHours: rules.special_hours,
1765
1766
  vehicleRequired: rules.vehicle_required,
1767
+ vehicleDeferred: rules.vehicle_deferred,
1766
1768
  dropOffOptions: rules.drop_off_options
1767
1769
  };
1768
1770
  }
@@ -3327,6 +3329,7 @@ function attachMultiPageSession(client, config) {
3327
3329
  }
3328
3330
 
3329
3331
  // ../tracking-core/src/triggers/form-start.ts
3332
+ var TAP_SLOP_PX = 10;
3330
3333
  function attachFormStart(client, config) {
3331
3334
  if (typeof window === "undefined" || typeof document === "undefined") {
3332
3335
  return () => {
@@ -3335,6 +3338,7 @@ function attachFormStart(client, config) {
3335
3338
  const selector = config.selector ?? "form";
3336
3339
  let firedForms = /* @__PURE__ */ new Set();
3337
3340
  let currentPath2 = window.location.pathname;
3341
+ let pendingTap = null;
3338
3342
  function getFormKey(form) {
3339
3343
  if (form.id) return `id:${form.id}`;
3340
3344
  const explicitAction = form.getAttribute("action");
@@ -3345,10 +3349,23 @@ function attachFormStart(client, config) {
3345
3349
  function onFocusIn(event) {
3346
3350
  startFrom(event.target);
3347
3351
  }
3348
- function onPointerDown(event) {
3352
+ function onClick(event) {
3349
3353
  if (event.button !== 0) return;
3350
3354
  startFrom(event.target);
3351
3355
  }
3356
+ function onPointerDown(event) {
3357
+ pendingTap = event.button === 0 ? { pointerId: event.pointerId, x: event.clientX, y: event.clientY, target: event.target } : null;
3358
+ }
3359
+ function onPointerCancel() {
3360
+ pendingTap = null;
3361
+ }
3362
+ function onPointerUp(event) {
3363
+ const tap = pendingTap;
3364
+ pendingTap = null;
3365
+ if (!tap || tap.pointerId !== event.pointerId) return;
3366
+ if (Math.hypot(event.clientX - tap.x, event.clientY - tap.y) > TAP_SLOP_PX) return;
3367
+ startFrom(tap.target);
3368
+ }
3352
3369
  function startFrom(target) {
3353
3370
  if (!(target instanceof Element)) return;
3354
3371
  const form = target.closest(selector);
@@ -3385,17 +3402,24 @@ function attachFormStart(client, config) {
3385
3402
  originalReplaceState(...args);
3386
3403
  setTimeout(resetIfPathChanged, 0);
3387
3404
  }
3405
+ const passiveCapture = { capture: true, passive: true };
3388
3406
  history.pushState = patchedPushState;
3389
3407
  history.replaceState = patchedReplaceState;
3390
3408
  window.addEventListener("popstate", resetIfPathChanged);
3391
3409
  document.addEventListener("focusin", onFocusIn);
3392
- document.addEventListener("pointerdown", onPointerDown, { capture: true, passive: true });
3410
+ document.addEventListener("click", onClick, passiveCapture);
3411
+ document.addEventListener("pointerdown", onPointerDown, passiveCapture);
3412
+ document.addEventListener("pointerup", onPointerUp, passiveCapture);
3413
+ document.addEventListener("pointercancel", onPointerCancel, passiveCapture);
3393
3414
  return () => {
3394
3415
  history.pushState = originalPushState;
3395
3416
  history.replaceState = originalReplaceState;
3396
3417
  window.removeEventListener("popstate", resetIfPathChanged);
3397
3418
  document.removeEventListener("focusin", onFocusIn);
3419
+ document.removeEventListener("click", onClick, { capture: true });
3398
3420
  document.removeEventListener("pointerdown", onPointerDown, { capture: true });
3421
+ document.removeEventListener("pointerup", onPointerUp, { capture: true });
3422
+ document.removeEventListener("pointercancel", onPointerCancel, { capture: true });
3399
3423
  };
3400
3424
  }
3401
3425
 
@@ -3985,7 +4009,7 @@ function GoogleAdsTracking(props) {
3985
4009
  var import_react6 = require("react");
3986
4010
 
3987
4011
  // package.json
3988
- var version = "1.8.0";
4012
+ var version = "1.9.0";
3989
4013
 
3990
4014
  // ../tracking-core/src/phone-react.tsx
3991
4015
  var import_react5 = require("react");
@@ -4301,25 +4325,31 @@ function useBookingSubmit(bookings, options = {}) {
4301
4325
  const [outcome, setOutcome] = (0, import_react7.useState)(null);
4302
4326
  const [submitting, setSubmitting] = (0, import_react7.useState)(false);
4303
4327
  const attemptKey = (0, import_react7.useRef)(null);
4328
+ const [booked, setBooked] = (0, import_react7.useState)(null);
4304
4329
  const inFlight = (0, import_react7.useRef)(null);
4305
4330
  const { formSubmit, simulate } = options;
4306
4331
  const submit = (0, import_react7.useCallback)(
4307
4332
  (input) => {
4308
4333
  if (inFlight.current) return inFlight.current;
4309
4334
  attemptKey.current ?? (attemptKey.current = newBookingAttemptKey());
4335
+ const key = attemptKey.current;
4310
4336
  setSubmitting(true);
4311
4337
  const run = bookings.submit(input, {
4312
- attemptKey: attemptKey.current,
4338
+ attemptKey: key,
4313
4339
  formSubmit,
4314
4340
  simulate,
4315
4341
  // The server has it: show the pending success while the final answer is polled.
4316
4342
  onPending: (pending) => {
4343
+ setBooked({ bookingId: pending.id, attemptKey: key });
4317
4344
  setOutcome(pending);
4318
4345
  setSubmitting(false);
4319
4346
  }
4320
4347
  }).then((result) => {
4321
4348
  const unrecorded = result.status === "invalid" || result.status === "stale" || result.status === "failed" && result.id === null;
4322
4349
  if (!unrecorded) attemptKey.current = null;
4350
+ if (result.status !== "invalid" && result.status !== "stale" && result.id) {
4351
+ setBooked({ bookingId: result.id, attemptKey: key });
4352
+ }
4323
4353
  setOutcome(result);
4324
4354
  return result;
4325
4355
  }).finally(() => {
@@ -4331,12 +4361,23 @@ function useBookingSubmit(bookings, options = {}) {
4331
4361
  },
4332
4362
  [bookings, formSubmit, simulate]
4333
4363
  );
4364
+ const updateBooking = (0, import_react7.useCallback)(
4365
+ async (details, target) => {
4366
+ const attempt = target ?? booked;
4367
+ if (!attempt) return { status: "not_found", id: "", message: "Nothing has been booked yet." };
4368
+ return bookings.updateBooking(attempt.bookingId, details, {
4369
+ attemptKey: attempt.attemptKey
4370
+ });
4371
+ },
4372
+ [bookings, booked]
4373
+ );
4334
4374
  const reset = (0, import_react7.useCallback)(() => {
4335
4375
  attemptKey.current = null;
4376
+ setBooked(null);
4336
4377
  setOutcome(null);
4337
4378
  }, []);
4338
4379
  const status = submitting ? "submitting" : outcome?.status ?? "idle";
4339
- return { status, outcome, submit, reset };
4380
+ return { status, outcome, booked, submit, updateBooking, reset };
4340
4381
  }
4341
4382
  // Annotate the CommonJS export names for ESM import in node:
4342
4383
  0 && (module.exports = {