@aranova/tracking-react 0.4.2 → 0.5.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/dist/index.mjs CHANGED
@@ -570,26 +570,21 @@ function createTrackingClient(config) {
570
570
  };
571
571
  }
572
572
 
573
- // ../tracking-core/src/events/contact-page-visit.ts
573
+ // ../tracking-core/src/events/cta-click.ts
574
574
  import { z as z2 } from "zod";
575
- var contactPageVisitMetadataSchema = z2.object({
575
+ var ctaClickMetadataSchema = z2.object({
576
+ cta_name: z2.string(),
576
577
  page: z2.object({
577
578
  path: z2.string()
578
- })
579
- }).strict();
580
- var contactPageVisitConfigSchema = z2.object({
581
- // RegExp is runtime-only so we wrap it via z.custom. Consumers pass a
582
- // real regex at createTracking() time; the factory validates with this
583
- // schema and then keeps the live RegExp reference for runtime matching.
584
- pathPattern: z2.custom(
585
- (value) => value instanceof RegExp,
586
- { message: "pathPattern must be a RegExp" }
587
- )
579
+ }),
580
+ section: z2.string().nullable().optional(),
581
+ destination_url: z2.string().nullable().optional()
588
582
  }).strict();
583
+ var ctaClickConfigSchema = z2.object({}).strict();
589
584
 
590
- // ../tracking-core/src/events/form-submit.ts
585
+ // ../tracking-core/src/events/form-start.ts
591
586
  import { z as z3 } from "zod";
