@aranova/tracking-react 0.11.0 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -16,31 +16,26 @@ function buildConsentPayload(state) {
16
16
  };
17
17
  }
18
18
  function getConsentState() {
19
- if (typeof window === "undefined")
20
- return "pending";
19
+ if (typeof window === "undefined") return "pending";
21
20
  const storedState = window.localStorage.getItem(CONSENT_STATE_KEY);
22
- if (storedState === "granted" || storedState === "denied")
23
- return storedState;
21
+ if (storedState === "granted" || storedState === "denied") return storedState;
24
22
  return "pending";
25
23
  }
26
24
  function setConsentState(state) {
27
- if (typeof window === "undefined")
28
- return;
25
+ if (typeof window === "undefined") return;
29
26
  window.localStorage.setItem(CONSENT_STATE_KEY, state);
30
27
  window.localStorage.setItem(CONSENT_TIMESTAMP_KEY, (/* @__PURE__ */ new Date()).toISOString());
31
28
  if (typeof window.gtag === "function")
32
29
  window.gtag("consent", "update", buildConsentPayload(state));
33
30
  }
34
31
  function resetConsent() {
35
- if (typeof window === "undefined")
36
- return;
32
+ if (typeof window === "undefined") return;
37
33
  window.localStorage.removeItem(CONSENT_STATE_KEY);
38
34
  window.localStorage.removeItem(CONSENT_TIMESTAMP_KEY);
39
35
  }
40
36
  function restoreStoredConsent() {
41
37
  const consentState = getConsentState();
42
- if (consentState === "granted" || consentState === "denied")
43
- setConsentState(consentState);
38
+ if (consentState === "granted" || consentState === "denied") setConsentState(consentState);
44
39
  return consentState;
45
40
  }
46
41
 
@@ -97,8 +92,7 @@ function isValidGtagId(id) {
97
92
  }
98
93
  function ensureGtagFunction() {
99
94
  window.dataLayer = window.dataLayer || [];
100
- if (typeof window.gtag === "function")
101
- return window.gtag;
95
+ if (typeof window.gtag === "function") return window.gtag;
102
96
  window.gtag = (...args) => {
103
97
  window.dataLayer?.push(args);
104
98
  };
@@ -115,17 +109,17 @@ function applyDefaultConsentState() {
115
109
  });
116
110
  }
117
111
  function loadGtagScript(gtagId) {
118
- if (typeof document === "undefined")
119
- return;
112
+ if (typeof document === "undefined") return;
120
113
  const marker = getScriptMarker("gtag-loader");
121
- const existingScript = document.querySelector(`script[${TRACKING_SCRIPT_ATTRIBUTE}="${marker}"]`);
122
- if (existingScript)
123
- return;
114
+ const existingScript = document.querySelector(
115
+ `script[${TRACKING_SCRIPT_ATTRIBUTE}="${marker}"]`
116
+ );
117
+ if (existingScript) return;
124
118
  const script = document.createElement("script");
125
119
  script.async = true;
126
120
  script.src = `${GTAG_SCRIPT_HOST}?id=${encodeURIComponent(gtagId)}`;
127
121
  script.setAttribute(TRACKING_SCRIPT_ATTRIBUTE, marker);
128
- document.head.appendChild(script);
122
+ document.head.append(script);
129
123
  }
130
124
  function initializeGtag(gtagId) {
131
125
  const gtag = ensureGtagFunction();
@@ -133,21 +127,17 @@ function initializeGtag(gtagId) {
133
127
  gtag("config", gtagId);
134
128
  }
135
129
  function bootstrapGoogleAdsTracking(gtagId) {
136
- if (typeof window === "undefined" || typeof document === "undefined")
137
- return;
138
- if (!isValidGtagId(gtagId))
139
- return;
130
+ if (typeof window === "undefined" || typeof document === "undefined") return;
131
+ if (!isValidGtagId(gtagId)) return;
140
132
  applyDefaultConsentState();
141
133
  loadGtagScript(gtagId);
142
134
  initializeGtag(gtagId);
143
135
  restoreStoredConsent();
144
136
  }
145
137
  function bootstrapMultipleGtags(gtagIds) {
146
- if (typeof window === "undefined" || typeof document === "undefined")
147
- return;
138
+ if (typeof window === "undefined" || typeof document === "undefined") return;
148
139
  const ids = Object.values(gtagIds).filter(isValidGtagId);
149
- if (ids.length === 0)
150
- return;
140
+ if (ids.length === 0) return;
151
141
  applyDefaultConsentState();
152
142
  loadGtagScript(ids[0]);
153
143
  const gtag = ensureGtagFunction();
@@ -199,18 +189,15 @@ function getTrackingQueryValues(searchParams) {
199
189
  }, {});
200
190
  }
