@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.js CHANGED
@@ -608,26 +608,21 @@ function createTrackingClient(config) {
608
608
  };
609
609
  }
610
610
 
611
- // ../tracking-core/src/events/contact-page-visit.ts
611
+ // ../tracking-core/src/events/cta-click.ts
612
612
  var import_zod2 = require("zod");
613
- var contactPageVisitMetadataSchema = import_zod2.z.object({
613
+ var ctaClickMetadataSchema = import_zod2.z.object({
614
+ cta_name: import_zod2.z.string(),
614
615
  page: import_zod2.z.object({
615
616
  path: import_zod2.z.string()
616
- })
617
- }).strict();
618
- var contactPageVisitConfigSchema = import_zod2.z.object({
619
- // RegExp is runtime-only so we wrap it via z.custom. Consumers pass a
620
- // real regex at createTracking() time; the factory validates with this
621
- // schema and then keeps the live RegExp reference for runtime matching.
622
- pathPattern: import_zod2.z.custom(
623
- (value) => value instanceof RegExp,
624
- { message: "pathPattern must be a RegExp" }
625
- )
617
+ }),
618
+ section: import_zod2.z.string().nullable().optional(),
619
+ destination_url: import_zod2.z.string().nullable().optional()
626
620
  }).strict();
621
+ var ctaClickConfigSchema = import_zod2.z.object({}).strict();
627
622
 
628
- // ../tracking-core/src/events/form-submit.ts
623
+ // ../tracking-core/src/events/form-start.ts
629
624
  var import_zod3 = require("zod");