592
- var formSubmitMetadataSchema = z3.object({
587
+ var formStartMetadataSchema = z3.object({
593
588
  form: z3.object({
594
589
  id: z3.string(),
595
590
  action: z3.string().nullable()
@@ -598,32 +593,106 @@ var formSubmitMetadataSchema = z3.object({
598
593
  path: z3.string()
599
594
  })
600
595
  }).strict();
601
- var formSubmitConfigSchema = z3.object({}).strict();
596
+ var formStartConfigSchema = z3.object({
597
+ selector: z3.string().optional()
598
+ }).strict();
602
599
 
603
- // ../tracking-core/src/events/phone-click.ts
600
+ // ../tracking-core/src/events/form-submit.ts
604
601
  import { z as z4 } from "zod";
605
- var phoneClickMetadataSchema = z4.object({
606
- element: z4.object({
607
- tag: z4.string(),
608
- text: z4.string().nullable(),
609
- href: z4.string()
602
+ var formSubmitMetadataSchema = z4.object({
603
+ form: z4.object({
604
+ id: z4.string(),
605
+ action: z4.string().nullable(),
606
+ fields: z4.array(
607
+ z4.object({
608
+ name: z4.string(),
609
+ type: z4.string(),
610
+ label: z4.string().nullable(),
611
+ has_value: z4.boolean()
612
+ })
613
+ ).optional()
610
614
  }),
611
615
  page: z4.object({
612
616
  path: z4.string()
613
617
  })
614
618
  }).strict();
615
- var phoneClickConfigSchema = z4.object({}).strict();
619
+ var formSubmitConfigSchema = z4.object({}).strict();
616
620
 
617
- // ../tracking-core/src/events/time-on-site.ts
621
+ // ../tracking-core/src/events/multi-page-session.ts
618
622
  import { z as z5 } from "zod";
619
- var timeOnSiteMetadataSchema = z5.object({
620
- duration_ms: z5.number().int().nonnegative(),
623
+ var multiPageSessionMetadataSchema = z5.object({
624
+ page_count: z5.number().int().min(2),
621
625
  page: z5.object({
622
626
  path: z5.string()
623
627
  })
624
628
  }).strict();
625
- var timeOnSiteConfigSchema = z5.object({
626
- thresholdSeconds: z5.number().int().positive()
629
+ var multiPageSessionConfigSchema = z5.object({
630
+ pageThreshold: z5.number().int().min(2)
631
+ }).strict();
632
+
633
+ // ../tracking-core/src/events/phone-click.ts
634
+ import { z as z6 } from "zod";
635
+ var phoneClickMetadataSchema = z6.object({
636
+ phone_number: z6.string(),
637
+ page: z6.object({
638
+ path: z6.string()
639
+ }),
640
+ section: z6.string().nullable().optional()
641
+ }).strict();
642
+ var phoneClickConfigSchema = z6.object({}).strict();
643
+
644
+ // ../tracking-core/src/events/scroll-depth.ts
645
+ import { z as z7 } from "zod";
646
+ var scrollDepthMetadataSchema = z7.object({
647
+ depth_percent: z7.number().int().min(1).max(100),
648
+ page: z7.object({
649
+ path: z7.string()
650
+ })
651
+ }).strict();
652
+ var scrollDepthConfigSchema = z7.object({
653
+ thresholds: z7.array(z7.number().int().min(1).max(100)).min(1)
654
+ }).strict();
655
+
656
+ // ../tracking-core/src/events/specific-page-visit.ts
657
+ import { z as z8 } from "zod";
658
+ var SPECIFIC_PAGE_NAMES = [
659
+ "contact_page",
660
+ "about_page",
661
+ "services_page",
662
+ "booking_page",
663
+ "location_page",
664
+ "pricing_page",
665
+ "faq_page",
666
+ "testimonials_page"
667
+ ];
668
+ var specificPageNameSchema = z8.enum(SPECIFIC_PAGE_NAMES);
669
+ var specificPageVisitMetadataSchema = z8.object({
670
+ page_name: specificPageNameSchema,
671
+ page: z8.object({
672
+ path: z8.string()
673
+ })
674
+ }).strict();
675
+ var specificPageVisitConfigSchema = z8.object({
676
+ pages: z8.array(
677
+ z8.object({
678
+ name: specificPageNameSchema,
679
+ pathPattern: z8.custom((value) => value instanceof RegExp, {
680
+ message: "pathPattern must be a RegExp"
681
+ })
682
+ })
683
+ ).min(1)
684
+ }).strict();
685
+
686
+ // ../tracking-core/src/events/time-on-site.ts
687
+ import { z as z9 } from "zod";
688
+ var timeOnSiteMetadataSchema = z9.object({
689
+ duration_ms: z9.number().int().nonnegative(),
690
+ page: z9.object({
691
+ path: z9.string()
692
+ })
693
+ }).strict();
694
+ var timeOnSiteConfigSchema = z9.object({
695
+ thresholdSeconds: z9.number().int().positive()
627
696
  }).strict();
628
697
 
629
698
  // ../tracking-core/src/events/registry.ts
@@ -639,10 +708,25 @@ var EVENT_REGISTRY = {
639
708
  metadataSchema: timeOnSiteMetadataSchema,
640
709
  configSchema: timeOnSiteConfigSchema
641
710
  },
642
- contact_page_visit: {
711
+ specific_page_visit: {
712
+ kind: "automatic",
713
+ metadataSchema: specificPageVisitMetadataSchema,
714
+ configSchema: specificPageVisitConfigSchema
715
+ },
716
+ scroll_depth: {
717
+ kind: "automatic",
718
+ metadataSchema: scrollDepthMetadataSchema,
719
+ configSchema: scrollDepthConfigSchema
720
+ },
721
+ multi_page_session: {
722
+ kind: "automatic",
723
+ metadataSchema: multiPageSessionMetadataSchema,
724
+ configSchema: multiPageSessionConfigSchema
725
+ },
726
+ form_start: {
643
727
  kind: "automatic",
644
- metadataSchema: contactPageVisitMetadataSchema,
645
- configSchema: contactPageVisitConfigSchema
728
+ metadataSchema: formStartMetadataSchema,
729
+ configSchema: formStartConfigSchema
646
730
  },
647
731
  // --- manual triggers ---
648
732
  form_submit: {
@@ -654,6 +738,11 @@ var EVENT_REGISTRY = {
654
738
  kind: "manual",
655
739
  metadataSchema: phoneClickMetadataSchema,
656
740
  configSchema: phoneClickConfigSchema
741
+ },
742
+ cta_click: {
743
+ kind: "manual",
744
+ metadataSchema: ctaClickMetadataSchema,
745
+ configSchema: ctaClickConfigSchema
657
746
  }
658
747
  };
659
748
  var ALL_AUTOMATIC_EVENT_NAMES = Object.entries(EVENT_REGISTRY).filter(([, def]) => def.kind === "automatic").map(([name]) => name);
@@ -744,26 +833,193 @@ function attachTimeOnSite(client, config) {
744
833
  };
745
834
  }
746
835
 
747
- // ../tracking-core/src/triggers/contact-page-visit.ts
748
- function attachContactPageVisit(client, config) {
836
+ // ../tracking-core/src/triggers/specific-page-visit.ts
837
+ function attachSpecificPageVisit(client, config) {
749
838
  if (typeof window === "undefined" || typeof history === "undefined") {
750
839
  return () => {
751
840
  };
752
841
  }
753
- const { pathPattern } = config;
754
- let lastFiredPath = null;
842
+ const { pages } = config;
843
+ const firedSet = /* @__PURE__ */ new Set();
755
844
  function check() {
756
845
  const path = window.location.pathname;
757
- if (!pathPattern.test(path)) return;
758
- if (lastFiredPath === path) return;
759
- lastFiredPath = path;
760
- client.trackEvent({
761
- eventType: "contact_page_visit",
762
- metadata: { page: { path } },
763
- pageUrl: window.location.href,
764
- occurredAt: null
846
+ for (const { name, pathPattern } of pages) {
847
+ pathPattern.lastIndex = 0;
848
+ if (!pathPattern.test(path)) continue;
849
+ const key = `${name}:${path}`;
850
+ if (firedSet.has(key)) continue;
851
+ firedSet.add(key);
852
+ client.trackEvent({
853
+ eventType: "specific_page_visit",
854
+ metadata: { page_name: name, page: { path } },
855
+ pageUrl: window.location.href,
856
+ occurredAt: null
857
+ });
858
+ }
859
+ }
860
+ const originalPushState = history.pushState.bind(history);
861
+ const originalReplaceState = history.replaceState.bind(history);
862
+ function patchedPushState(...args) {
863
+ originalPushState(...args);
864
+ setTimeout(check, 0);
865
+ }
866
+ function patchedReplaceState(...args) {
867
+ originalReplaceState(...args);
868
+ setTimeout(check, 0);
869
+ }
870
+ history.pushState = patchedPushState;
871
+ history.replaceState = patchedReplaceState;
872
+ window.addEventListener("popstate", check);
873
+ check();
874
+ return () => {
875
+ history.pushState = originalPushState;
876
+ history.replaceState = originalReplaceState;
877
+ window.removeEventListener("popstate", check);
878
+ };
879
+ }
880
+
881
+ // ../tracking-core/src/triggers/scroll-depth.ts
882
+ function attachScrollDepth(client, config) {
883
+ if (typeof window === "undefined" || typeof document === "undefined") {
884
+ return () => {
885
+ };
886
+ }
887
+ const thresholds = new Set(config.thresholds);
888
+ let firedForPath = /* @__PURE__ */ new Set();
889
+ let currentPath = window.location.pathname;
890
+ let rafId = null;
891
+ function getScrollPercent() {
892
+ const doc = document.documentElement;
893
+ const scrollTop = window.scrollY || doc.scrollTop;
894
+ const scrollHeight = doc.scrollHeight;
895
+ const clientHeight = doc.clientHeight;
896
+ if (scrollHeight <= clientHeight) return 100;
897
+ return Math.round((scrollTop + clientHeight) / scrollHeight * 100);
898
+ }
899
+ function checkThresholds() {
900
+ const percent = getScrollPercent();
901
+ for (const threshold of thresholds) {
902
+ if (percent >= threshold && !firedForPath.has(threshold)) {
903
+ firedForPath.add(threshold);
904
+ client.trackEvent({
905
+ eventType: "scroll_depth",
906
+ metadata: {
907
+ depth_percent: threshold,
908
+ page: { path: currentPath }
909
+ },
910
+ pageUrl: window.location.href,
911
+ occurredAt: null
912
+ });
913
+ }
914
+ }
915
+ }
916
+ function onScroll() {
917
+ if (rafId !== null) return;
918
+ rafId = requestAnimationFrame(() => {
919
+ rafId = null;
920
+ checkThresholds();
765
921
  });
766
922
  }
923
+ function resetIfPathChanged() {
924
+ const newPath = window.location.pathname;
925
+ if (newPath === currentPath) return;
926
+ currentPath = newPath;
927
+ firedForPath = /* @__PURE__ */ new Set();
928
+ setTimeout(checkThresholds, 0);
929
+ }
930
+ const originalPushState = history.pushState.bind(history);
931
+ const originalReplaceState = history.replaceState.bind(history);
932
+ function patchedPushState(...args) {
933
+ originalPushState(...args);
934
+ setTimeout(resetIfPathChanged, 0);
935
+ }
936
+ function patchedReplaceState(...args) {
937
+ originalReplaceState(...args);
938
+ setTimeout(resetIfPathChanged, 0);
939
+ }
940
+ history.pushState = patchedPushState;
941
+ history.replaceState = patchedReplaceState;
942
+ window.addEventListener("popstate", resetIfPathChanged);
943
+ window.addEventListener("scroll", onScroll, { passive: true });
944
+ setTimeout(checkThresholds, 0);
945
+ return () => {
946
+ if (rafId !== null) cancelAnimationFrame(rafId);
947
+ history.pushState = originalPushState;
948
+ history.replaceState = originalReplaceState;
949
+ window.removeEventListener("popstate", resetIfPathChanged);
950
+ window.removeEventListener("scroll", onScroll);
951
+ };
952
+ }
953
+
954
+ // ../tracking-core/src/triggers/multi-page-session.ts
955
+ var STORAGE_KEY = "aranova_tracking_mps_paths";
956
+ var SESSION_KEY = "aranova_tracking_mps_session";
957
+ var FIRED_KEY = "aranova_tracking_mps_fired";
958
+ function getSessionStorage() {
959
+ try {
960
+ return typeof window !== "undefined" ? window.sessionStorage : null;
961
+ } catch {
962
+ return null;
963
+ }
964
+ }
965
+ function attachMultiPageSession(client, config) {
966
+ if (typeof window === "undefined" || typeof history === "undefined") {
967
+ return () => {
968
+ };
969
+ }
970
+ const storage = getSessionStorage();
971
+ if (!storage) return () => {
972
+ };
973
+ const { pageThreshold } = config;
974
+ let lastCheckedPath = "";
975
+ function getDistinctPaths() {
976
+ try {
977
+ const raw = storage.getItem(STORAGE_KEY);
978
+ return raw ? new Set(JSON.parse(raw)) : /* @__PURE__ */ new Set();
979
+ } catch {
980
+ return /* @__PURE__ */ new Set();
981
+ }
982
+ }
983
+ function saveDistinctPaths(paths) {
984
+ try {
985
+ storage.setItem(STORAGE_KEY, JSON.stringify([...paths]));
986
+ } catch {
987
+ }
988
+ }
989
+ function resetIfSessionChanged() {
990
+ const currentSession = getOrRotateSessionId();
991
+ const storedSession = storage.getItem(SESSION_KEY);
992
+ if (storedSession !== currentSession) {
993
+ storage.setItem(SESSION_KEY, currentSession);
994
+ storage.removeItem(STORAGE_KEY);
995
+ storage.removeItem(FIRED_KEY);
996
+ }
997
+ }
998
+ function hasFired() {
999
+ return storage.getItem(FIRED_KEY) === "1";
1000
+ }
1001
+ function check() {
1002
+ const currentPath = window.location.pathname;
1003
+ if (currentPath === lastCheckedPath) return;
1004
+ lastCheckedPath = currentPath;
1005
+ resetIfSessionChanged();
1006
+ if (hasFired()) return;
1007
+ const paths = getDistinctPaths();
1008
+ paths.add(currentPath);
1009
+ saveDistinctPaths(paths);
1010
+ if (paths.size >= pageThreshold) {
1011
+ storage.setItem(FIRED_KEY, "1");
1012
+ client.trackEvent({
1013
+ eventType: "multi_page_session",
1014
+ metadata: {
1015
+ page_count: paths.size,
1016
+ page: { path: window.location.pathname }
1017
+ },
1018
+ pageUrl: window.location.href,
1019
+ occurredAt: null
1020
+ });
1021
+ }
1022
+ }
767
1023
  const originalPushState = history.pushState.bind(history);
768
1024
  const originalReplaceState = history.replaceState.bind(history);
769
1025
  function patchedPushState(...args) {
@@ -785,6 +1041,71 @@ function attachContactPageVisit(client, config) {
785
1041
  };
786
1042
  }
787
1043
 
1044
+ // ../tracking-core/src/triggers/form-start.ts
1045
+ function attachFormStart(client, config) {
1046
+ if (typeof window === "undefined" || typeof document === "undefined") {
1047
+ return () => {
1048
+ };
1049
+ }
1050
+ const selector = config.selector ?? "form";
1051
+ let firedForms = /* @__PURE__ */ new Set();
1052
+ let currentPath = window.location.pathname;
1053
+ function getFormKey(form) {
1054
+ if (form.id) return `id:${form.id}`;
1055
+ const explicitAction = form.getAttribute("action");
1056
+ if (explicitAction) return `action:${explicitAction}`;
1057
+ const forms = Array.from(document.querySelectorAll(selector));
1058
+ return `index:${forms.indexOf(form)}`;
1059
+ }
1060
+ function onFocusIn(event) {
1061
+ const target = event.target;
1062
+ if (!(target instanceof HTMLElement)) return;
1063
+ const form = target.closest(selector);
1064
+ if (!form || form.tagName !== "FORM") return;
1065
+ const key = getFormKey(form);
1066
+ if (firedForms.has(key)) return;
1067
+ firedForms.add(key);
1068
+ client.trackEvent({
1069
+ eventType: "form_start",
1070
+ metadata: {
1071
+ form: {
1072
+ id: form.id || "",
1073
+ action: form.getAttribute("action") ?? null
1074
+ },
1075
+ page: { path: window.location.pathname }
1076
+ },
1077
+ pageUrl: window.location.href,
1078
+ occurredAt: null
1079
+ });
1080
+ }
1081
+ function resetIfPathChanged() {
1082
+ const newPath = window.location.pathname;
1083
+ if (newPath === currentPath) return;
1084
+ currentPath = newPath;
1085
+ firedForms = /* @__PURE__ */ new Set();
1086
+ }
1087
+ const originalPushState = history.pushState.bind(history);
1088
+ const originalReplaceState = history.replaceState.bind(history);
1089
+ function patchedPushState(...args) {
1090
+ originalPushState(...args);
1091
+ setTimeout(resetIfPathChanged, 0);
1092
+ }
1093
+ function patchedReplaceState(...args) {
1094
+ originalReplaceState(...args);
1095
+ setTimeout(resetIfPathChanged, 0);
1096
+ }
1097
+ history.pushState = patchedPushState;
1098
+ history.replaceState = patchedReplaceState;
1099
+ window.addEventListener("popstate", resetIfPathChanged);
1100
+ document.addEventListener("focusin", onFocusIn);
1101
+ return () => {
1102
+ history.pushState = originalPushState;
1103
+ history.replaceState = originalReplaceState;
1104
+ window.removeEventListener("popstate", resetIfPathChanged);
1105
+ document.removeEventListener("focusin", onFocusIn);
1106
+ };
1107
+ }
1108
+
788
1109
  // src/ConsentBanner.tsx
789
1110
  import { jsx, jsxs } from "react/jsx-runtime";
790
1111
  function ConsentBanner() {
@@ -879,7 +1200,7 @@ import {
879
1200
  } from "react";
880
1201
 
881
1202
  // package.json
882
- var version = "0.4.2";
1203
+ var version = "0.5.0";
883
1204
 
884
1205
  // src/factory.tsx
885
1206
  import { jsx as jsx2 } from "react/jsx-runtime";
@@ -941,9 +1262,21 @@ function createTracking(options) {
941
1262
  if (timeOnSite) {
942
1263
  detachers.push(attachTimeOnSite(rawClient, timeOnSite));
943
1264
  }
944
- const contactPageVisit = triggers.automatic.contact_page_visit;
945
- if (contactPageVisit) {
946
- detachers.push(attachContactPageVisit(rawClient, contactPageVisit));
1265
+ const specificPageVisit = triggers.automatic.specific_page_visit;
1266
+ if (specificPageVisit) {
1267
+ detachers.push(attachSpecificPageVisit(rawClient, specificPageVisit));
1268
+ }
1269
+ const scrollDepth = triggers.automatic.scroll_depth;
1270
+ if (scrollDepth) {
1271
+ detachers.push(attachScrollDepth(rawClient, scrollDepth));
1272
+ }
1273
+ const multiPageSession = triggers.automatic.multi_page_session;
1274
+ if (multiPageSession) {
1275
+ detachers.push(attachMultiPageSession(rawClient, multiPageSession));
1276
+ }
1277
+ const formStart = triggers.automatic.form_start;
1278
+ if (formStart) {
1279
+ detachers.push(attachFormStart(rawClient, formStart));
947
1280
  }
948
1281
  return () => {
949
1282
  for (let i = detachers.length - 1; i >= 0; i--) {