201
191
  function getCookieValueFromDocument(key) {
202
- if (typeof document === "undefined")
203
- return null;
192
+ if (typeof document === "undefined") return null;
204
193
  const cookies = document.cookie ? document.cookie.split("; ") : [];
205
194
  const match = cookies.find((cookie) => cookie.startsWith(`${key}=`));
206
- if (!match)
207
- return null;
195
+ if (!match) return null;
208
196
  const [, rawValue = ""] = match.split("=");
209
197
  return normalizeTrackingCookieValue(decodeURIComponent(rawValue));
210
198
  }
211
199
  function setTrackingCookie(key, value, maxAgeSeconds = TRACKING_COOKIE_MAX_AGE_SECONDS) {
212
- if (typeof document === "undefined")
213
- return;
200
+ if (typeof document === "undefined") return;
214
201
  const encodedValue = encodeURIComponent(value);
215
202
  document.cookie = `${key}=${encodedValue}; Max-Age=${maxAgeSeconds}; Path=/; SameSite=Lax`;
216
203
  }
@@ -250,18 +237,15 @@ function writeLocalStorage(key, value) {
250
237
  }
251
238
  }
252
239
  function getVisitorId() {
253
- if (typeof window === "undefined")
254
- return safeUuid();
240
+ if (typeof window === "undefined") return safeUuid();
255
241
  const existing = readLocalStorage(VISITOR_STORAGE_KEY);
256
- if (existing && existing.length > 0)
257
- return existing;
242
+ if (existing && existing.length > 0) return existing;
258
243
  const fresh = safeUuid();
259
244
  writeLocalStorage(VISITOR_STORAGE_KEY, fresh);
260
245
  return fresh;
261
246
  }