630
- var formSubmitMetadataSchema = import_zod3.z.object({
625
+ var formStartMetadataSchema = import_zod3.z.object({
631
626
  form: import_zod3.z.object({
632
627
  id: import_zod3.z.string(),
633
628
  action: import_zod3.z.string().nullable()
@@ -636,32 +631,106 @@ var formSubmitMetadataSchema = import_zod3.z.object({
636
631
  path: import_zod3.z.string()
637
632
  })
638
633
  }).strict();
639
- var formSubmitConfigSchema = import_zod3.z.object({}).strict();
634
+ var formStartConfigSchema = import_zod3.z.object({
635
+ selector: import_zod3.z.string().optional()
636
+ }).strict();
640
637
 
641
- // ../tracking-core/src/events/phone-click.ts
638
+ // ../tracking-core/src/events/form-submit.ts
642
639
  var import_zod4 = require("zod");
643
- var phoneClickMetadataSchema = import_zod4.z.object({
644
- element: import_zod4.z.object({
645
- tag: import_zod4.z.string(),
646
- text: import_zod4.z.string().nullable(),
647
- href: import_zod4.z.string()
640
+ var formSubmitMetadataSchema = import_zod4.z.object({
641
+ form: import_zod4.z.object({
642
+ id: import_zod4.z.string(),
643
+ action: import_zod4.z.string().nullable(),
644
+ fields: import_zod4.z.array(
645
+ import_zod4.z.object({
646
+ name: import_zod4.z.string(),
647
+ type: import_zod4.z.string(),
648
+ label: import_zod4.z.string().nullable(),
649
+ has_value: import_zod4.z.boolean()
650
+ })
651
+ ).optional()
648
652
  }),
649
653
  page: import_zod4.z.object({
650
654
  path: import_zod4.z.string()
651
655
  })
652
656
  }).strict();
653
- var phoneClickConfigSchema = import_zod4.z.object({}).strict();
657
+ var formSubmitConfigSchema = import_zod4.z.object({}).strict();
654
658
 
655
- // ../tracking-core/src/events/time-on-site.ts
659
+ // ../tracking-core/src/events/multi-page-session.ts
656
660
  var import_zod5 = require("zod");
657
- var timeOnSiteMetadataSchema = import_zod5.z.object({
658
- duration_ms: import_zod5.z.number().int().nonnegative(),
661
+ var multiPageSessionMetadataSchema = import_zod5.z.object({
662
+ page_count: import_zod5.z.number().int().min(2),
659
663
  page: import_zod5.z.object({
660
664
  path: import_zod5.z.string()
661
665
  })
662
666
  }).strict();
663
- var timeOnSiteConfigSchema = import_zod5.z.object({
664
- thresholdSeconds: import_zod5.z.number().int().positive()
667
+ var multiPageSessionConfigSchema = import_zod5.z.object({
668
+ pageThreshold: import_zod5.z.number().int().min(2)
669
+ }).strict();
670
+
671
+ // ../tracking-core/src/events/phone-click.ts
672
+ var import_zod6 = require("zod");
673
+ var phoneClickMetadataSchema = import_zod6.z.object({
674
+ phone_number: import_zod6.z.string(),
675
+ page: import_zod6.z.object({
676
+ path: import_zod6.z.string()
677
+ }),
678
+ section: import_zod6.z.string().nullable().optional()
679
+ }).strict();
680
+ var phoneClickConfigSchema = import_zod6.z.object({}).strict();
681
+
682
+ // ../tracking-core/src/events/scroll-depth.ts
683
+ var import_zod7 = require("zod");
684
+ var scrollDepthMetadataSchema = import_zod7.z.object({
685
+ depth_percent: import_zod7.z.number().int().min(1).max(100),
686
+ page: import_zod7.z.object({
687
+ path: import_zod7.z.string()
688
+ })
689
+ }).strict();
690
+ var scrollDepthConfigSchema = import_zod7.z.object({
691
+ thresholds: import_zod7.z.array(import_zod7.z.number().int().min(1).max(100)).min(1)
692
+ }).strict();
693
+
694
+ // ../tracking-core/src/events/specific-page-visit.ts
695
+ var import_zod8 = require("zod");
696
+ var SPECIFIC_PAGE_NAMES = [
697
+ "contact_page",
698
+ "about_page",
699
+ "services_page",
700
+ "booking_page",
701
+ "location_page",
702
+ "pricing_page",
703
+ "faq_page",
704
+ "testimonials_page"
705
+ ];
706
+ var specificPageNameSchema = import_zod8.z.enum(SPECIFIC_PAGE_NAMES);
707
+ var specificPageVisitMetadataSchema = import_zod8.z.object({
708
+ page_name: specificPageNameSchema,
709
+ page: import_zod8.z.object({
710
+ path: import_zod8.z.string()
711
+ })
712
+ }).strict();
713
+ var specificPageVisitConfigSchema = import_zod8.z.object({
714
+ pages: import_zod8.z.array(
715
+ import_zod8.z.object({
716
+ name: specificPageNameSchema,
717
+ pathPattern: import_zod8.z.custom((value) => value instanceof RegExp, {
718
+ message: "pathPattern must be a RegExp"
719
+ })
720
+ })
721
+ ).min(1)
722
+ }).strict();
723
+
724
+ // ../tracking-core/src/events/time-on-site.ts
725
+ var import_zod9 = require("zod");
726
+ var timeOnSiteMetadataSchema = import_zod9.z.object({
727
+ duration_ms: import_zod9.z.number().int().nonnegative(),
728
+ page: import_zod9.z.object({
729
+ path: import_zod9.z.string()
730
+ })
731
+ }).strict();
732
+ var timeOnSiteConfigSchema = import_zod9.z.object({
733
+ thresholdSeconds: import_zod9.z.number().int().positive()
665
734
  }).strict();
666
735
 
667
736
  // ../tracking-core/src/events/registry.ts
@@ -677,10 +746,25 @@ var EVENT_REGISTRY = {
677
746
  metadataSchema: timeOnSiteMetadataSchema,
678
747
  configSchema: timeOnSiteConfigSchema
679
748
  },
680
- contact_page_visit: {
749
+ specific_page_visit: {
750
+ kind: "automatic",
751
+ metadataSchema: specificPageVisitMetadataSchema,
752
+ configSchema: specificPageVisitConfigSchema
753
+ },
754
+ scroll_depth: {
755
+ kind: "automatic",
756
+ metadataSchema: scrollDepthMetadataSchema,
757
+ configSchema: scrollDepthConfigSchema
758
+ },
759
+ multi_page_session: {
760
+ kind: "automatic",
761
+ metadataSchema: multiPageSessionMetadataSchema,
762
+ configSchema: multiPageSessionConfigSchema
763
+ },
764
+ form_start: {
681
765
  kind: "automatic",
682
- metadataSchema: contactPageVisitMetadataSchema,
683
- configSchema: contactPageVisitConfigSchema
766
+ metadataSchema: formStartMetadataSchema,
767
+ configSchema: formStartConfigSchema
684
768
  },
685
769
  // --- manual triggers ---
686
770
  form_submit: {
@@ -692,6 +776,11 @@ var EVENT_REGISTRY = {
692
776
  kind: "manual",
693
777
  metadataSchema: phoneClickMetadataSchema,
694
778
  configSchema: phoneClickConfigSchema
779
+ },
780
+ cta_click: {
781
+ kind: "manual",
782
+ metadataSchema: ctaClickMetadataSchema,
783
+ configSchema: ctaClickConfigSchema
695
784
  }
696
785
  };
697
786
  var ALL_AUTOMATIC_EVENT_NAMES = Object.entries(EVENT_REGISTRY).filter(([, def]) => def.kind === "automatic").map(([name]) => name);
@@ -782,26 +871,193 @@ function attachTimeOnSite(client, config) {
782
871
  };
783
872
  }
784
873
 
785
- // ../tracking-core/src/triggers/contact-page-visit.ts
786
- function attachContactPageVisit(client, config) {
874
+ // ../tracking-core/src/triggers/specific-page-visit.ts
875
+ function attachSpecificPageVisit(client, config) {
787
876
  if (typeof window === "undefined" || typeof history === "undefined") {
788
877
  return () => {
789
878
  };
790
879
  }
791
- const { pathPattern } = config;
792
- let lastFiredPath = null;
880
+ const { pages } = config;
881
+ const firedSet = /* @__PURE__ */ new Set();
793
882
  function check() {
794
883
  const path = window.location.pathname;
795
- if (!pathPattern.test(path)) return;
796
- if (lastFiredPath === path) return;
797
- lastFiredPath = path;
798
- client.trackEvent({
799
- eventType: "contact_page_visit",
800
- metadata: { page: { path } },
801
- pageUrl: window.location.href,
802
- occurredAt: null
884
+ for (const { name, pathPattern } of pages) {
885
+ pathPattern.lastIndex = 0;
886
+ if (!pathPattern.test(path)) continue;
887
+ const key = `${name}:${path}`;
888
+ if (firedSet.has(key)) continue;
889
+ firedSet.add(key);
890
+ client.trackEvent({
891
+ eventType: "specific_page_visit",
892
+ metadata: { page_name: name, page: { path } },
893
+ pageUrl: window.location.href,
894
+ occurredAt: null
895
+ });
896
+ }
897
+ }
898
+ const originalPushState = history.pushState.bind(history);
899
+ const originalReplaceState = history.replaceState.bind(history);
900
+ function patchedPushState(...args) {
901
+ originalPushState(...args);
902
+ setTimeout(check, 0);
903
+ }
904
+ function patchedReplaceState(...args) {
905
+ originalReplaceState(...args);
906
+ setTimeout(check, 0);
907
+ }
908
+ history.pushState = patchedPushState;
909
+ history.replaceState = patchedReplaceState;
910
+ window.addEventListener("popstate", check);
911
+ check();
912
+ return () => {
913
+ history.pushState = originalPushState;
914
+ history.replaceState = originalReplaceState;
915
+ window.removeEventListener("popstate", check);
916
+ };
917
+ }
918
+
919
+ // ../tracking-core/src/triggers/scroll-depth.ts
920
+ function attachScrollDepth(client, config) {
921
+ if (typeof window === "undefined" || typeof document === "undefined") {
922
+ return () => {
923
+ };
924
+ }
925
+ const thresholds = new Set(config.thresholds);
926
+ let firedForPath = /* @__PURE__ */ new Set();
927
+ let currentPath = window.location.pathname;
928
+ let rafId = null;
929
+ function getScrollPercent() {
930
+ const doc = document.documentElement;
931
+ const scrollTop = window.scrollY || doc.scrollTop;
932
+ const scrollHeight = doc.scrollHeight;
933
+ const clientHeight = doc.clientHeight;
934
+ if (scrollHeight <= clientHeight) return 100;
935
+ return Math.round((scrollTop + clientHeight) / scrollHeight * 100);
936
+ }
937
+ function checkThresholds() {
938
+ const percent = getScrollPercent();
939
+ for (const threshold of thresholds) {
940
+ if (percent >= threshold && !firedForPath.has(threshold)) {
941
+ firedForPath.add(threshold);
942
+ client.trackEvent({
943
+ eventType: "scroll_depth",
944
+ metadata: {
945
+ depth_percent: threshold,
946
+ page: { path: currentPath }
947
+ },
948
+ pageUrl: window.location.href,
949
+ occurredAt: null
950
+ });
951
+ }
952
+ }
953
+ }
954
+ function onScroll() {
955
+ if (rafId !== null) return;
956
+ rafId = requestAnimationFrame(() => {
957
+ rafId = null;
958
+ checkThresholds();
803
959
  });
804
960
  }
961
+ function resetIfPathChanged() {
962
+ const newPath = window.location.pathname;
963
+ if (newPath === currentPath) return;
964
+ currentPath = newPath;
965
+ firedForPath = /* @__PURE__ */ new Set();
966
+ setTimeout(checkThresholds, 0);
967
+ }
968
+ const originalPushState = history.pushState.bind(history);
969
+ const originalReplaceState = history.replaceState.bind(history);
970
+ function patchedPushState(...args) {
971
+ originalPushState(...args);
972
+ setTimeout(resetIfPathChanged, 0);
973
+ }
974
+ function patchedReplaceState(...args) {
975
+ originalReplaceState(...args);
976
+ setTimeout(resetIfPathChanged, 0);
977
+ }
978
+ history.pushState = patchedPushState;
979
+ history.replaceState = patchedReplaceState;
980
+ window.addEventListener("popstate", resetIfPathChanged);
981
+ window.addEventListener("scroll", onScroll, { passive: true });
982
+ setTimeout(checkThresholds, 0);
983
+ return () => {
984
+ if (rafId !== null) cancelAnimationFrame(rafId);
985
+ history.pushState = originalPushState;
986
+ history.replaceState = originalReplaceState;
987
+ window.removeEventListener("popstate", resetIfPathChanged);
988
+ window.removeEventListener("scroll", onScroll);
989
+ };
990
+ }
991
+
992
+ // ../tracking-core/src/triggers/multi-page-session.ts
993
+ var STORAGE_KEY = "aranova_tracking_mps_paths";
994
+ var SESSION_KEY = "aranova_tracking_mps_session";
995
+ var FIRED_KEY = "aranova_tracking_mps_fired";
996
+ function getSessionStorage() {
997
+ try {
998
+ return typeof window !== "undefined" ? window.sessionStorage : null;
999
+ } catch {
1000
+ return null;
1001
+ }
1002
+ }
1003
+ function attachMultiPageSession(client, config) {
1004
+ if (typeof window === "undefined" || typeof history === "undefined") {
1005
+ return () => {
1006
+ };
1007
+ }
1008
+ const storage = getSessionStorage();
1009
+ if (!storage) return () => {
1010
+ };
1011
+ const { pageThreshold } = config;
1012
+ let lastCheckedPath = "";
1013
+ function getDistinctPaths() {
1014
+ try {
1015
+ const raw = storage.getItem(STORAGE_KEY);
1016
+ return raw ? new Set(JSON.parse(raw)) : /* @__PURE__ */ new Set();
1017
+ } catch {
1018
+ return /* @__PURE__ */ new Set();
1019
+ }
1020
+ }
1021
+ function saveDistinctPaths(paths) {
1022
+ try {
1023
+ storage.setItem(STORAGE_KEY, JSON.stringify([...paths]));
1024
+ } catch {
1025
+ }
1026
+ }
1027
+ function resetIfSessionChanged() {
1028
+ const currentSession = getOrRotateSessionId();
1029
+ const storedSession = storage.getItem(SESSION_KEY);
1030
+ if (storedSession !== currentSession) {
1031
+ storage.setItem(SESSION_KEY, currentSession);
1032
+ storage.removeItem(STORAGE_KEY);
1033
+ storage.removeItem(FIRED_KEY);
1034
+ }
1035
+ }
1036
+ function hasFired() {
1037
+ return storage.getItem(FIRED_KEY) === "1";
1038
+ }
1039
+ function check() {
1040
+ const currentPath = window.location.pathname;
1041
+ if (currentPath === lastCheckedPath) return;
1042
+ lastCheckedPath = currentPath;
1043
+ resetIfSessionChanged();
1044
+ if (hasFired()) return;
1045
+ const paths = getDistinctPaths();
1046
+ paths.add(currentPath);
1047
+ saveDistinctPaths(paths);
1048
+ if (paths.size >= pageThreshold) {
1049
+ storage.setItem(FIRED_KEY, "1");
1050
+ client.trackEvent({
1051
+ eventType: "multi_page_session",
1052
+ metadata: {
1053
+ page_count: paths.size,
1054
+ page: { path: window.location.pathname }
1055
+ },
1056
+ pageUrl: window.location.href,
1057
+ occurredAt: null
1058
+ });
1059
+ }
1060
+ }
805
1061
  const originalPushState = history.pushState.bind(history);
806
1062
  const originalReplaceState = history.replaceState.bind(history);
807
1063
  function patchedPushState(...args) {
@@ -823,6 +1079,71 @@ function attachContactPageVisit(client, config) {
823
1079
  };
824
1080
  }
825
1081
 
1082
+ // ../tracking-core/src/triggers/form-start.ts
1083
+ function attachFormStart(client, config) {
1084
+ if (typeof window === "undefined" || typeof document === "undefined") {
1085
+ return () => {
1086
+ };
1087
+ }
1088
+ const selector = config.selector ?? "form";
1089
+ let firedForms = /* @__PURE__ */ new Set();
1090
+ let currentPath = window.location.pathname;
1091
+ function getFormKey(form) {
1092
+ if (form.id) return `id:${form.id}`;
1093
+ const explicitAction = form.getAttribute("action");
1094
+ if (explicitAction) return `action:${explicitAction}`;
1095
+ const forms = Array.from(document.querySelectorAll(selector));
1096
+ return `index:${forms.indexOf(form)}`;
1097
+ }
1098
+ function onFocusIn(event) {
1099
+ const target = event.target;
1100
+ if (!(target instanceof HTMLElement)) return;
1101
+ const form = target.closest(selector);
1102
+ if (!form || form.tagName !== "FORM") return;
1103
+ const key = getFormKey(form);
1104
+ if (firedForms.has(key)) return;
1105
+ firedForms.add(key);
1106
+ client.trackEvent({
1107
+ eventType: "form_start",
1108
+ metadata: {
1109
+ form: {
1110
+ id: form.id || "",
1111
+ action: form.getAttribute("action") ?? null
1112
+ },
1113
+ page: { path: window.location.pathname }
1114
+ },
1115
+ pageUrl: window.location.href,
1116
+ occurredAt: null
1117
+ });
1118
+ }
1119
+ function resetIfPathChanged() {
1120
+ const newPath = window.location.pathname;
1121
+ if (newPath === currentPath) return;
1122
+ currentPath = newPath;
1123
+ firedForms = /* @__PURE__ */ new Set();
1124
+ }
1125
+ const originalPushState = history.pushState.bind(history);
1126
+ const originalReplaceState = history.replaceState.bind(history);
1127
+ function patchedPushState(...args) {
1128
+ originalPushState(...args);
1129
+ setTimeout(resetIfPathChanged, 0);
1130
+ }
1131
+ function patchedReplaceState(...args) {
1132
+ originalReplaceState(...args);
1133
+ setTimeout(resetIfPathChanged, 0);
1134
+ }
1135
+ history.pushState = patchedPushState;
1136
+ history.replaceState = patchedReplaceState;
1137
+ window.addEventListener("popstate", resetIfPathChanged);
1138
+ document.addEventListener("focusin", onFocusIn);
1139
+ return () => {
1140
+ history.pushState = originalPushState;
1141
+ history.replaceState = originalReplaceState;
1142
+ window.removeEventListener("popstate", resetIfPathChanged);
1143
+ document.removeEventListener("focusin", onFocusIn);
1144
+ };
1145
+ }
1146
+
826
1147
  // src/ConsentBanner.tsx
827
1148
  var import_jsx_runtime = require("react/jsx-runtime");
828
1149
  function ConsentBanner() {
@@ -912,7 +1233,7 @@ function useConsentState() {
912
1233
  var import_react4 = require("react");
913
1234
 
914
1235
  // package.json
915
- var version = "0.4.2";
1236
+ var version = "0.5.0";
916
1237
 
917
1238
  // src/factory.tsx
918
1239
  var import_jsx_runtime2 = require("react/jsx-runtime");
@@ -974,9 +1295,21 @@ function createTracking(options) {
974
1295
  if (timeOnSite) {
975
1296
  detachers.push(attachTimeOnSite(rawClient, timeOnSite));
976
1297
  }
977
- const contactPageVisit = triggers.automatic.contact_page_visit;
978
- if (contactPageVisit) {
979
- detachers.push(attachContactPageVisit(rawClient, contactPageVisit));
1298
+ const specificPageVisit = triggers.automatic.specific_page_visit;
1299
+ if (specificPageVisit) {
1300
+ detachers.push(attachSpecificPageVisit(rawClient, specificPageVisit));
1301
+ }
1302
+ const scrollDepth = triggers.automatic.scroll_depth;
1303
+ if (scrollDepth) {
1304
+ detachers.push(attachScrollDepth(rawClient, scrollDepth));
1305
+ }
1306
+ const multiPageSession = triggers.automatic.multi_page_session;
1307
+ if (multiPageSession) {
1308
+ detachers.push(attachMultiPageSession(rawClient, multiPageSession));
1309
+ }
1310
+ const formStart = triggers.automatic.form_start;
1311
+ if (formStart) {
1312
+ detachers.push(attachFormStart(rawClient, formStart));
980
1313
  }
981
1314
  return () => {
982
1315
  for (let i = detachers.length - 1; i >= 0; i--) {