@aranova/tracking-react 1.14.0 → 1.14.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/client.d.mts CHANGED
@@ -1,11 +1,10 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode, CSSProperties, InputHTMLAttributes, ChangeEvent, FocusEvent, AnchorHTMLAttributes } from 'react';
3
- import { G as GtagEnvironmentMap, M as MetaPixelEnvironmentMap, h as ConsentState, f as ConsentChoiceState, g as ConsentSource, T as TrackingParams, b as TrackingEnvironment, s as PhoneConfig, P as ParsedPhone, t as PhoneDisplayFormat } from './phone-utils-Cfdx474X.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-CDGNZUaH.mjs';
5
- import { a7 as TriggerRegistryConfig, a9 as TypedTrackingClient, a as BookingHandoffPrefill, b as BookingIntegration, B as BookableDay } from './submit-rqgXl-Wg.mjs';
6
- export { ai as createLeadFormSubmit } from './submit-rqgXl-Wg.mjs';
3
+ import { G as GtagEnvironmentMap, M as MetaPixelEnvironmentMap, h as ConsentState, f as ConsentChoiceState, g as ConsentSource, T as TrackingParams, b as TrackingEnvironment, u as PhoneConfig, P as ParsedPhone, v as PhoneDisplayFormat } from './phone-utils-CYr6AmEP.mjs';
4
+ import { T as TrackingConfigReference, C as ConversionConfig, h as BookingHandoffPrefill, i as BookingInput, p as BookingOutcome, b as BookingDetailsInput, d as BookingDetailsOutcome, k as BookingIntegration, B as BookableDay, x as LeadFormContracts, S as SalesBookingsClient, g as BookingFormSubmit, t as BookingSimulation, j as BookingInputFor } from './bookings-DFz3BUiP.mjs';
5
+ export { Z as createLeadFormSubmit } from './bookings-DFz3BUiP.mjs';
6
+ import { L as TriggerRegistryConfig, O as TypedTrackingClient } from './ingest-typed-BO6Arbw-.mjs';
7
7
  import { CountryCode } from 'libphonenumber-js';
8
- import './form-submit-C_vJXCxh.mjs';
9
8
  import 'zod/mini';
10
9
  import 'zod/v4/core';
11
10
 
@@ -408,11 +407,11 @@ interface BookedAttempt {
408
407
  bookingId: string;
409
408
  attemptKey: string;
410
409
  }
