@arthurreira/analytics 0.21.0 → 0.22.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/client.js CHANGED
@@ -41,6 +41,12 @@ function getConnectionType() {
41
41
  const nav = navigator;
42
42
  return nav.connection?.effectiveType ?? null;
43
43
  }
44
+ function resolveInteractiveTarget(el) {
45
+ const interactive = el.closest(
46
+ 'a, button, [role="button"], input, select, textarea, label, summary'
47
+ );
48
+ return interactive ?? el;
49
+ }
44
50
  function resolveElementHref(element) {
45
51
  if (element.tagName.toLowerCase() === "a") {
46
52
  return element.href || null;
@@ -48,6 +54,15 @@ function resolveElementHref(element) {
48
54
  const anchor = element.closest("a");
49
55
  return anchor ? anchor.href || null : null;
50
56
  }
57
+ function safeClassName(el) {
58
+ const cn = el.className;
59
+ return typeof cn === "string" ? cn || null : null;
60
+ }
61
+ function cleanLabel(raw, maxLen) {
62
+ if (!raw) return null;
63
+ const cleaned = raw.replace(/\s+/g, " ").trim().slice(0, maxLen);
64
+ return cleaned || null;
65
+ }
51
66
  function isExternalHref(href) {
52
67
  if (!href) return null;
53
68
  if (typeof window === "undefined") return null;
@@ -135,23 +150,24 @@ async function trackPageview(apiUrl, apiKey, sessionId, path, routeName) {
135
150
  });
136
151
  }
137
152
  async function trackClick(apiUrl, apiKey, sessionId, path, e, element) {
138
- const href = resolveElementHref(element);
139
- const ariaLabel = element.getAttribute("aria-label");
140
- const text = element.innerText?.slice(0, 60);
141
- const eventName = ariaLabel || text || element.tagName.toLowerCase();
153
+ const target = resolveInteractiveTarget(element);
154
+ const href = resolveElementHref(target);
155
+ const ariaLabel = target.getAttribute("aria-label");
156
+ const rawText = target.innerText ?? target.textContent ?? void 0;
157
+ const eventName = cleanLabel(ariaLabel, 60) ?? cleanLabel(rawText, 60) ?? target.tagName.toLowerCase();
142
158
  await sendEvent(apiUrl, apiKey, {
143
159
  ...BASE_FIELDS(sessionId, "click", path, eventName, "interaction"),
144
160
  x_position: e.clientX,
145
161
  y_position: e.clientY,
146
- element_id: element.id || null,
147
- element_class: element.className || null,
148
- element_text: element.innerText?.slice(0, 100) || null,
149
- tag_name: element.tagName.toLowerCase(),
162
+ element_id: target.id || null,
163
+ element_class: safeClassName(target),
164
+ element_text: cleanLabel(rawText, 100),
165
+ tag_name: target.tagName.toLowerCase(),
150
166
  element_href: href,
151
- element_role: element.getAttribute("role") || null,
152
- element_aria_label: element.getAttribute("aria-label") || null,
153
- element_type: element.type || null,
154
- element_name: element.name || null,
167
+ element_role: target.getAttribute("role") || null,
168
+ element_aria_label: ariaLabel || null,
169
+ element_type: target.type || null,
170
+ element_name: target.name || null,
155
171
  is_external_link: isExternalHref(href),
156
172
  modifier_keys: {
157
173
  alt: e.altKey,
@@ -542,6 +558,10 @@ function Analytics({ apiKey, apiUrl, wsUrl, routeName }) {
542
558
  const { trackPageview: trackPageview2, trackClick: trackClick2, trackScroll: trackScroll2, trackCopy: trackCopy2, trackException: trackException2, trackWebVital: trackWebVital2 } = useAnalytics(apiUrl, apiKey, pathname, wsUrl, routeName);
543
559
  const lastTracked = useRef2(null);
544
560
  const lastScrollDepth = useRef2(0);
561
+ const trackWebVitalRef = useRef2(trackWebVital2);
562
+ useEffect2(() => {
563
+ trackWebVitalRef.current = trackWebVital2;
564
+ });
545
565
  useEffect2(() => {
546
566
  if (lastTracked.current === pathname) return;
547
567
  lastTracked.current = pathname;
@@ -551,14 +571,14 @@ function Analytics({ apiKey, apiUrl, wsUrl, routeName }) {
551
571
  useEffect2(() => {
552
572
  if (typeof window === "undefined") return;
553
573
  const report = (name, value, rating) => {
554
- trackWebVital2({ name, value, rating });
574
+ trackWebVitalRef.current({ name, value, rating });
555
575
  };
556
576
  onLCP((m) => report("lcp_ms", Math.round(m.value), m.rating));
557
577
  onCLS((m) => report("cls_score", m.value, m.rating));
558
578
  onINP((m) => report("inp_ms", Math.round(m.value), m.rating));
559
579
  onTTFB((m) => report("ttfb_ms", Math.round(m.value), m.rating));
560
580
  onFCP((m) => report("fcp_ms", Math.round(m.value), m.rating));
561
- }, [trackWebVital2]);
581
+ }, []);
562
582
  useEffect2(() => {
563
583
  if (typeof window === "undefined") return;
564
584
  const handler = (e) => trackClick2(e, e.target);
package/dist/index.js CHANGED
@@ -42,6 +42,12 @@ function getConnectionType() {
42
42
  const nav = navigator;
43
43
  return nav.connection?.effectiveType ?? null;
44
44
  }
45
+ function resolveInteractiveTarget(el) {
46
+ const interactive = el.closest(
47
+ 'a, button, [role="button"], input, select, textarea, label, summary'
48
+ );
49
+ return interactive ?? el;
50
+ }
45
51
  function resolveElementHref(element) {
46
52
  if (element.tagName.toLowerCase() === "a") {
47
53
  return element.href || null;
@@ -49,6 +55,15 @@ function resolveElementHref(element) {
49
55
  const anchor = element.closest("a");
50
56
  return anchor ? anchor.href || null : null;
51
57
  }
58
+ function safeClassName(el) {
59
+ const cn = el.className;
60
+ return typeof cn === "string" ? cn || null : null;
61
+ }
62
+ function cleanLabel(raw, maxLen) {
63
+ if (!raw) return null;
64
+ const cleaned = raw.replace(/\s+/g, " ").trim().slice(0, maxLen);
65
+ return cleaned || null;
66
+ }
52
67
  function isExternalHref(href) {
53
68
  if (!href) return null;
54
69
  if (typeof window === "undefined") return null;
@@ -136,23 +151,24 @@ async function trackPageview(apiUrl, apiKey, sessionId, path, routeName) {
136
151
  });
137
152
  }
138
153
  async function trackClick(apiUrl, apiKey, sessionId, path, e, element) {
139
- const href = resolveElementHref(element);
140
- const ariaLabel = element.getAttribute("aria-label");
141
- const text = element.innerText?.slice(0, 60);
142
- const eventName = ariaLabel || text || element.tagName.toLowerCase();
154
+ const target = resolveInteractiveTarget(element);
155
+ const href = resolveElementHref(target);
156
+ const ariaLabel = target.getAttribute("aria-label");
157
+ const rawText = target.innerText ?? target.textContent ?? void 0;
158
+ const eventName = cleanLabel(ariaLabel, 60) ?? cleanLabel(rawText, 60) ?? target.tagName.toLowerCase();
143
159
  await sendEvent(apiUrl, apiKey, {
144
160
  ...BASE_FIELDS(sessionId, "click", path, eventName, "interaction"),
145
161
  x_position: e.clientX,
146
162
  y_position: e.clientY,
147
- element_id: element.id || null,
148
- element_class: element.className || null,
149
- element_text: element.innerText?.slice(0, 100) || null,
150
- tag_name: element.tagName.toLowerCase(),
163
+ element_id: target.id || null,
164
+ element_class: safeClassName(target),
165
+ element_text: cleanLabel(rawText, 100),
166
+ tag_name: target.tagName.toLowerCase(),
151
167
  element_href: href,
152
- element_role: element.getAttribute("role") || null,
153
- element_aria_label: element.getAttribute("aria-label") || null,
154
- element_type: element.type || null,
155
- element_name: element.name || null,
168
+ element_role: target.getAttribute("role") || null,
169
+ element_aria_label: ariaLabel || null,
170
+ element_type: target.type || null,
171
+ element_name: target.name || null,
156
172
  is_external_link: isExternalHref(href),
157
173
  modifier_keys: {
158
174
  alt: e.altKey,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arthurreira/analytics",
3
- "version": "0.21.0",
3
+ "version": "0.22.0",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "build": "tsup",