262
247
  function getOrRotateSessionId(now = Date.now()) {
263
- if (typeof window === "undefined")
264
- return { id: safeUuid(), isNew: true };
248
+ if (typeof window === "undefined") return { id: safeUuid(), isNew: true };
265
249
  const raw = readLocalStorage(SESSION_STORAGE_KEY);
266
250
  if (raw) {
267
251
  try {
@@ -334,8 +318,7 @@ function setLastFiredUrl(url) {
334
318
  writeSessionStorage(LAST_FIRED_URL_STORAGE_KEY, url);
335
319
  }
336
320
  function buildPageViewMetadata(referrerOverride) {
337
- if (typeof window === "undefined" || typeof document === "undefined")
338
- return null;
321
+ if (typeof window === "undefined" || typeof document === "undefined") return null;
339
322
  return pageViewMetadataSchema.parse({
340
323
  page: {
341
324
  title: document.title || null,
@@ -348,8 +331,7 @@ function buildPageViewMetadata(referrerOverride) {
348
331
  });
349
332
  }
350
333
  function fireManualPageView(client) {
351
- if (typeof window === "undefined")
352
- return;
334
+ if (typeof window === "undefined") return;
353
335
  const currentHref = window.location.href;
354
336
  const previousFiredUrl = getLastFiredUrl();
355
337
  const internalReferrer = previousFiredUrl !== null && previousFiredUrl !== currentHref ? previousFiredUrl : null;
@@ -385,8 +367,7 @@ function attachAutoPageView(client, options = {}) {
385
367
  let lastPath = window.location.pathname + window.location.search;
386
368
  function maybeFire() {
387
369
  const current = window.location.pathname + window.location.search;
388
- if (current === lastPath)
389
- return;
370
+ if (current === lastPath) return;
390
371
  lastPath = current;
391
372
  fireManualPageView(client);
392
373
  }
@@ -408,8 +389,7 @@ function attachAutoPageView(client, options = {}) {
408
389
  history.replaceState = patchedReplaceState;
409
390
  window.addEventListener("popstate", maybeFire);
410
391
  window.addEventListener("pageshow", handlePageShow);
411
- if (!options.skipInitial)
412
- fireManualPageView(client);
392
+ if (!options.skipInitial) fireManualPageView(client);
413
393
  return () => {
414
394
  history.pushState = originalPushState;
415
395
  history.replaceState = originalReplaceState;
@@ -491,8 +471,7 @@ function buildContext(surface, sdkVersion, packageName, environment, activeGtagI
491
471
  };
492
472
  }
493
473
  function readTrackingParams() {
494
- if (typeof window === "undefined")
495
- return createEmptyTrackingParams();
474
+ if (typeof window === "undefined") return createEmptyTrackingParams();
496
475
  try {
497
476
  captureTrackingParamsFromLocation();
498
477
  } catch {
@@ -507,8 +486,7 @@ function consentSnapshot() {
507
486
  }
508
487
  }
509
488
  async function postWithFetch(url, body, apiKey, identity, keepalive) {
510
- if (typeof fetch !== "function")
511
- return;
489
+ if (typeof fetch !== "function") return;
512
490
  try {
513
491
  await fetch(url, {
514
492
  method: "POST",
@@ -568,8 +546,7 @@ function createTrackingClient(config) {
568
546
  const visitorId = getVisitorId();
569
547
  const initialSession = getOrRotateSessionId();
570
548
  let sessionId = initialSession.id;
571
- if (typeof window !== "undefined")
572
- firstPage = window.location.href;
549
+ if (typeof window !== "undefined") firstPage = window.location.href;
573
550
  function enqueueHeartbeat() {
574
551
  const metadata = buildHeartbeatMetadata(
575
552
  config.surface,
@@ -595,7 +572,13 @@ function createTrackingClient(config) {
595
572
  }
596
573
  sessionId = rotated.id;
597
574
  const params = readTrackingParams();
598
- const context = buildContext(config.surface, sdkVersion, packageName, environment, activeGtagIds);
575
+ const context = buildContext(
576
+ config.surface,
577
+ sdkVersion,
578
+ packageName,
579
+ environment,
580
+ activeGtagIds
581
+ );
599
582
  return {
600
583
  session_id: sessionId,
601
584
  visitor_id: visitorId,
@@ -612,8 +595,7 @@ function createTrackingClient(config) {
612
595
  };
613
596
  }
614
597
  function scheduleFlush() {
615
- if (flushTimer !== null || destroyed)
616
- return;
598
+ if (flushTimer !== null || destroyed) return;
617
599
  flushTimer = setTimeout(() => {
618
600
  flushTimer = null;
619
601
  void flush();
@@ -626,8 +608,7 @@ function createTrackingClient(config) {
626
608
  }
627
609
  }
628
610
  async function flush() {
629
- if (queue.length === 0)
630
- return;
611
+ if (queue.length === 0) return;
631
612
  const events = queue.slice(0, HARD_MAX_BATCH);
632
613
  queue = queue.slice(events.length);
633
614
  clearScheduledFlush();
@@ -639,10 +620,8 @@ function createTrackingClient(config) {
639
620
  await postWithFetch(eventsUrl, serialized, config.apiKey, identityHeaders2, false);
640
621
  }
641
622
  function trackEvent(input) {
642
- if (destroyed)
643
- return;
644
- if (!input || typeof input.eventType !== "string" || input.eventType.length === 0)
645
- return;
623
+ if (destroyed) return;
624
+ if (!input || typeof input.eventType !== "string" || input.eventType.length === 0) return;
646
625
  const occurredAt = input.occurredAt instanceof Date ? input.occurredAt.toISOString() : typeof input.occurredAt === "string" ? input.occurredAt : (/* @__PURE__ */ new Date()).toISOString();
647
626
  queue.push({
648
627
  event_type: input.eventType,
@@ -657,8 +636,7 @@ function createTrackingClient(config) {
657
636
  }
658
637
  }
659
638
  function flushOnUnload() {
660
- if (queue.length === 0)
661
- return;
639
+ if (queue.length === 0) return;
662
640
  const events = queue.slice(0, HARD_MAX_BATCH);
663
641
  queue = queue.slice(events.length);
664
642
  clearScheduledFlush();
@@ -1333,21 +1311,40 @@ function createSalesClient(config) {
1333
1311
  return salesRequest(config, "POST", "/sales", body);
1334
1312
  },
1335
1313
  async list(query) {
1336
- const { cursor, limit, ...filters } = query ?? {};
1314
+ const { cursor, limit, sort, order, want_total, ...filters } = query ?? {};
1337
1315
  return salesRequest(config, "POST", "/sales/query", {
1338
1316
  filters,
1339
1317
  ...limit !== void 0 ? { limit } : {},
1340
- ...cursor !== void 0 ? { cursor } : {}
1318
+ ...cursor !== void 0 ? { cursor } : {},
1319
+ ...sort !== void 0 ? { sort } : {},
1320
+ ...order !== void 0 ? { order } : {},
1321
+ ...want_total !== void 0 ? { want_total } : {}
1341
1322
  });
1342
1323
  },
1343
1324
  async summary(query) {
1344
- const { range, include_categories, include_deleted_services, top_n, ...filters } = query;
1325
+ const {
1326
+ range,
1327
+ include_categories,
1328
+ include_deleted_services,
1329
+ top_n,
1330
+ since,
1331
+ until,
1332
+ timezone,
1333
+ granularity,
1334
+ compare_to,
1335
+ ...filters
1336
+ } = query;
1345
1337
  return salesRequest(config, "POST", "/sales/summary", {
1346
1338
  filters,
1347
- range,
1339
+ ...range !== void 0 ? { range } : {},
1348
1340
  ...include_categories !== void 0 ? { include_categories } : {},
1349
1341
  ...include_deleted_services !== void 0 ? { include_deleted_services } : {},
1350
- ...top_n !== void 0 ? { top_n } : {}
1342
+ ...top_n !== void 0 ? { top_n } : {},
1343
+ ...since !== void 0 ? { since } : {},
1344
+ ...until !== void 0 ? { until } : {},
1345
+ ...timezone !== void 0 ? { timezone } : {},
1346
+ ...granularity !== void 0 ? { granularity } : {},
1347
+ ...compare_to !== void 0 ? { compare_to } : {}
1351
1348
  });
1352
1349
  },
1353
1350
  async get(id) {
@@ -1358,15 +1355,53 @@ function createSalesClient(config) {
1358
1355
  },
1359
1356
  async delete(id) {
1360
1357
  await salesRequest(config, "DELETE", `/sales/${id}`);
1358
+ },
1359
+ customers: {
1360
+ async list(query) {
1361
+ const { segment, sort, order, cursor, limit, want_total, ...filters } = query ?? {};
1362
+ return salesRequest(config, "POST", "/customers/query", {
1363
+ filters,
1364
+ ...segment !== void 0 ? { segment } : {},
1365
+ ...sort !== void 0 ? { sort } : {},
1366
+ ...order !== void 0 ? { order } : {},
1367
+ ...cursor !== void 0 ? { cursor } : {},
1368
+ ...limit !== void 0 ? { limit } : {},
1369
+ ...want_total !== void 0 ? { want_total } : {}
1370
+ });
1371
+ },
1372
+ async get(id, options) {
1373
+ const params = new URLSearchParams();
1374
+ if (options?.include_sales !== void 0)
1375
+ params.set("include_sales", String(options.include_sales));
1376
+ if (options?.limit !== void 0) params.set("limit", String(options.limit));
1377
+ if (options?.cursor != null) params.set("cursor", options.cursor);
1378
+ const qs = params.toString();
1379
+ return salesRequest(
1380
+ config,
1381
+ "GET",
1382
+ `/customers/${encodeURIComponent(id)}${qs ? `?${qs}` : ""}`
1383
+ );
1384
+ },
1385
+ async summary(query) {
1386
+ const { range, since, until, timezone, compare_to, ...filters } = query ?? {};
1387
+ return salesRequest(config, "POST", "/customers/summary", {
1388
+ filters,
1389
+ ...range !== void 0 ? { range } : {},
1390
+ ...since !== void 0 ? { since } : {},
1391
+ ...until !== void 0 ? { until } : {},
1392
+ ...timezone !== void 0 ? { timezone } : {},
1393
+ ...compare_to !== void 0 ? { compare_to } : {}
1394
+ });
1395
+ }
1396
+ },
1397
+ business: {
1398
+ async config() {
1399
+ return salesRequest(config, "GET", "/business/config");
1400
+ }
1361
1401
  }
1362
1402
  };
1363
1403
  }
1364
1404
 
1365
- // ../tracking-core/src/resources/services.ts
1366
- async function fetchServices(config) {
1367
- return salesRequest(config, "GET", "/services");
1368
- }
1369
-
1370
1405
  // ../tracking-core/src/resources/sales/money.ts
1371
1406
  var MINOR_UNIT_EXPONENT = {
1372
1407
  USD: 2,
@@ -1386,6 +1421,181 @@ function formatMoney(cents, currency, locale) {
1386
1421
  fromMinor(cents, currency)
1387
1422
  );
1388
1423
  }
1424
+ function formatDateInTz(iso, timeZone, opts, locale) {
1425
+ const date = new Date(iso);
1426
+ if (Number.isNaN(date.getTime())) return iso;
1427
+ return new Intl.DateTimeFormat(locale, {
1428
+ year: "numeric",
1429
+ month: "short",
1430
+ day: "2-digit",
1431
+ hour: "2-digit",
1432
+ minute: "2-digit",
1433
+ ...opts,
1434
+ timeZone
1435
+ }).format(date);
1436
+ }
1437
+
1438
+ // ../tracking-core/src/resources/sales/schema.ts
1439
+ import { z as z11 } from "zod";
1440
+ var SUPPORTED_CURRENCIES = ["USD", "CAD"];
1441
+ var TRACKING_ENVIRONMENTS = ["production", "development"];
1442
+ var currencySchema = z11.enum(SUPPORTED_CURRENCIES);
1443
+ var centsSchema = z11.number().int().nonnegative();
1444
+ var quantitySchema = z11.string().regex(/^\d+(\.\d{1,3})?$/);
1445
+ var metadataSchema = z11.record(z11.unknown());
1446
+ var saleItemSchema = z11.object({
1447
+ external_item_id: z11.string().nullable().optional(),
1448
+ name: z11.string().nullable().optional(),
1449
+ category: z11.string().nullable().optional(),
1450
+ quantity: quantitySchema,
1451
+ unit_price_cents: centsSchema,
1452
+ // Non-negativity validated on the wire — same contract as the other cents
1453
+ // fields — and backstopped by the DB CHECK.
1454
+ unit_cost_cents: centsSchema.nullable().optional()
1455
+ }).strict();
1456
+ var saleServiceSchema = z11.object({
1457
+ service: z11.string(),
1458
+ amount_cents: centsSchema
1459
+ }).strict();
1460
+ var customerNameSchema = z11.string().max(200);
1461
+ var customerPhoneSchema = z11.string().max(64);
1462
+ var customerEmailSchema = z11.string().max(320).email();
1463
+ function refineServiceXor(val, ctx, { requireAmount }) {
1464
+ if (val.services != null) {
1465
+ if (val.service != null) {
1466
+ ctx.addIssue({
1467
+ code: z11.ZodIssueCode.custom,
1468
+ message: "pass either `service` or `services`, not both",
1469
+ path: ["services"]
1470
+ });
1471
+ }
1472
+ if (val.services.length === 0) {
1473
+ ctx.addIssue({
1474
+ code: z11.ZodIssueCode.custom,
1475
+ message: "`services` must not be empty",
1476
+ path: ["services"]
1477
+ });
1478
+ }
1479
+ const keys = val.services.map((s) => s.service);
1480
+ if (new Set(keys).size !== keys.length) {
1481
+ ctx.addIssue({
1482
+ code: z11.ZodIssueCode.custom,
1483
+ message: "`services` must not list the same service more than once",
1484
+ path: ["services"]
1485
+ });
1486
+ }
1487
+ if (val.amount_total_cents != null) {
1488
+ const sum = val.services.reduce((acc, s) => acc + s.amount_cents, 0);
1489
+ if (val.amount_total_cents !== sum) {
1490
+ ctx.addIssue({
1491
+ code: z11.ZodIssueCode.custom,
1492
+ message: "amount_total_cents must equal the sum of the services amounts (omit it to derive it automatically)",
1493
+ path: ["amount_total_cents"]
1494
+ });
1495
+ }
1496
+ }
1497
+ } else if (requireAmount && val.amount_total_cents == null) {
1498
+ ctx.addIssue({
1499
+ code: z11.ZodIssueCode.custom,
1500
+ message: "amount_total_cents is required unless `services` is provided",
1501
+ path: ["amount_total_cents"]
1502
+ });
1503
+ }
1504
+ }
1505
+ var saleCreateSchema = z11.object({
1506
+ external_id: z11.string().nullable().optional(),
1507
+ description: z11.string().nullable().optional(),
1508
+ service: z11.string().nullable().optional(),
1509
+ services: z11.array(saleServiceSchema).nullable().optional(),
1510
+ currency: currencySchema,
1511
+ // Optional only because the plural `services` form derives it from the sum
1512
+ // (see refineServiceXor); the singular/serviceless path still requires it.
1513
+ amount_total_cents: centsSchema.nullable().optional(),
1514
+ occurred_at: z11.string().datetime(),
1515
+ environment: z11.enum(TRACKING_ENVIRONMENTS).default("production"),
1516
+ items: z11.array(saleItemSchema).default([]),
1517
+ metadata: metadataSchema.nullable().optional(),
1518
+ customer_name: customerNameSchema.nullable().optional(),
1519
+ customer_phone: customerPhoneSchema.nullable().optional(),
1520
+ customer_email: customerEmailSchema.nullable().optional()
1521
+ }).strict().superRefine((val, ctx) => refineServiceXor(val, ctx, { requireAmount: true }));
1522
+ var saleUpdateSchema = z11.object({
1523
+ description: z11.string().nullable().optional(),
1524
+ service: z11.string().nullable().optional(),
1525
+ services: z11.array(saleServiceSchema).nullable().optional(),
1526
+ currency: currencySchema.optional(),
1527
+ amount_total_cents: centsSchema.optional(),
1528
+ occurred_at: z11.string().datetime().optional(),
1529
+ items: z11.array(saleItemSchema).optional(),
1530
+ metadata: metadataSchema.nullable().optional(),
1531
+ customer_name: customerNameSchema.nullable().optional(),
1532
+ customer_phone: customerPhoneSchema.nullable().optional(),
1533
+ customer_email: customerEmailSchema.nullable().optional()
1534
+ }).strict().superRefine((val, ctx) => refineServiceXor(val, ctx, { requireAmount: false }));
1535
+ var TRACKING_RANGES = ["24h", "7d", "30d"];
1536
+ var NAMED_RANGES = [
1537
+ "today",
1538
+ "yesterday",
1539
+ "wtd",
1540
+ "mtd",
1541
+ "qtd",
1542
+ "ytd",
1543
+ "24h",
1544
+ "7d",
1545
+ "30d",
1546
+ "90d",
1547
+ "custom"
1548
+ ];
1549
+
1550
+ // ../tracking-core/src/resources/services.ts
1551
+ async function fetchServices(config) {
1552
+ return salesRequest(config, "GET", "/services");
1553
+ }
1554
+
1555
+ // ../tracking-core/src/phone.ts
1556
+ import { AsYouType, parsePhoneNumberFromString } from "libphonenumber-js";
1557
+ var DEFAULT_PHONE_COUNTRY = "CA";
1558
+ function parsePhone(raw, country) {
1559
+ const region = country ?? DEFAULT_PHONE_COUNTRY;
1560
+ const parsed = parsePhoneNumberFromString(raw ?? "", region);
1561
+ if (!parsed) {
1562
+ return { e164: null, national: "", international: "", country: region, isValid: false };
1563
+ }
1564
+ const isValid = parsed.isValid();
1565
+ return {
1566
+ // E.164 is only surfaced for a *valid* number — a possible-but-invalid input
1567
+ // (e.g. too few digits) still parses but must not be transmitted.
1568
+ e164: isValid ? parsed.number : null,
1569
+ national: parsed.formatNational(),
1570
+ international: parsed.formatInternational(),
1571
+ country: parsed.country ?? region,
1572
+ isValid
1573
+ };
1574
+ }
1575
+ function toE164(raw, country) {
1576
+ return parsePhone(raw, country).e164;
1577
+ }
1578
+ function formatPhone(value, format = "national", country) {
1579
+ const parsed = parsePhone(value, country);
1580
+ if (typeof format === "function") return format(parsed);
1581
+ switch (format) {
1582
+ case "international":
1583
+ return parsed.international || value;
1584
+ case "e164":
1585
+ return parsed.e164 ?? value;
1586
+ case "national":
1587
+ default:
1588
+ return parsed.national || value;
1589
+ }
1590
+ }
1591
+ function formatPhoneAsTyped(raw, country) {
1592
+ return new AsYouType(country ?? DEFAULT_PHONE_COUNTRY).input(raw ?? "");
1593
+ }
1594
+
1595
+ // ../tracking-core/src/phone-field.ts
1596
+ function phoneField(name, raw, country) {
1597
+ return { name, type: "phone", value: toE164(raw, country) };
1598
+ }
1389
1599
 
1390
1600
  // src/hooks.ts
1391
1601
  function useGclid() {
@@ -1627,10 +1837,7 @@ var ANIMATION_KEYFRAMES = `
1627
1837
  import { useEffect as useEffect3, useMemo } from "react";
1628
1838
  function GoogleAdsTracking(props) {
1629
1839
  const { gtagId, gtagIds } = props;
1630
- const gtagIdsKey = useMemo(
1631
- () => gtagIds ? JSON.stringify(gtagIds) : "",
1632
- [gtagIds]
1633
- );
1840
+ const gtagIdsKey = useMemo(() => gtagIds ? JSON.stringify(gtagIds) : "", [gtagIds]);
1634
1841
  useEffect3(() => {
1635
1842
  if (gtagIds && Object.keys(gtagIds).length > 0) {
1636
1843
  bootstrapMultipleGtags(gtagIds);
@@ -1642,18 +1849,106 @@ function GoogleAdsTracking(props) {
1642
1849
  }
1643
1850
 
1644
1851
  // src/factory.tsx
1852
+ import { createContext as createContext2, useContext as useContext2, useEffect as useEffect4, useMemo as useMemo3 } from "react";
1853
+
1854
+ // package.json
1855
+ var version = "0.12.1";
1856
+
1857
+ // ../tracking-core/src/phone-react.tsx
1645
1858
  import {
1646
1859
  createContext,
1860
+ forwardRef,
1861
+ useCallback as useCallback2,
1647
1862
  useContext,
1648
- useEffect as useEffect4,
1649
- useMemo as useMemo2
1863
+ useMemo as useMemo2,
1864
+ useState as useState3
1650
1865
  } from "react";
1651
-
1652
- // package.json
1653
- var version = "0.11.0";
1866
+ import { jsx as jsx2 } from "react/jsx-runtime";
1867
+ var _phoneConfigContext;
1868
+ function phoneConfigContext() {
1869
+ return _phoneConfigContext ?? (_phoneConfigContext = createContext(null));
1870
+ }
1871
+ function PhoneConfigProvider({
1872
+ value,
1873
+ children
1874
+ }) {
1875
+ const Ctx = phoneConfigContext();
1876
+ return /* @__PURE__ */ jsx2(Ctx.Provider, { value, children });
1877
+ }
1878
+ function usePhoneConfig() {
1879
+ const ctx = useContext(phoneConfigContext());
1880
+ return {
1881
+ defaultCountry: ctx?.defaultCountry ?? DEFAULT_PHONE_COUNTRY,
1882
+ display: ctx?.display ?? "national"
1883
+ };
1884
+ }
1885
+ function usePhoneField(opts = {}) {
1886
+ const cfg = usePhoneConfig();
1887
+ const country = opts.country ?? cfg.defaultCountry;
1888
+ const display = opts.display ?? cfg.display;
1889
+ const { onValueChange } = opts;
1890
+ const [value, setValue] = useState3(() => formatPhoneAsTyped(opts.defaultValue ?? "", country));
1891
+ const [touched, setTouched] = useState3(false);
1892
+ const parsed = useMemo2(() => parsePhone(value, country), [value, country]);
1893
+ const onChange = useCallback2(
1894
+ (event) => {
1895
+ const next = formatPhoneAsTyped(event.target.value, country);
1896
+ setValue(next);
1897
+ onValueChange?.(parsePhone(next, country).e164);
1898
+ },
1899
+ [country, onValueChange]
1900
+ );
1901
+ const onBlur = useCallback2(
1902
+ (_event) => {
1903
+ setTouched(true);
1904
+ setValue((current) => {
1905
+ const p = parsePhone(current, country);
1906
+ return p.isValid ? formatPhone(current, display, country) : current;
1907
+ });
1908
+ },
1909
+ [country, display]
1910
+ );
1911
+ const error = touched && value.length > 0 && !parsed.isValid ? "Enter a valid phone number" : null;
1912
+ return {
1913
+ value,
1914
+ e164: parsed.e164,
1915
+ isValid: parsed.isValid,
1916
+ error,
1917
+ parsed,
1918
+ inputProps: { value, onChange, onBlur, type: "tel", inputMode: "tel", autoComplete: "tel" }
1919
+ };
1920
+ }
1921
+ var PhoneField = forwardRef(function PhoneField2({ country, value, defaultValue, onChange, onE164Change, ...rest }, ref) {
1922
+ const cfg = usePhoneConfig();
1923
+ const resolvedCountry = country ?? cfg.defaultCountry;
1924
+ const isControlled = value !== void 0;
1925
+ const [internal, setInternal] = useState3(
1926
+ () => formatPhoneAsTyped(defaultValue ?? "", resolvedCountry)
1927
+ );
1928
+ const handleChange = (event) => {
1929
+ const formatted = formatPhoneAsTyped(event.target.value, resolvedCountry);
1930
+ event.target.value = formatted;
1931
+ onE164Change?.(parsePhone(formatted, resolvedCountry).e164);
1932
+ if (!isControlled) setInternal(formatted);
1933
+ onChange?.(event);
1934
+ };
1935
+ const shown = isControlled ? formatPhoneAsTyped(value, resolvedCountry) : internal;
1936
+ return /* @__PURE__ */ jsx2(
1937
+ "input",
1938
+ {
1939
+ ...rest,
1940
+ ref,
1941
+ type: "tel",
1942
+ inputMode: "tel",
1943
+ autoComplete: "tel",
1944
+ value: shown,
1945
+ onChange: handleChange
1946
+ }
1947
+ );
1948
+ });
1654
1949
 
1655
1950
  // src/factory.tsx
1656
- import { jsx as jsx2 } from "react/jsx-runtime";
1951
+ import { jsx as jsx3 } from "react/jsx-runtime";
1657
1952
  var NOOP_CLIENT = {
1658
1953
  trackEvent: () => {
1659
1954
  },
@@ -1663,7 +1958,7 @@ var NOOP_CLIENT = {
1663
1958
  getVisitorId: () => ""
1664
1959
  };
1665
1960
  function createTracking(options) {
1666
- const { apiKey, endpoint, triggers, environment, debug } = options;
1961
+ const { apiKey, endpoint, triggers, environment, debug, phone } = options;
1667
1962
  if (!apiKey || !endpoint) {
1668
1963
  if (apiKey || endpoint) {
1669
1964
  console.warn(
@@ -1672,14 +1967,16 @@ function createTracking(options) {
1672
1967
  }
1673
1968
  const noopTyped = NOOP_CLIENT;
1674
1969
  return {
1675
- TrackingProvider: ({ children }) => children,
1970
+ // Still publish phone config so usePhoneField/<PhoneField> work even when
1971
+ // tracking is disabled (missing apiKey/endpoint).
1972
+ TrackingProvider: ({ children }) => /* @__PURE__ */ jsx3(PhoneConfigProvider, { value: phone ?? null, children }),
1676
1973
  useTracking: () => noopTyped
1677
1974
  };
1678
1975
  }
1679
- const TrackingContext = createContext(null);
1976
+ const TrackingContext = createContext2(null);
1680
1977
  function TrackingProvider({ gtagId, gtagIds, children }) {
1681
1978
  const resolvedGtagIds = gtagIds ?? (gtagId ? { default: gtagId } : void 0);
1682
- const client = useMemo2(
1979
+ const client = useMemo3(
1683
1980
  () => createTypedClient(
1684
1981
  getOrCreateTrackingClient({
1685
1982
  apiKey,
@@ -1697,7 +1994,7 @@ function createTracking(options) {
1697
1994
  ),
1698
1995
  []
1699
1996
  );
1700
- const gtagIdsKey = useMemo2(() => gtagIds ? JSON.stringify(gtagIds) : "", [gtagIds]);
1997
+ const gtagIdsKey = useMemo3(() => gtagIds ? JSON.stringify(gtagIds) : "", [gtagIds]);
1701
1998
  useEffect4(() => {
1702
1999
  if (gtagIds && Object.keys(gtagIds).length > 0) {
1703
2000
  bootstrapMultipleGtags(gtagIds);
@@ -1745,10 +2042,10 @@ function createTracking(options) {
1745
2042
  }
1746
2043
  };
1747
2044
  }, []);
1748
- return /* @__PURE__ */ jsx2(TrackingContext.Provider, { value: client, children });
2045
+ return /* @__PURE__ */ jsx3(TrackingContext.Provider, { value: client, children: /* @__PURE__ */ jsx3(PhoneConfigProvider, { value: phone ?? null, children }) });
1749
2046
  }
1750
2047
  function useTracking() {
1751
- const client = useContext(TrackingContext);
2048
+ const client = useContext2(TrackingContext);
1752
2049
  if (client === null) {
1753
2050
  throw new Error(
1754
2051
  "useTracking must be called inside a <TrackingProvider> returned by createTracking()"
@@ -1761,8 +2058,13 @@ function createTracking(options) {
1761
2058
  export {
1762
2059
  AranovaApiError,
1763
2060
  ConsentBanner,
2061
+ DEFAULT_PHONE_COUNTRY,
1764
2062
  GoogleAdsTracking,
2063
+ NAMED_RANGES,
2064
+ PhoneField,
2065
+ SUPPORTED_CURRENCIES,
1765
2066
  TRACKING_PARAM_KEYS,
2067
+ TRACKING_RANGES,
1766
2068
  captureTrackingParamsFromLocation,
1767
2069
  createSalesClient,
1768
2070
  createTracking,
@@ -1770,15 +2072,28 @@ export {
1770
2072
  createTrackingEventCreatePayload,
1771
2073
  createTrackingSessionUpsertPayload,
1772
2074
  fetchServices,
2075
+ formatDateInTz,
1773
2076
  formatMoney,
2077
+ formatPhone,
2078
+ formatPhoneAsTyped,
1774
2079
  fromMinor,
1775
2080
  getConsentState,
2081
+ parsePhone,
2082
+ phoneField,
1776
2083
  resetConsent,
2084
+ saleCreateSchema,
2085
+ saleItemSchema,
2086
+ saleServiceSchema,
2087
+ saleUpdateSchema,
2088
+ salesRequest,
1777
2089
  setConsentState,
2090
+ toE164,
1778
2091
  toMinor,
1779
2092
  useConsent,
1780
2093
  useConsentState,
1781
2094
  useGclid,
2095
+ usePhoneConfig,
2096
+ usePhoneField,
1782
2097
  useTrackingParams
1783
2098
  };
1784
2099
  //# sourceMappingURL=index.mjs.map