411
- interface BookingSubmitState {
410
+ interface BookingSubmitState<I = BookingInput> {
412
411
  status: BookingSubmitStatus;
413
412
  outcome: BookingOutcome | null;
414
413
  /** Send the booking. A retry of an unanswered or refused submit reuses its attempt key. */
415
- submit(input: BookingInput): Promise<BookingOutcome>;
414
+ submit(input: I): Promise<BookingOutcome>;
416
415
  /**
417
416
  * The recorded booking and the key it was sent with; persist it (e.g. `sessionStorage`) to add
418
417
  * details after a reload. Null until the server has the booking.
@@ -431,11 +430,13 @@ interface BookingSubmitState {
431
430
  * get wrong: one attempt key per submit (a double-click never books twice), the visit's
432
431
  * `form_submit`, the stale-config refetch, and the fallback link on failure. A `pending` answer
433
432
  * shows at once (`status: "pending"`) while the final one is polled; the submit stays in flight.
433
+ * Pass the site's `ARANOVA_LEAD_FORMS` as `contracts` to type the input by the shop's rules.
434
434
  */
435
- declare function useBookingSubmit(bookings: SalesBookingsClient, options?: {
435
+ declare function useBookingSubmit<C extends LeadFormContracts | undefined = undefined>(bookings: SalesBookingsClient, options?: {
436
436
  formSubmit?: BookingFormSubmit;
437
437
  /** Development only: a simulated create's outcome. The production API ignores it. */
438
438
  simulate?: BookingSimulation;
439
- }): BookingSubmitState;
439
+ contracts?: C;
440
+ }): BookingSubmitState<C extends LeadFormContracts ? BookingInputFor<C> : BookingInput>;
440
441
 
441
442
  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,11 +1,10 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode, CSSProperties, InputHTMLAttributes, ChangeEvent, FocusEvent, AnchorHTMLAttributes } from 'react';
3
- import { G as GtagEnvironmentMap, M as MetaPixelEnvironmentMap, h as ConsentState, f as ConsentChoiceState, g as ConsentSource, T as TrackingParams, b as TrackingEnvironment, s as PhoneConfig, P as ParsedPhone, t as PhoneDisplayFormat } from './phone-utils-D2TGPCQW.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-Bw2ImtPd.js';
5
- import { a7 as TriggerRegistryConfig, a9 as TypedTrackingClient, a as BookingHandoffPrefill, b as BookingIntegration, B as BookableDay } from './submit-DuvP5AGN.js';
6
- export { ai as createLeadFormSubmit } from './submit-DuvP5AGN.js';
3
+ import { G as GtagEnvironmentMap, M as MetaPixelEnvironmentMap, h as ConsentState, f as ConsentChoiceState, g as ConsentSource, T as TrackingParams, b as TrackingEnvironment, u as PhoneConfig, P as ParsedPhone, v as PhoneDisplayFormat } from './phone-utils-CYr6AmEP.js';
4
+ import { T as TrackingConfigReference, C as ConversionConfig, h as BookingHandoffPrefill, i as BookingInput, p as BookingOutcome, b as BookingDetailsInput, d as BookingDetailsOutcome, k as BookingIntegration, B as BookableDay, x as LeadFormContracts, S as SalesBookingsClient, g as BookingFormSubmit, t as BookingSimulation, j as BookingInputFor } from './bookings-CkfA5XOM.js';
5
+ export { Z as createLeadFormSubmit } from './bookings-CkfA5XOM.js';
6
+ import { L as TriggerRegistryConfig, O as TypedTrackingClient } from './ingest-typed-D7I4aQ7p.js';
7
7
  import { CountryCode } from 'libphonenumber-js';
8
- import './form-submit-C_vJXCxh.js';
9
8
  import 'zod/mini';
10
9
  import 'zod/v4/core';
11
10
 
@@ -408,11 +407,11 @@ interface BookedAttempt {
408
407
  bookingId: string;
409
408
  attemptKey: string;
410
409
  }
411
- interface BookingSubmitState {
410
+ interface BookingSubmitState<I = BookingInput> {
412
411
  status: BookingSubmitStatus;
413
412
  outcome: BookingOutcome | null;
414
413
  /** Send the booking. A retry of an unanswered or refused submit reuses its attempt key. */
415
- submit(input: BookingInput): Promise<BookingOutcome>;
414
+ submit(input: I): Promise<BookingOutcome>;
416
415
  /**
417
416
  * The recorded booking and the key it was sent with; persist it (e.g. `sessionStorage`) to add
418
417
  * details after a reload. Null until the server has the booking.
@@ -431,11 +430,13 @@ interface BookingSubmitState {
431
430
  * get wrong: one attempt key per submit (a double-click never books twice), the visit's
432
431
  * `form_submit`, the stale-config refetch, and the fallback link on failure. A `pending` answer
433
432
  * shows at once (`status: "pending"`) while the final one is polled; the submit stays in flight.
433
+ * Pass the site's `ARANOVA_LEAD_FORMS` as `contracts` to type the input by the shop's rules.
434
434
  */
435
- declare function useBookingSubmit(bookings: SalesBookingsClient, options?: {
435
+ declare function useBookingSubmit<C extends LeadFormContracts | undefined = undefined>(bookings: SalesBookingsClient, options?: {
436
436
  formSubmit?: BookingFormSubmit;
437
437
  /** Development only: a simulated create's outcome. The production API ignores it. */
438
438
  simulate?: BookingSimulation;
439
- }): BookingSubmitState;
439
+ contracts?: C;
440
+ }): BookingSubmitState<C extends LeadFormContracts ? BookingInputFor<C> : BookingInput>;
440
441
 
441
442
  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
@@ -3931,6 +3931,157 @@ var LEAD_FORM_ROLE_SPECS = {
3931
3931
  attribution: { primitive: "enum", smsLabel: "", uninteresting: true }
3932
3932
  };
3933
3933
 
3934
+ // ../tracking-core/src/lead-forms/submit.ts
3935
+ var import_mini4 = require("zod/mini");
3936
+ var KNOWN_ROLES = new Set(LEAD_FORM_ROLES);
3937
+ var ROLE_ORDER = new Map(LEAD_FORM_ROLES.map((role, index) => [role, index]));
3938
+ var ADD_ON = /^add_on:[a-z0-9_-]{1,48}$/;
3939
+ var CONTRACT_NAME = /^[a-z][a-z0-9_]{0,47}(:[a-z0-9_-]{1,48})?$/;
3940
+ var INPUT_TYPES = {
3941
+ string: "text",
3942
+ text: "textarea",
3943
+ enum: "select",
3944
+ multi_enum: "select",
3945
+ bool: "checkbox",
3946
+ date: "date",
3947
+ time_window: "time",
3948
+ phone: "tel",
3949
+ email: "email",
3950
+ address: "address",
3951
+ number: "number"
3952
+ };
3953
+ function createLeadFormSubmit(tracking, contracts, options = {}) {
3954
+ registerCapability("typed_form_submit");
3955
+ const settings = { ...typedClientSettings(tracking), ...options };
3956
+ return {
3957
+ submit(input) {
3958
+ const fields = presentFields(input.fields);
3959
+ const { violations, hash } = judgeLeadForm(
3960
+ input.kind,
3961
+ input.formId,
3962
+ fields,
3963
+ contracts,
3964
+ settings
3965
+ );
3966
+ const metadata = {
3967
+ form: {
3968
+ id: input.formId,
3969
+ action: input.action ?? null,
3970
+ fields: fields.map(([role, value]) => ({
3971
+ name: role,
3972
+ type: INPUT_TYPES[primitiveOf(role) ?? "string"],
3973
+ label: labelOf(role),
3974
+ value,
3975
+ role
3976
+ })),
3977
+ kind: input.kind,
3978
+ contract_version: LEAD_FORM_REGISTRY_VERSION,
3979
+ ...hash ? { contract_hash: hash } : {},
3980
+ ...input.extra?.length ? { extra: input.extra } : {},
3981
+ ...violations.length > 0 ? { contract_violations: violations.slice(0, MAX_CONTRACT_VIOLATIONS) } : {}
3982
+ },
3983
+ page: { path: typeof window === "undefined" ? "/" : window.location.pathname }
3984
+ };
3985
+ tracking.trackEvent("form_submit", metadata);
3986
+ return metadata;
3987
+ }
3988
+ };
3989
+ }
3990
+ function judgeLeadForm(kind, formId, fields, contracts, settings) {
3991
+ const generated = contracts?.contracts[kind];
3992
+ const live = liveContract(settings.trackingConfig, kind);
3993
+ const violations = check(live === void 0 ? generated : fromLive(live), fields);
3994
+ const liveHash = live === void 0 ? void 0 : live?.contract_hash ?? null;
3995
+ if (contracts && (liveHash !== void 0 && (generated?.hash ?? null) !== liveHash || contracts.registryVersion !== LEAD_FORM_REGISTRY_VERSION)) {
3996
+ violations.push({ role: null, rule: "contract_behind" });
3997
+ }
3998
+ if (violations.length > 0 && (settings.debug || settings.environment === "development")) {
3999
+ const message = `[AranovaTracking] lead form "${formId}" (${kind}) breaks its contract: ${violations.map((v) => `${v.rule}${v.role ? ` (${v.role})` : ""}`).join(", ")}`;
4000
+ console.error(message);
4001
+ if (settings.debug) throw new Error(message);
4002
+ }
4003
+ return {
4004
+ violations,
4005
+ hash: liveHash === void 0 ? generated?.hash ?? null : liveHash
4006
+ };
4007
+ }
4008
+ function liveContract(ref, kind) {
4009
+ const leadForms = ref ? getTrackingConfigRuntime(ref).leadForms() : null;
4010
+ if (!leadForms) return void 0;
4011
+ return leadForms.contracts.find((c) => c.form_kind === kind) ?? null;
4012
+ }
4013
+ function fromLive(live) {
4014
+ if (!live) return void 0;
4015
+ return {
4016
+ requires: live.requires.map((r) => ({
4017
+ role: r.role,
4018
+ requiredWhen: r.required_when ? { role: r.required_when.role, anyOf: r.required_when.any_of } : null
4019
+ })),
4020
+ requiresAny: live.requires_any.map((g) => g.roles),
4021
+ // Deprecated keys still resolve: a cached form may send them (ADR-0024 §7).
4022
+ options: Object.fromEntries(live.options.map((o) => [o.role, o.options.map((x) => x.key)]))
4023
+ };
4024
+ }
4025
+ function presentFields(fields) {
4026
+ return Object.entries(fields).map(([role, value]) => [role, normalize(role, value)]).filter((entry) => !isBlank(entry[1])).sort(([a], [b]) => (ROLE_ORDER.get(a) ?? 999) - (ROLE_ORDER.get(b) ?? 999));
4027
+ }
4028
+ function normalize(role, value) {
4029
+ if (typeof value !== "string") return value;
4030
+ const trimmed = value.trim();
4031
+ const primitive = primitiveOf(role);
4032
+ if (primitive === "phone") return toE164(trimmed) ?? trimmed;
4033
+ if (primitive === "email") return trimmed.toLowerCase();
4034
+ return primitive === "text" ? value : trimmed;
4035
+ }
4036
+ function isBlank(value) {
4037
+ return value === void 0 || value === null || typeof value === "string" && value.trim() === "" || Array.isArray(value) && value.length === 0;
4038
+ }
4039
+ function check(contract, fields) {
4040
+ const answered = new Map(fields);
4041
+ const violations = [];
4042
+ for (const [role, value] of fields) {
4043
+ const primitive = primitiveOf(role);
4044
+ if (!primitive) {
4045
+ violations.push({ role: CONTRACT_NAME.test(role) ? role : null, rule: "unknown_role" });
4046
+ continue;
4047
+ }
4048
+ if (!(0, import_mini4.safeParse)(leadFormPrimitiveSchemas[primitive], value).success) {
4049
+ violations.push({ role, rule: "invalid_value" });
4050
+ continue;
4051
+ }
4052
+ const allowed = contract?.options[role] ?? registryOptions(role);
4053
+ if (allowed?.length && optionKeys(value).some((key) => !allowed.includes(key))) {
4054
+ violations.push({ role, rule: "unknown_option" });
4055
+ }
4056
+ }
4057
+ for (const { role, requiredWhen } of contract?.requires ?? []) {
4058
+ const owing = requiredWhen === null || optionKeys(answered.get(requiredWhen.role)).some((key) => requiredWhen.anyOf.includes(key));
4059
+ if (owing && !answered.has(role)) violations.push({ role, rule: "missing_required" });
4060
+ }
4061
+ for (const group of contract?.requiresAny ?? []) {
4062
+ if (!group.some((role) => answered.has(role))) {
4063
+ violations.push({ role: group.join("|"), rule: "missing_any_of" });
4064
+ }
4065
+ }
4066
+ return violations;
4067
+ }
4068
+ function primitiveOf(role) {
4069
+ if (ADD_ON.test(role)) return "bool";
4070
+ return KNOWN_ROLES.has(role) ? LEAD_FORM_ROLE_SPECS[role].primitive : null;
4071
+ }
4072
+ function labelOf(role) {
4073
+ if (ADD_ON.test(role)) return role.slice("add_on:".length).replaceAll(/[_-]+/g, " ");
4074
+ return KNOWN_ROLES.has(role) ? LEAD_FORM_ROLE_SPECS[role].smsLabel || null : null;
4075
+ }
4076
+ function registryOptions(role) {
4077
+ const spec = KNOWN_ROLES.has(role) ? LEAD_FORM_ROLE_SPECS[role] : void 0;
4078
+ return spec && "options" in spec ? spec.options.map((o) => o.key) : void 0;
4079
+ }
4080
+ function optionKeys(value) {
4081
+ if (typeof value === "string") return [value];
4082
+ return Array.isArray(value) ? value.filter((v) => typeof v === "string") : [];
4083
+ }
4084
+
3934
4085
  // ../tracking-core/src/resources/sales/bookings.ts
3935
4086
  var newBookingAttemptKey = uuidV4;
3936
4087
 
@@ -4265,7 +4416,7 @@ function GoogleAdsTracking(props) {
4265
4416
  var import_react6 = require("react");
4266
4417
 
4267
4418
  // package.json
4268
- var version = "1.14.0";
4419
+ var version = "1.14.1";
4269
4420
 
4270
4421
  // ../tracking-core/src/phone-react.tsx
4271
4422
  var import_react5 = require("react");
@@ -4587,7 +4738,11 @@ function useBookingSubmit(bookings, options = {}) {
4587
4738
  const attemptKey = (0, import_react7.useRef)(null);
4588
4739
  const [booked, setBooked] = (0, import_react7.useState)(null);
4589
4740
  const inFlight = (0, import_react7.useRef)(null);
4590
- const { formSubmit, simulate } = options;
4741
+ const { simulate, contracts } = options;
4742
+ const formSubmit = (0, import_react7.useMemo)(
4743
+ () => options.formSubmit && contracts ? { ...options.formSubmit, contracts } : options.formSubmit,
4744
+ [options.formSubmit, contracts]
4745
+ );
4591
4746
  const submit = (0, import_react7.useCallback)(
4592
4747
  (input) => {
4593
4748
  if (inFlight.current) return inFlight.current;
@@ -4639,145 +4794,6 @@ function useBookingSubmit(bookings, options = {}) {
4639
4794
  const status = submitting ? "submitting" : outcome?.status ?? "idle";
4640
4795
  return { status, outcome, booked, submit, updateBooking, reset };
4641
4796
  }
4642
-
4643
- // ../tracking-core/src/lead-forms/submit.ts
4644
- var import_mini4 = require("zod/mini");
4645
- var KNOWN_ROLES = new Set(LEAD_FORM_ROLES);
4646
- var ROLE_ORDER = new Map(LEAD_FORM_ROLES.map((role, index) => [role, index]));
4647
- var ADD_ON = /^add_on:[a-z0-9_-]{1,48}$/;
4648
- var CONTRACT_NAME = /^[a-z][a-z0-9_]{0,47}(:[a-z0-9_-]{1,48})?$/;
4649
- var INPUT_TYPES = {
4650
- string: "text",
4651
- text: "textarea",
4652
- enum: "select",
4653
- multi_enum: "select",
4654
- bool: "checkbox",
4655
- date: "date",
4656
- time_window: "time",
4657
- phone: "tel",
4658
- email: "email",
4659
- address: "address",
4660
- number: "number"
4661
- };
4662
- function createLeadFormSubmit(tracking, contracts, options = {}) {
4663
- registerCapability("typed_form_submit");
4664
- const settings = { ...typedClientSettings(tracking), ...options };
4665
- return {
4666
- submit(input) {
4667
- const generated = contracts.contracts[input.kind];
4668
- const live = liveContract(settings.trackingConfig, input.kind);
4669
- const fields = presentFields(input.fields);
4670
- const violations = check(live === void 0 ? generated : fromLive(live), fields);
4671
- const liveHash = live === void 0 ? void 0 : live?.contract_hash ?? null;
4672
- if (liveHash !== void 0 && (generated?.hash ?? null) !== liveHash || contracts.registryVersion !== LEAD_FORM_REGISTRY_VERSION) {
4673
- violations.push({ role: null, rule: "contract_behind" });
4674
- }
4675
- const hash = liveHash === void 0 ? generated?.hash ?? null : liveHash;
4676
- const metadata = {
4677
- form: {
4678
- id: input.formId,
4679
- action: input.action ?? null,
4680
- fields: fields.map(([role, value]) => ({
4681
- name: role,
4682
- type: INPUT_TYPES[primitiveOf(role) ?? "string"],
4683
- label: labelOf(role),
4684
- value,
4685
- role
4686
- })),
4687
- kind: input.kind,
4688
- contract_version: LEAD_FORM_REGISTRY_VERSION,
4689
- ...hash ? { contract_hash: hash } : {},
4690
- ...input.extra?.length ? { extra: input.extra } : {},
4691
- ...violations.length > 0 ? { contract_violations: violations.slice(0, MAX_CONTRACT_VIOLATIONS) } : {}
4692
- },
4693
- page: { path: typeof window === "undefined" ? "/" : window.location.pathname }
4694
- };
4695
- if (violations.length > 0 && (settings.debug || settings.environment === "development")) {
4696
- const message = `[AranovaTracking] lead form "${input.formId}" (${input.kind}) breaks its contract: ${violations.map((v) => `${v.rule}${v.role ? ` (${v.role})` : ""}`).join(", ")}`;
4697
- console.error(message);
4698
- if (settings.debug) throw new Error(message);
4699
- }
4700
- tracking.trackEvent("form_submit", metadata);
4701
- return metadata;
4702
- }
4703
- };
4704
- }
4705
- function liveContract(ref, kind) {
4706
- const leadForms = ref ? getTrackingConfigRuntime(ref).leadForms() : null;
4707
- if (!leadForms) return void 0;
4708
- return leadForms.contracts.find((c) => c.form_kind === kind) ?? null;
4709
- }
4710
- function fromLive(live) {
4711
- if (!live) return void 0;
4712
- return {
4713
- requires: live.requires.map((r) => ({
4714
- role: r.role,
4715
- requiredWhen: r.required_when ? { role: r.required_when.role, anyOf: r.required_when.any_of } : null
4716
- })),
4717
- requiresAny: live.requires_any.map((g) => g.roles),
4718
- // Deprecated keys still resolve: a cached form may send them (ADR-0024 §7).
4719
- options: Object.fromEntries(live.options.map((o) => [o.role, o.options.map((x) => x.key)]))
4720
- };
4721
- }
4722
- function presentFields(fields) {
4723
- return Object.entries(fields).map(([role, value]) => [role, normalize(role, value)]).filter((entry) => !isBlank(entry[1])).sort(([a], [b]) => (ROLE_ORDER.get(a) ?? 999) - (ROLE_ORDER.get(b) ?? 999));
4724
- }
4725
- function normalize(role, value) {
4726
- if (typeof value !== "string") return value;
4727
- const trimmed = value.trim();
4728
- const primitive = primitiveOf(role);
4729
- if (primitive === "phone") return toE164(trimmed) ?? trimmed;
4730
- if (primitive === "email") return trimmed.toLowerCase();
4731
- return primitive === "text" ? value : trimmed;
4732
- }
4733
- function isBlank(value) {
4734
- return value === void 0 || value === null || typeof value === "string" && value.trim() === "" || Array.isArray(value) && value.length === 0;
4735
- }
4736
- function check(contract, fields) {
4737
- const answered = new Map(fields);
4738
- const violations = [];
4739
- for (const [role, value] of fields) {
4740
- const primitive = primitiveOf(role);
4741
- if (!primitive) {
4742
- violations.push({ role: CONTRACT_NAME.test(role) ? role : null, rule: "unknown_role" });
4743
- continue;
4744
- }
4745
- if (!(0, import_mini4.safeParse)(leadFormPrimitiveSchemas[primitive], value).success) {
4746
- violations.push({ role, rule: "invalid_value" });
4747
- continue;
4748
- }
4749
- const allowed = contract?.options[role] ?? registryOptions(role);
4750
- if (allowed?.length && optionKeys(value).some((key) => !allowed.includes(key))) {
4751
- violations.push({ role, rule: "unknown_option" });
4752
- }
4753
- }
4754
- for (const { role, requiredWhen } of contract?.requires ?? []) {
4755
- const owing = requiredWhen === null || optionKeys(answered.get(requiredWhen.role)).some((key) => requiredWhen.anyOf.includes(key));
4756
- if (owing && !answered.has(role)) violations.push({ role, rule: "missing_required" });
4757
- }
4758
- for (const group of contract?.requiresAny ?? []) {
4759
- if (!group.some((role) => answered.has(role))) {
4760
- violations.push({ role: group.join("|"), rule: "missing_any_of" });
4761
- }
4762
- }
4763
- return violations;
4764
- }
4765
- function primitiveOf(role) {
4766
- if (ADD_ON.test(role)) return "bool";
4767
- return KNOWN_ROLES.has(role) ? LEAD_FORM_ROLE_SPECS[role].primitive : null;
4768
- }
4769
- function labelOf(role) {
4770
- if (ADD_ON.test(role)) return role.slice("add_on:".length).replaceAll(/[_-]+/g, " ");
4771
- return KNOWN_ROLES.has(role) ? LEAD_FORM_ROLE_SPECS[role].smsLabel || null : null;
4772
- }
4773
- function registryOptions(role) {
4774
- const spec = KNOWN_ROLES.has(role) ? LEAD_FORM_ROLE_SPECS[role] : void 0;
4775
- return spec && "options" in spec ? spec.options.map((o) => o.key) : void 0;
4776
- }
4777
- function optionKeys(value) {
4778
- if (typeof value === "string") return [value];
4779
- return Array.isArray(value) ? value.filter((v) => typeof v === "string") : [];
4780
- }
4781
4797
  // Annotate the CommonJS export names for ESM import in node:
4782
4798
  0 && (module.exports = {
4783
4799
  AdPlatformTracking,