@aranova/tracking-react 0.10.0 → 0.12.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 +35 -24
- package/dist/index.d.mts +469 -316
- package/dist/index.d.ts +469 -316
- package/dist/index.js +237 -17
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +233 -17
- package/dist/index.mjs.map +1 -1
- package/dist/phone-utils-CVX7JmqB.d.mts +343 -0
- package/dist/phone-utils-CVX7JmqB.d.ts +343 -0
- package/dist/phone.d.mts +3 -0
- package/dist/phone.d.ts +3 -0
- package/dist/phone.js +318 -0
- package/dist/phone.js.map +1 -0
- package/dist/phone.mjs +286 -0
- package/dist/phone.mjs.map +1 -0
- package/package.json +7 -1
package/dist/phone.js
ADDED
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/phone-utils.ts
|
|
21
|
+
var phone_utils_exports = {};
|
|
22
|
+
__export(phone_utils_exports, {
|
|
23
|
+
DEFAULT_PHONE_COUNTRY: () => DEFAULT_PHONE_COUNTRY,
|
|
24
|
+
formatPhone: () => formatPhone,
|
|
25
|
+
formatPhoneAsTyped: () => formatPhoneAsTyped,
|
|
26
|
+
parsePhone: () => parsePhone,
|
|
27
|
+
phoneField: () => phoneField,
|
|
28
|
+
toE164: () => toE164
|
|
29
|
+
});
|
|
30
|
+
module.exports = __toCommonJS(phone_utils_exports);
|
|
31
|
+
|
|
32
|
+
// ../tracking-core/src/session.ts
|
|
33
|
+
var SESSION_IDLE_MS = 30 * 60 * 1e3;
|
|
34
|
+
|
|
35
|
+
// ../tracking-core/src/events/page-view.ts
|
|
36
|
+
var import_zod = require("zod");
|
|
37
|
+
var pageViewMetadataSchema = import_zod.z.object({
|
|
38
|
+
page: import_zod.z.object({
|
|
39
|
+
title: import_zod.z.string().nullable(),
|
|
40
|
+
path: import_zod.z.string(),
|
|
41
|
+
search: import_zod.z.string(),
|
|
42
|
+
hash: import_zod.z.string()
|
|
43
|
+
}).strict(),
|
|
44
|
+
referrer: import_zod.z.string().nullable(),
|
|
45
|
+
// `.nullable().optional()` — absent (undefined) OR explicit null OR a
|
|
46
|
+
// real viewport object. Mirrors Pydantic's `_Viewport | None = None`
|
|
47
|
+
// on the backend side so the drift test stays clean.
|
|
48
|
+
viewport: import_zod.z.object({
|
|
49
|
+
w: import_zod.z.number(),
|
|
50
|
+
h: import_zod.z.number()
|
|
51
|
+
}).strict().nullable().optional()
|
|
52
|
+
}).strict();
|
|
53
|
+
var pageViewConfigSchema = import_zod.z.object({}).strict();
|
|
54
|
+
|
|
55
|
+
// ../tracking-core/src/events/cta-click.ts
|
|
56
|
+
var import_zod2 = require("zod");
|
|
57
|
+
var ctaClickMetadataSchema = import_zod2.z.object({
|
|
58
|
+
cta_name: import_zod2.z.string(),
|
|
59
|
+
page: import_zod2.z.object({
|
|
60
|
+
path: import_zod2.z.string()
|
|
61
|
+
}).strict(),
|
|
62
|
+
section: import_zod2.z.string().nullable().optional(),
|
|
63
|
+
destination_url: import_zod2.z.string().nullable().optional()
|
|
64
|
+
}).strict();
|
|
65
|
+
var ctaClickConfigSchema = import_zod2.z.object({}).strict();
|
|
66
|
+
|
|
67
|
+
// ../tracking-core/src/events/sdk-heartbeat.ts
|
|
68
|
+
var import_zod3 = require("zod");
|
|
69
|
+
var sdkHeartbeatTriggersSchema = import_zod3.z.object({
|
|
70
|
+
automatic: import_zod3.z.array(import_zod3.z.string()),
|
|
71
|
+
manual: import_zod3.z.array(import_zod3.z.string())
|
|
72
|
+
}).strict();
|
|
73
|
+
var sdkHeartbeatMetadataSchema = import_zod3.z.object({
|
|
74
|
+
sdk_version: import_zod3.z.string(),
|
|
75
|
+
package_name: import_zod3.z.string().nullable(),
|
|
76
|
+
surface: import_zod3.z.enum(["next", "react", "script"]),
|
|
77
|
+
triggers: sdkHeartbeatTriggersSchema,
|
|
78
|
+
trigger_config: import_zod3.z.record(import_zod3.z.string(), import_zod3.z.record(import_zod3.z.string(), import_zod3.z.unknown())).nullable().optional(),
|
|
79
|
+
configured_gtag_ids: import_zod3.z.record(import_zod3.z.string(), import_zod3.z.string()).nullable().optional()
|
|
80
|
+
}).strict();
|
|
81
|
+
var sdkHeartbeatConfigSchema = import_zod3.z.object({}).strict();
|
|
82
|
+
|
|
83
|
+
// ../tracking-core/src/events/form-start.ts
|
|
84
|
+
var import_zod4 = require("zod");
|
|
85
|
+
var formStartMetadataSchema = import_zod4.z.object({
|
|
86
|
+
form: import_zod4.z.object({
|
|
87
|
+
id: import_zod4.z.string(),
|
|
88
|
+
action: import_zod4.z.string().nullable()
|
|
89
|
+
}).strict(),
|
|
90
|
+
page: import_zod4.z.object({
|
|
91
|
+
path: import_zod4.z.string()
|
|
92
|
+
}).strict()
|
|
93
|
+
}).strict();
|
|
94
|
+
var formStartConfigSchema = import_zod4.z.object({
|
|
95
|
+
selector: import_zod4.z.string().optional()
|
|
96
|
+
}).strict();
|
|
97
|
+
|
|
98
|
+
// ../tracking-core/src/events/form-submit.ts
|
|
99
|
+
var import_zod5 = require("zod");
|
|
100
|
+
var jsonValueSchema = import_zod5.z.lazy(
|
|
101
|
+
() => import_zod5.z.union([
|
|
102
|
+
import_zod5.z.string(),
|
|
103
|
+
import_zod5.z.number().finite(),
|
|
104
|
+
import_zod5.z.boolean(),
|
|
105
|
+
import_zod5.z.null(),
|
|
106
|
+
import_zod5.z.array(jsonValueSchema),
|
|
107
|
+
import_zod5.z.record(jsonValueSchema)
|
|
108
|
+
])
|
|
109
|
+
);
|
|
110
|
+
var formSubmitMetadataSchema = import_zod5.z.object({
|
|
111
|
+
form: import_zod5.z.object({
|
|
112
|
+
id: import_zod5.z.string(),
|
|
113
|
+
action: import_zod5.z.string().nullable(),
|
|
114
|
+
fields: import_zod5.z.array(
|
|
115
|
+
import_zod5.z.object({
|
|
116
|
+
name: import_zod5.z.string(),
|
|
117
|
+
type: import_zod5.z.string(),
|
|
118
|
+
label: import_zod5.z.string().nullable(),
|
|
119
|
+
value: jsonValueSchema
|
|
120
|
+
}).strict()
|
|
121
|
+
).optional()
|
|
122
|
+
}).strict(),
|
|
123
|
+
page: import_zod5.z.object({
|
|
124
|
+
path: import_zod5.z.string()
|
|
125
|
+
}).strict()
|
|
126
|
+
}).strict();
|
|
127
|
+
var formSubmitConfigSchema = import_zod5.z.object({}).strict();
|
|
128
|
+
|
|
129
|
+
// ../tracking-core/src/events/multi-page-session.ts
|
|
130
|
+
var import_zod6 = require("zod");
|
|
131
|
+
var multiPageSessionMetadataSchema = import_zod6.z.object({
|
|
132
|
+
page_count: import_zod6.z.number().int().min(2),
|
|
133
|
+
page: import_zod6.z.object({
|
|
134
|
+
path: import_zod6.z.string()
|
|
135
|
+
}).strict()
|
|
136
|
+
}).strict();
|
|
137
|
+
var multiPageSessionConfigSchema = import_zod6.z.object({
|
|
138
|
+
pageThreshold: import_zod6.z.number().int().min(2)
|
|
139
|
+
}).strict();
|
|
140
|
+
|
|
141
|
+
// ../tracking-core/src/events/phone-click.ts
|
|
142
|
+
var import_zod7 = require("zod");
|
|
143
|
+
var phoneClickMetadataSchema = import_zod7.z.object({
|
|
144
|
+
phone_number: import_zod7.z.string(),
|
|
145
|
+
page: import_zod7.z.object({
|
|
146
|
+
path: import_zod7.z.string()
|
|
147
|
+
}).strict(),
|
|
148
|
+
section: import_zod7.z.string().nullable().optional()
|
|
149
|
+
}).strict();
|
|
150
|
+
var phoneClickConfigSchema = import_zod7.z.object({}).strict();
|
|
151
|
+
|
|
152
|
+
// ../tracking-core/src/events/scroll-depth.ts
|
|
153
|
+
var import_zod8 = require("zod");
|
|
154
|
+
var scrollDepthMetadataSchema = import_zod8.z.object({
|
|
155
|
+
depth_percent: import_zod8.z.number().int().min(1).max(100),
|
|
156
|
+
page: import_zod8.z.object({
|
|
157
|
+
path: import_zod8.z.string()
|
|
158
|
+
}).strict()
|
|
159
|
+
}).strict();
|
|
160
|
+
var scrollDepthConfigSchema = import_zod8.z.object({
|
|
161
|
+
thresholds: import_zod8.z.array(import_zod8.z.number().int().min(1).max(100)).min(1)
|
|
162
|
+
}).strict();
|
|
163
|
+
|
|
164
|
+
// ../tracking-core/src/events/specific-page-visit.ts
|
|
165
|
+
var import_zod9 = require("zod");
|
|
166
|
+
var SPECIFIC_PAGE_NAMES = [
|
|
167
|
+
"contact_page",
|
|
168
|
+
"about_page",
|
|
169
|
+
"services_page",
|
|
170
|
+
"booking_page",
|
|
171
|
+
"location_page",
|
|
172
|
+
"pricing_page",
|
|
173
|
+
"faq_page",
|
|
174
|
+
"testimonials_page"
|
|
175
|
+
];
|
|
176
|
+
var specificPageNameSchema = import_zod9.z.enum(SPECIFIC_PAGE_NAMES);
|
|
177
|
+
var specificPageVisitMetadataSchema = import_zod9.z.object({
|
|
178
|
+
page_name: specificPageNameSchema,
|
|
179
|
+
page: import_zod9.z.object({
|
|
180
|
+
path: import_zod9.z.string()
|
|
181
|
+
}).strict()
|
|
182
|
+
}).strict();
|
|
183
|
+
var specificPageVisitConfigSchema = import_zod9.z.object({
|
|
184
|
+
pages: import_zod9.z.array(
|
|
185
|
+
import_zod9.z.object({
|
|
186
|
+
name: specificPageNameSchema,
|
|
187
|
+
pathPattern: import_zod9.z.custom((value) => value instanceof RegExp, {
|
|
188
|
+
message: "pathPattern must be a RegExp"
|
|
189
|
+
})
|
|
190
|
+
}).strict()
|
|
191
|
+
).min(1)
|
|
192
|
+
}).strict();
|
|
193
|
+
|
|
194
|
+
// ../tracking-core/src/events/time-on-site.ts
|
|
195
|
+
var import_zod10 = require("zod");
|
|
196
|
+
var timeOnSiteMetadataSchema = import_zod10.z.object({
|
|
197
|
+
duration_ms: import_zod10.z.number().int().nonnegative(),
|
|
198
|
+
page: import_zod10.z.object({
|
|
199
|
+
path: import_zod10.z.string()
|
|
200
|
+
}).strict()
|
|
201
|
+
}).strict();
|
|
202
|
+
var timeOnSiteConfigSchema = import_zod10.z.object({
|
|
203
|
+
thresholdSeconds: import_zod10.z.number().int().positive()
|
|
204
|
+
}).strict();
|
|
205
|
+
|
|
206
|
+
// ../tracking-core/src/events/registry.ts
|
|
207
|
+
var EVENT_REGISTRY = {
|
|
208
|
+
// --- automatic triggers ---
|
|
209
|
+
page_view: {
|
|
210
|
+
kind: "automatic",
|
|
211
|
+
metadataSchema: pageViewMetadataSchema,
|
|
212
|
+
configSchema: pageViewConfigSchema
|
|
213
|
+
},
|
|
214
|
+
time_on_site: {
|
|
215
|
+
kind: "automatic",
|
|
216
|
+
metadataSchema: timeOnSiteMetadataSchema,
|
|
217
|
+
configSchema: timeOnSiteConfigSchema
|
|
218
|
+
},
|
|
219
|
+
specific_page_visit: {
|
|
220
|
+
kind: "automatic",
|
|
221
|
+
metadataSchema: specificPageVisitMetadataSchema,
|
|
222
|
+
configSchema: specificPageVisitConfigSchema
|
|
223
|
+
},
|
|
224
|
+
scroll_depth: {
|
|
225
|
+
kind: "automatic",
|
|
226
|
+
metadataSchema: scrollDepthMetadataSchema,
|
|
227
|
+
configSchema: scrollDepthConfigSchema
|
|
228
|
+
},
|
|
229
|
+
multi_page_session: {
|
|
230
|
+
kind: "automatic",
|
|
231
|
+
metadataSchema: multiPageSessionMetadataSchema,
|
|
232
|
+
configSchema: multiPageSessionConfigSchema
|
|
233
|
+
},
|
|
234
|
+
form_start: {
|
|
235
|
+
kind: "automatic",
|
|
236
|
+
metadataSchema: formStartMetadataSchema,
|
|
237
|
+
configSchema: formStartConfigSchema
|
|
238
|
+
},
|
|
239
|
+
// --- SDK-internal automatic (not consumer-configurable) ---
|
|
240
|
+
sdk_heartbeat: {
|
|
241
|
+
kind: "automatic",
|
|
242
|
+
metadataSchema: sdkHeartbeatMetadataSchema,
|
|
243
|
+
configSchema: sdkHeartbeatConfigSchema
|
|
244
|
+
},
|
|
245
|
+
// --- manual triggers ---
|
|
246
|
+
form_submit: {
|
|
247
|
+
kind: "manual",
|
|
248
|
+
metadataSchema: formSubmitMetadataSchema,
|
|
249
|
+
configSchema: formSubmitConfigSchema
|
|
250
|
+
},
|
|
251
|
+
phone_click: {
|
|
252
|
+
kind: "manual",
|
|
253
|
+
metadataSchema: phoneClickMetadataSchema,
|
|
254
|
+
configSchema: phoneClickConfigSchema
|
|
255
|
+
},
|
|
256
|
+
cta_click: {
|
|
257
|
+
kind: "manual",
|
|
258
|
+
metadataSchema: ctaClickMetadataSchema,
|
|
259
|
+
configSchema: ctaClickConfigSchema
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
var ALL_AUTOMATIC_EVENT_NAMES = Object.entries(EVENT_REGISTRY).filter(([, def]) => def.kind === "automatic").map(([name]) => name);
|
|
263
|
+
var ALL_MANUAL_EVENT_NAMES = Object.entries(EVENT_REGISTRY).filter(([, def]) => def.kind === "manual").map(([name]) => name);
|
|
264
|
+
|
|
265
|
+
// ../tracking-core/src/phone.ts
|
|
266
|
+
var import_libphonenumber_js = require("libphonenumber-js");
|
|
267
|
+
var DEFAULT_PHONE_COUNTRY = "CA";
|
|
268
|
+
function parsePhone(raw, country) {
|
|
269
|
+
const region = country ?? DEFAULT_PHONE_COUNTRY;
|
|
270
|
+
const parsed = (0, import_libphonenumber_js.parsePhoneNumberFromString)(raw ?? "", region);
|
|
271
|
+
if (!parsed) {
|
|
272
|
+
return { e164: null, national: "", international: "", country: region, isValid: false };
|
|
273
|
+
}
|
|
274
|
+
const isValid = parsed.isValid();
|
|
275
|
+
return {
|
|
276
|
+
// E.164 is only surfaced for a *valid* number — a possible-but-invalid input
|
|
277
|
+
// (e.g. too few digits) still parses but must not be transmitted.
|
|
278
|
+
e164: isValid ? parsed.number : null,
|
|
279
|
+
national: parsed.formatNational(),
|
|
280
|
+
international: parsed.formatInternational(),
|
|
281
|
+
country: parsed.country ?? region,
|
|
282
|
+
isValid
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
function toE164(raw, country) {
|
|
286
|
+
return parsePhone(raw, country).e164;
|
|
287
|
+
}
|
|
288
|
+
function formatPhone(value, format = "national", country) {
|
|
289
|
+
const parsed = parsePhone(value, country);
|
|
290
|
+
if (typeof format === "function") return format(parsed);
|
|
291
|
+
switch (format) {
|
|
292
|
+
case "international":
|
|
293
|
+
return parsed.international || value;
|
|
294
|
+
case "e164":
|
|
295
|
+
return parsed.e164 ?? value;
|
|
296
|
+
case "national":
|
|
297
|
+
default:
|
|
298
|
+
return parsed.national || value;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
function formatPhoneAsTyped(raw, country) {
|
|
302
|
+
return new import_libphonenumber_js.AsYouType(country ?? DEFAULT_PHONE_COUNTRY).input(raw ?? "");
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// ../tracking-core/src/phone-field.ts
|
|
306
|
+
function phoneField(name, raw, country) {
|
|
307
|
+
return { name, type: "phone", value: toE164(raw, country) };
|
|
308
|
+
}
|
|
309
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
310
|
+
0 && (module.exports = {
|
|
311
|
+
DEFAULT_PHONE_COUNTRY,
|
|
312
|
+
formatPhone,
|
|
313
|
+
formatPhoneAsTyped,
|
|
314
|
+
parsePhone,
|
|
315
|
+
phoneField,
|
|
316
|
+
toE164
|
|
317
|
+
});
|
|
318
|
+
//# sourceMappingURL=phone.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/phone-utils.ts","../../tracking-core/src/session.ts","../../tracking-core/src/events/page-view.ts","../../tracking-core/src/events/cta-click.ts","../../tracking-core/src/events/sdk-heartbeat.ts","../../tracking-core/src/events/form-start.ts","../../tracking-core/src/events/form-submit.ts","../../tracking-core/src/events/multi-page-session.ts","../../tracking-core/src/events/phone-click.ts","../../tracking-core/src/events/scroll-depth.ts","../../tracking-core/src/events/specific-page-visit.ts","../../tracking-core/src/events/time-on-site.ts","../../tracking-core/src/events/registry.ts","../../tracking-core/src/phone.ts","../../tracking-core/src/phone-field.ts"],"sourcesContent":["// `@aranova/tracking-react/phone` — the isomorphic phone utils only (NO React,\n// NO 'use client'), so server/Node code can normalize/format too. The React\n// hook + component live on the package root.\nexport {\n DEFAULT_PHONE_COUNTRY,\n formatPhone,\n formatPhoneAsTyped,\n parsePhone,\n phoneField,\n toE164,\n} from '../../tracking-core/src/index';\nexport type {\n CountryCode,\n ParsedPhone,\n PhoneConfig,\n PhoneDisplayFormat,\n TrackedField,\n} from '../../tracking-core/src/index';\n","// Visitor + session identity for the tracking SDK.\n//\n// Visitor: persistent localStorage UUID, never expires until the user clears\n// browser storage. Used for cross-session correlation.\n//\n// Session: rolling 30-minute idle window. Regenerated when more than\n// SESSION_IDLE_MS has passed since the last event. Matches the behavior of\n// GA4, PostHog, Mixpanel, etc., so analytics is comparable.\n\n/**\n * localStorage key for the persistent visitor id.\n */\nexport const VISITOR_STORAGE_KEY = 'aranova_tracking_visitor';\n\n/**\n * localStorage key for the rolling session id state.\n */\nexport const SESSION_STORAGE_KEY = 'aranova_tracking_session';\n\n/**\n * Idle window before a new session id is created.\n */\nexport const SESSION_IDLE_MS = 30 * 60 * 1000;\n\n/**\n * Serialized session state stored in localStorage.\n */\nexport interface StoredSession {\n /** Client-generated session UUID. */\n id: string;\n /** Unix timestamp in milliseconds for the most recent event/session touch. */\n last_event_at: number;\n}\n\nfunction safeUuid(): string {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function')\n return crypto.randomUUID();\n // Fallback for ancient browsers — not cryptographically perfect but unique enough.\n return `${Date.now().toString(16)}-${Math.random().toString(16).slice(2)}-${Math.random().toString(16).slice(2)}`;\n}\n\nfunction readLocalStorage(key: string): string | null {\n try {\n return window.localStorage.getItem(key);\n } catch {\n return null;\n }\n}\n\nfunction writeLocalStorage(key: string, value: string): void {\n try {\n window.localStorage.setItem(key, value);\n } catch {\n // Storage may be denied (private mode, blocked cookies, quota). Caller is\n // responsible for degrading gracefully.\n }\n}\n\n/**\n * Return the persistent visitor id for this browser profile.\n *\n * Creates and stores a new id when one does not already exist. During SSR,\n * returns an ephemeral id because browser storage is unavailable.\n */\nexport function getVisitorId(): string {\n if (typeof window === 'undefined')\n return safeUuid();\n\n const existing = readLocalStorage(VISITOR_STORAGE_KEY);\n if (existing && existing.length > 0)\n return existing;\n\n const fresh = safeUuid();\n writeLocalStorage(VISITOR_STORAGE_KEY, fresh);\n return fresh;\n}\n\n/**\n * Result from `getOrRotateSessionId()`.\n */\nexport interface SessionIdResult {\n /** Current session id. */\n id: string;\n /** Whether this call created a new session. */\n isNew: boolean;\n}\n\n/**\n * Return the current session id, rotating it after the idle window expires.\n *\n * Also refreshes `last_event_at` for active sessions.\n */\nexport function getOrRotateSessionId(now: number = Date.now()): SessionIdResult {\n if (typeof window === 'undefined')\n return { id: safeUuid(), isNew: true };\n\n const raw = readLocalStorage(SESSION_STORAGE_KEY);\n if (raw) {\n try {\n const parsed = JSON.parse(raw) as Partial<StoredSession>;\n if (typeof parsed.id === 'string' && typeof parsed.last_event_at === 'number') {\n if (now - parsed.last_event_at <= SESSION_IDLE_MS) {\n const refreshed: StoredSession = { id: parsed.id, last_event_at: now };\n writeLocalStorage(SESSION_STORAGE_KEY, JSON.stringify(refreshed));\n return { id: parsed.id, isNew: false };\n }\n }\n } catch {\n // Fall through to a fresh session.\n }\n }\n\n const fresh: StoredSession = { id: safeUuid(), last_event_at: now };\n writeLocalStorage(SESSION_STORAGE_KEY, JSON.stringify(fresh));\n return { id: fresh.id, isNew: true };\n}\n\n/**\n * Clear visitor and session identity from localStorage.\n *\n * Intended for tests, debugging, and explicit user reset flows.\n */\nexport function resetTrackingIdentity(): void {\n if (typeof window === 'undefined')\n return;\n try {\n window.localStorage.removeItem(VISITOR_STORAGE_KEY);\n window.localStorage.removeItem(SESSION_STORAGE_KEY);\n } catch {\n // ignored\n }\n}\n","import { z } from 'zod';\n\n/**\n * Metadata for the automatic `page_view` event.\n *\n * The SDK emits this on initial load, SPA route changes, and bfcache restores.\n * Consumers do not call `trackEvent('page_view', ...)`; registering\n * `automatic: { page_view: {} }` enables the SDK-owned trigger.\n */\nexport const pageViewMetadataSchema = z\n .object({\n page: z\n .object({\n title: z.string().nullable(),\n path: z.string(),\n search: z.string(),\n hash: z.string(),\n })\n .strict(),\n referrer: z.string().nullable(),\n // `.nullable().optional()` — absent (undefined) OR explicit null OR a\n // real viewport object. Mirrors Pydantic's `_Viewport | None = None`\n // on the backend side so the drift test stays clean.\n viewport: z\n .object({\n w: z.number(),\n h: z.number(),\n })\n .strict()\n .nullable()\n .optional(),\n })\n .strict();\n\nexport type PageViewMetadata = z.infer<typeof pageViewMetadataSchema>;\n\n/**\n * Registration config for automatic `page_view`.\n *\n * `page_view` is required in every trigger registry and currently has no\n * options. Use `{ page_view: {} }`.\n */\nexport const pageViewConfigSchema = z.object({}).strict();\nexport type PageViewConfig = z.infer<typeof pageViewConfigSchema>;\n","import { z } from 'zod';\n\n/**\n * Metadata for a manually fired `cta_click` event.\n *\n * Use this for non-phone calls to action such as directions, appointment\n * buttons, downloads, or external booking links.\n */\nexport const ctaClickMetadataSchema = z\n .object({\n cta_name: z.string(),\n page: z\n .object({\n path: z.string(),\n })\n .strict(),\n section: z.string().nullable().optional(),\n destination_url: z.string().nullable().optional(),\n })\n .strict();\n\nexport type CtaClickMetadata = z.infer<typeof ctaClickMetadataSchema>;\n\n/**\n * Registration config for `cta_click`.\n *\n * This event is manual-only and currently has no registration options.\n */\nexport const ctaClickConfigSchema = z.object({}).strict();\nexport type CtaClickConfig = z.infer<typeof ctaClickConfigSchema>;\n","import { z } from 'zod';\n\n/**\n * Registered trigger names reported by the SDK heartbeat.\n */\nexport const sdkHeartbeatTriggersSchema = z.object({\n automatic: z.array(z.string()),\n manual: z.array(z.string()),\n}).strict();\n\n/**\n * Metadata for the SDK-internal `sdk_heartbeat` event.\n *\n * The SDK fires this once per new session so the dashboard can show which SDK\n * version, install surface, and trigger registry a client site is running.\n * Consumers do not manually register or fire this event.\n */\nexport const sdkHeartbeatMetadataSchema = z\n .object({\n sdk_version: z.string(),\n package_name: z.string().nullable(),\n surface: z.enum(['next', 'react', 'script']),\n triggers: sdkHeartbeatTriggersSchema,\n trigger_config: z.record(z.string(), z.record(z.string(), z.unknown())).nullable().optional(),\n configured_gtag_ids: z.record(z.string(), z.string()).nullable().optional(),\n })\n .strict();\n\nexport type SdkHeartbeatMetadata = z.infer<typeof sdkHeartbeatMetadataSchema>;\n\n/**\n * Internal registration config for `sdk_heartbeat`.\n *\n * This event has no consumer-facing options.\n */\nexport const sdkHeartbeatConfigSchema = z.object({}).strict();\nexport type SdkHeartbeatConfig = z.infer<typeof sdkHeartbeatConfigSchema>;\n","import { z } from 'zod';\n\n/**\n * Metadata for the automatic `form_start` event.\n *\n * The SDK emits this once per form when the visitor first focuses a field.\n */\nexport const formStartMetadataSchema = z\n .object({\n form: z\n .object({\n id: z.string(),\n action: z.string().nullable(),\n })\n .strict(),\n page: z\n .object({\n path: z.string(),\n })\n .strict(),\n })\n .strict();\n\nexport type FormStartMetadata = z.infer<typeof formStartMetadataSchema>;\n\n/**\n * Registration config for automatic `form_start`.\n *\n * Use `selector` to narrow which forms can trigger the event. When omitted,\n * the SDK observes all `<form>` elements.\n */\nexport const formStartConfigSchema = z\n .object({\n selector: z.string().optional(),\n })\n .strict();\n\nexport type FormStartConfig = z.infer<typeof formStartConfigSchema>;\n","import { z } from 'zod';\n\n// form_submit is manual — the SDK never auto-fires this. Consumer code calls\n// `tracking.trackEvent('form_submit', { form, page })` from their own submit\n// handler. Registering it enables the type-level permission; omitting it\n// turns manual calls into a compile error.\n//\n// The optional `fields` array captures submitted form field metadata and JSON\n// values. Consumers explicitly build the fields array themselves so they\n// control exactly what is sent.\n\n/**\n * JSON-serializable value accepted by `form_submit.fields[].value`.\n *\n * This intentionally excludes `undefined`, functions, symbols, `Date`\n * instances, and non-finite numbers. Values are stored in PostgreSQL JSONB, so\n * consumers should send only data that has a stable JSON representation.\n */\nexport type JsonValue =\n | string\n | number\n | boolean\n | null\n | JsonValue[]\n | { [key: string]: JsonValue };\n\nexport const jsonValueSchema: z.ZodType<JsonValue> = z.lazy(() =>\n z.union([\n z.string(),\n z.number().finite(),\n z.boolean(),\n z.null(),\n z.array(jsonValueSchema),\n z.record(jsonValueSchema),\n ]),\n);\n\n/**\n * Metadata for a manually fired `form_submit` event.\n *\n * Register the event with `manual: { form_submit: {} }`, then call\n * `trackEvent('form_submit', metadata)` from the host site's submit handler.\n *\n * `fields` is optional. If present, each field value must be JSON-serializable\n * and should be explicitly allowlisted by the integration. Do not send names,\n * emails, visitor phone numbers, addresses, payment data, medical details,\n * passwords, file contents, or free-text messages.\n *\n * @example\n * ```ts\n * tracking.trackEvent('form_submit', {\n * form: {\n * id: 'lead-form',\n * action: '/api/lead',\n * fields: [\n * {\n * name: 'service_interest',\n * type: 'select',\n * label: 'Service interest',\n * value: 'teeth_whitening',\n * },\n * ],\n * },\n * page: { path: window.location.pathname },\n * });\n * ```\n */\nexport const formSubmitMetadataSchema = z\n .object({\n form: z\n .object({\n id: z.string(),\n action: z.string().nullable(),\n fields: z\n .array(\n z\n .object({\n name: z.string(),\n type: z.string(),\n label: z.string().nullable(),\n value: jsonValueSchema,\n })\n .strict(),\n )\n .optional(),\n })\n .strict(),\n page: z\n .object({\n path: z.string(),\n })\n .strict(),\n })\n .strict();\n\nexport type FormSubmitMetadata = z.infer<typeof formSubmitMetadataSchema>;\n\n/**\n * Registration config for `form_submit`.\n *\n * This event is manual-only and currently has no registration options. The\n * empty object enables typed `trackEvent('form_submit', ...)` calls.\n */\nexport const formSubmitConfigSchema = z.object({}).strict();\nexport type FormSubmitConfig = z.infer<typeof formSubmitConfigSchema>;\n","import { z } from 'zod';\n\n/**\n * Metadata for the automatic `multi_page_session` event.\n *\n * Fired when the visitor reaches the configured distinct-page threshold in a\n * single tracking session.\n */\nexport const multiPageSessionMetadataSchema = z\n .object({\n page_count: z.number().int().min(2),\n page: z\n .object({\n path: z.string(),\n })\n .strict(),\n })\n .strict();\n\nexport type MultiPageSessionMetadata = z.infer<typeof multiPageSessionMetadataSchema>;\n\n/**\n * Registration config for automatic `multi_page_session`.\n */\nexport const multiPageSessionConfigSchema = z\n .object({\n pageThreshold: z.number().int().min(2),\n })\n .strict();\n\nexport type MultiPageSessionConfig = z.infer<typeof multiPageSessionConfigSchema>;\n","import { z } from 'zod';\n\n/**\n * Metadata for a manually fired `phone_click` event.\n *\n * `phone_number` should be the business phone number from the clicked `tel:`\n * link, not a visitor-entered phone number. `section` can distinguish header,\n * footer, hero, or contact-page links.\n */\nexport const phoneClickMetadataSchema = z\n .object({\n phone_number: z.string(),\n page: z\n .object({\n path: z.string(),\n })\n .strict(),\n section: z.string().nullable().optional(),\n })\n .strict();\n\nexport type PhoneClickMetadata = z.infer<typeof phoneClickMetadataSchema>;\n\n/**\n * Registration config for `phone_click`.\n *\n * This event is manual-only and currently has no registration options.\n */\nexport const phoneClickConfigSchema = z.object({}).strict();\nexport type PhoneClickConfig = z.infer<typeof phoneClickConfigSchema>;\n","import { z } from 'zod';\n\n/**\n * Metadata for the automatic `scroll_depth` event.\n *\n * Fired once per configured threshold per page.\n */\nexport const scrollDepthMetadataSchema = z\n .object({\n depth_percent: z.number().int().min(1).max(100),\n page: z\n .object({\n path: z.string(),\n })\n .strict(),\n })\n .strict();\n\nexport type ScrollDepthMetadata = z.infer<typeof scrollDepthMetadataSchema>;\n\n/**\n * Registration config for automatic `scroll_depth`.\n *\n * `thresholds` are integer percentages from 1 to 100.\n */\nexport const scrollDepthConfigSchema = z\n .object({\n thresholds: z.array(z.number().int().min(1).max(100)).min(1),\n })\n .strict();\n\nexport type ScrollDepthConfig = z.infer<typeof scrollDepthConfigSchema>;\n","import { z } from 'zod';\n\n/**\n * Canonical page intent names supported by `specific_page_visit`.\n */\nexport const SPECIFIC_PAGE_NAMES = [\n 'contact_page',\n 'about_page',\n 'services_page',\n 'booking_page',\n 'location_page',\n 'pricing_page',\n 'faq_page',\n 'testimonials_page',\n] as const;\n\nexport type SpecificPageName = (typeof SPECIFIC_PAGE_NAMES)[number];\n\nexport const specificPageNameSchema = z.enum(SPECIFIC_PAGE_NAMES);\n\n/**\n * Metadata for the automatic `specific_page_visit` event.\n *\n * The SDK emits this when the current pathname matches one of the configured\n * named page patterns.\n */\nexport const specificPageVisitMetadataSchema = z\n .object({\n page_name: specificPageNameSchema,\n page: z\n .object({\n path: z.string(),\n })\n .strict(),\n })\n .strict();\n\nexport type SpecificPageVisitMetadata = z.infer<typeof specificPageVisitMetadataSchema>;\n\n/**\n * Registration config for automatic `specific_page_visit`.\n *\n * Each page entry pairs a semantic `name` with a `RegExp` that matches the\n * pathname. Use this instead of hard-coding path regexes downstream.\n */\nexport const specificPageVisitConfigSchema = z\n .object({\n pages: z\n .array(\n z\n .object({\n name: specificPageNameSchema,\n pathPattern: z.custom<RegExp>((value) => value instanceof RegExp, {\n message: 'pathPattern must be a RegExp',\n }),\n })\n .strict(),\n )\n .min(1),\n })\n .strict();\n\nexport type SpecificPageVisitConfig = z.infer<typeof specificPageVisitConfigSchema>;\n","import { z } from 'zod';\n\n/**\n * Metadata for the automatic `time_on_site` event.\n *\n * The SDK starts a visibility-aware timer and fires once when visible\n * engagement crosses the configured threshold.\n */\nexport const timeOnSiteMetadataSchema = z\n .object({\n duration_ms: z.number().int().nonnegative(),\n page: z\n .object({\n path: z.string(),\n })\n .strict(),\n })\n .strict();\n\nexport type TimeOnSiteMetadata = z.infer<typeof timeOnSiteMetadataSchema>;\n\n/**\n * Registration config for automatic `time_on_site`.\n */\nexport const timeOnSiteConfigSchema = z\n .object({\n thresholdSeconds: z.number().int().positive(),\n })\n .strict();\nexport type TimeOnSiteConfig = z.infer<typeof timeOnSiteConfigSchema>;\n","import type { z } from 'zod';\n\nimport {\n ctaClickConfigSchema,\n ctaClickMetadataSchema,\n type CtaClickConfig,\n type CtaClickMetadata,\n} from './cta-click';\nimport {\n sdkHeartbeatConfigSchema,\n sdkHeartbeatMetadataSchema,\n type SdkHeartbeatConfig,\n type SdkHeartbeatMetadata,\n} from './sdk-heartbeat';\nimport {\n formStartConfigSchema,\n formStartMetadataSchema,\n type FormStartConfig,\n type FormStartMetadata,\n} from './form-start';\nimport {\n formSubmitConfigSchema,\n formSubmitMetadataSchema,\n type FormSubmitConfig,\n type FormSubmitMetadata,\n} from './form-submit';\nimport {\n multiPageSessionConfigSchema,\n multiPageSessionMetadataSchema,\n type MultiPageSessionConfig,\n type MultiPageSessionMetadata,\n} from './multi-page-session';\nimport {\n pageViewConfigSchema,\n pageViewMetadataSchema,\n type PageViewConfig,\n type PageViewMetadata,\n} from './page-view';\nimport {\n phoneClickConfigSchema,\n phoneClickMetadataSchema,\n type PhoneClickConfig,\n type PhoneClickMetadata,\n} from './phone-click';\nimport {\n scrollDepthConfigSchema,\n scrollDepthMetadataSchema,\n type ScrollDepthConfig,\n type ScrollDepthMetadata,\n} from './scroll-depth';\nimport {\n specificPageVisitConfigSchema,\n specificPageVisitMetadataSchema,\n type SpecificPageVisitConfig,\n type SpecificPageVisitMetadata,\n} from './specific-page-visit';\nimport {\n timeOnSiteConfigSchema,\n timeOnSiteMetadataSchema,\n type TimeOnSiteConfig,\n type TimeOnSiteMetadata,\n} from './time-on-site';\n\n// Event kind — automatic events are fired by the SDK itself when their\n// client-side signal fires (page_view on navigation, time_on_site on timer,\n// etc). Manual events are only fireable via explicit consumer code.\nexport type EventKind = 'automatic' | 'manual';\n\n// The registry is the single client-side source of truth for \"what events\n// exist, what shape does their metadata take, and what kind are they\". The\n// backend mirror lives in apps/api/src/schemas/tracking_events.py and is\n// kept in sync via the drift test in apps/api/tests/test_event_schema_drift.py.\nexport const EVENT_REGISTRY = {\n // --- automatic triggers ---\n page_view: {\n kind: 'automatic',\n metadataSchema: pageViewMetadataSchema,\n configSchema: pageViewConfigSchema,\n },\n time_on_site: {\n kind: 'automatic',\n metadataSchema: timeOnSiteMetadataSchema,\n configSchema: timeOnSiteConfigSchema,\n },\n specific_page_visit: {\n kind: 'automatic',\n metadataSchema: specificPageVisitMetadataSchema,\n configSchema: specificPageVisitConfigSchema,\n },\n scroll_depth: {\n kind: 'automatic',\n metadataSchema: scrollDepthMetadataSchema,\n configSchema: scrollDepthConfigSchema,\n },\n multi_page_session: {\n kind: 'automatic',\n metadataSchema: multiPageSessionMetadataSchema,\n configSchema: multiPageSessionConfigSchema,\n },\n form_start: {\n kind: 'automatic',\n metadataSchema: formStartMetadataSchema,\n configSchema: formStartConfigSchema,\n },\n // --- SDK-internal automatic (not consumer-configurable) ---\n sdk_heartbeat: {\n kind: 'automatic',\n metadataSchema: sdkHeartbeatMetadataSchema,\n configSchema: sdkHeartbeatConfigSchema,\n },\n // --- manual triggers ---\n form_submit: {\n kind: 'manual',\n metadataSchema: formSubmitMetadataSchema,\n configSchema: formSubmitConfigSchema,\n },\n phone_click: {\n kind: 'manual',\n metadataSchema: phoneClickMetadataSchema,\n configSchema: phoneClickConfigSchema,\n },\n cta_click: {\n kind: 'manual',\n metadataSchema: ctaClickMetadataSchema,\n configSchema: ctaClickConfigSchema,\n },\n} as const;\n\n/**\n * Name of any event known to the tracking SDK.\n */\nexport type EventName = keyof typeof EVENT_REGISTRY;\n\n/**\n * Event names that are fired by the SDK when their configured signal occurs.\n *\n * Automatic events are not accepted by the typed `trackEvent()` API.\n */\nexport type AutomaticEventName = {\n [K in EventName]: (typeof EVENT_REGISTRY)[K]['kind'] extends 'automatic' ? K : never;\n}[EventName];\n\n/**\n * Event names that consumer code can fire manually after registering them.\n */\nexport type ManualEventName = {\n [K in EventName]: (typeof EVENT_REGISTRY)[K]['kind'] extends 'manual' ? K : never;\n}[EventName];\n\n// Map an event name to its explicit metadata / config TS type. We pull the\n// inferred types directly from the per-event files rather than deriving\n// via `z.infer<typeof EVENT_REGISTRY[K]['metadataSchema']>` so that error\n// messages name the event-specific type (PageViewMetadata, not an\n// anonymous zod inference).\ntype MetadataByName = {\n page_view: PageViewMetadata;\n time_on_site: TimeOnSiteMetadata;\n specific_page_visit: SpecificPageVisitMetadata;\n scroll_depth: ScrollDepthMetadata;\n multi_page_session: MultiPageSessionMetadata;\n form_start: FormStartMetadata;\n sdk_heartbeat: SdkHeartbeatMetadata;\n form_submit: FormSubmitMetadata;\n phone_click: PhoneClickMetadata;\n cta_click: CtaClickMetadata;\n};\n\ntype ConfigByName = {\n page_view: PageViewConfig;\n time_on_site: TimeOnSiteConfig;\n specific_page_visit: SpecificPageVisitConfig;\n scroll_depth: ScrollDepthConfig;\n multi_page_session: MultiPageSessionConfig;\n form_start: FormStartConfig;\n sdk_heartbeat: SdkHeartbeatConfig;\n form_submit: FormSubmitConfig;\n phone_click: PhoneClickConfig;\n cta_click: CtaClickConfig;\n};\n\n/**\n * Metadata payload type for a specific tracking event.\n *\n * @example\n * ```ts\n * type SubmitMetadata = EventMetadata<'form_submit'>;\n * ```\n */\nexport type EventMetadata<K extends EventName> = MetadataByName[K];\n\n/**\n * Trigger registration config type for a specific tracking event.\n */\nexport type EventConfig<K extends EventName> = ConfigByName[K];\n\n// Runtime constant arrays for iteration at consumer / factory time.\n/**\n * Runtime list of automatic event names.\n */\nexport const ALL_AUTOMATIC_EVENT_NAMES: readonly AutomaticEventName[] = (\n Object.entries(EVENT_REGISTRY) as Array<[EventName, (typeof EVENT_REGISTRY)[EventName]]>\n)\n .filter(([, def]) => def.kind === 'automatic')\n .map(([name]) => name) as AutomaticEventName[];\n\n/**\n * Runtime list of manual event names.\n */\nexport const ALL_MANUAL_EVENT_NAMES: readonly ManualEventName[] = (\n Object.entries(EVENT_REGISTRY) as Array<[EventName, (typeof EVENT_REGISTRY)[EventName]]>\n)\n .filter(([, def]) => def.kind === 'manual')\n .map(([name]) => name) as ManualEventName[];\n\n/**\n * Trigger registry passed to `createTracking({ triggers })`.\n *\n * `automatic.page_view` is required because every install should capture page\n * views. Other automatic events are opt-in. Manual events must be registered\n * here before the typed client accepts `trackEvent()` calls for them.\n *\n * @example\n * ```ts\n * createTracking({\n * apiKey,\n * endpoint,\n * triggers: {\n * automatic: {\n * page_view: {},\n * time_on_site: { thresholdSeconds: 60 },\n * },\n * manual: {\n * form_submit: {},\n * phone_click: {},\n * },\n * },\n * });\n * ```\n */\nexport type TriggerRegistryConfig = {\n automatic: {\n page_view: EventConfig<'page_view'>;\n } & Partial<{\n time_on_site: EventConfig<'time_on_site'>;\n specific_page_visit: EventConfig<'specific_page_visit'>;\n scroll_depth: EventConfig<'scroll_depth'>;\n multi_page_session: EventConfig<'multi_page_session'>;\n form_start: EventConfig<'form_start'>;\n }>;\n manual?: Partial<{\n form_submit: EventConfig<'form_submit'>;\n phone_click: EventConfig<'phone_click'>;\n cta_click: EventConfig<'cta_click'>;\n }>;\n};\n\n/**\n * Manual event names registered in a concrete trigger registry.\n *\n * Used by `TypedTrackingClient` so `trackEvent()` only accepts events the\n * consumer explicitly enabled.\n */\nexport type RegisteredManualEvents<TRegistry extends TriggerRegistryConfig> =\n Extract<keyof NonNullable<TRegistry['manual']>, ManualEventName>;\n\n/**\n * Automatic event names registered in a concrete trigger registry.\n */\nexport type RegisteredAutomaticEvents<TRegistry extends TriggerRegistryConfig> =\n Extract<keyof TRegistry['automatic'], AutomaticEventName>;\n\n/**\n * Discriminated union of valid manual tracking calls for a registry.\n */\nexport type TrackableEvent<TRegistry extends TriggerRegistryConfig> = {\n [K in RegisteredManualEvents<TRegistry>]: {\n eventType: K;\n metadata: EventMetadata<K>;\n };\n}[RegisteredManualEvents<TRegistry>];\n\n// Runtime helper: look up a schema pair by name. Cast through `unknown`\n// because the registry is `as const` and TS loses the specific schema type\n// when indexing via a dynamic key.\nexport function getEventDefinition(name: EventName): {\n kind: EventKind;\n metadataSchema: z.ZodTypeAny;\n configSchema: z.ZodTypeAny;\n} {\n return EVENT_REGISTRY[name] as unknown as {\n kind: EventKind;\n metadataSchema: z.ZodTypeAny;\n configSchema: z.ZodTypeAny;\n };\n}\n","// Framework-agnostic phone-number utilities — isomorphic (browser + Node/RSC),\n// zero React. Bundled `libphonenumber-js` (standard metadata) so clients add no\n// dependency. The transmitted value is ALWAYS E.164; display is the only knob.\n\nimport { AsYouType, parsePhoneNumberFromString, type CountryCode } from 'libphonenumber-js';\n\nexport type { CountryCode };\n\n/**\n * How a phone number is shown in the UI. The transmitted value is always E.164\n * and is deliberately NOT part of this — only the display format is configurable.\n * A function form covers the long tail (`(parsed) => string`).\n */\nexport type PhoneDisplayFormat =\n | 'national'\n | 'international'\n | 'e164'\n | ((parsed: ParsedPhone) => string);\n\nexport interface ParsedPhone {\n /** E.164 (`\"+14165550199\"`) or `null` when the input isn't a valid number. This is what gets transmitted. */\n e164: string | null;\n /** National display form (`\"(416) 555-0199\"`); empty string when unparseable. */\n national: string;\n /** International display form (`\"+1 416 555 0199\"`); empty string when unparseable. */\n international: string;\n /** ISO-3166 country resolved by libphonenumber, or `null`. */\n country: CountryCode | null;\n isValid: boolean;\n}\n\n/** Region assumed for numbers typed without a country code. */\nexport const DEFAULT_PHONE_COUNTRY: CountryCode = 'CA';\n\n/** Parse a raw/display string into every representation at once (one parse → display + wire never drift). */\nexport function parsePhone(raw: string, country?: CountryCode): ParsedPhone {\n const region = country ?? DEFAULT_PHONE_COUNTRY;\n const parsed = parsePhoneNumberFromString(raw ?? '', region);\n if (!parsed) {\n return { e164: null, national: '', international: '', country: region, isValid: false };\n }\n const isValid = parsed.isValid();\n return {\n // E.164 is only surfaced for a *valid* number — a possible-but-invalid input\n // (e.g. too few digits) still parses but must not be transmitted.\n e164: isValid ? parsed.number : null,\n national: parsed.formatNational(),\n international: parsed.formatInternational(),\n country: parsed.country ?? region,\n isValid,\n };\n}\n\n/** Normalize any raw/display value to E.164, or `null` if it isn't a valid number. */\nexport function toE164(raw: string, country?: CountryCode): string | null {\n return parsePhone(raw, country).e164;\n}\n\n/** Format a value for display. Defaults to `'national'`. Never affects the wire value. */\nexport function formatPhone(\n value: string,\n format: PhoneDisplayFormat = 'national',\n country?: CountryCode,\n): string {\n const parsed = parsePhone(value, country);\n if (typeof format === 'function') return format(parsed);\n switch (format) {\n case 'international':\n return parsed.international || value;\n case 'e164':\n return parsed.e164 ?? value;\n case 'national':\n default:\n return parsed.national || value;\n }\n}\n\n/** Live, incremental formatting for an `<input>` as the user types (`AsYouType`). */\nexport function formatPhoneAsTyped(raw: string, country?: CountryCode): string {\n return new AsYouType(country ?? DEFAULT_PHONE_COUNTRY).input(raw ?? '');\n}\n","// Layer 0 (init config) + Layer 3 (tracking integration) for phone fields.\n// Kept in core so both the React and Next packages share identical types, and\n// the `phoneField` helper stays isomorphic.\n\nimport type { JsonValue } from './events/form-submit';\nimport { toE164, type CountryCode, type PhoneDisplayFormat } from './phone';\n\n/**\n * Init-time phone config (`createTracking({ phone })`). The transmitted value is\n * ALWAYS E.164 and is deliberately not configurable here — only display is.\n */\nexport interface PhoneConfig {\n /** Region assumed for numbers typed without a country code. Default `'CA'`. */\n defaultCountry?: CountryCode;\n /** How the input DISPLAYS to the user. Default `'national'`. Does not affect the wire. */\n display?: PhoneDisplayFormat;\n}\n\n/** A single tracked form field destined for `form_submit.fields[]`. */\nexport interface TrackedField {\n name: string;\n type: string;\n value: JsonValue;\n label?: string | null;\n}\n\n/**\n * Build a tracked field whose wire value is ALWAYS E.164. The client keeps its\n * own display value for UI/email; this puts `+E.164` on the wire (or `null` when\n * the input isn't a valid number).\n */\nexport function phoneField(name: string, raw: string, country?: CountryCode): TrackedField {\n return { name, type: 'phone', value: toE164(raw, country) };\n}\n"],"mappings":"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","names":["import_zod","import_zod","import_zod","import_zod","import_zod","import_zod","import_zod","import_zod","import_zod"]}
|