react-global-tracking 0.1.1 → 0.1.2
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.cjs +12 -6
- package/dist/index.mjs +12 -6
- package/dist/index.mjs.map +1 -1
- package/package.json +13 -13
package/dist/index.cjs
CHANGED
|
@@ -72,7 +72,6 @@ function getFiberFromElement(element) {
|
|
|
72
72
|
if (cachedKey !== null) {
|
|
73
73
|
const fiber = element[cachedKey];
|
|
74
74
|
if (fiber != null) return fiber;
|
|
75
|
-
return null;
|
|
76
75
|
}
|
|
77
76
|
for (const key of Object.keys(element)) for (const prefix of FIBER_PREFIXES) if (key.startsWith(prefix)) {
|
|
78
77
|
cachedKey = key;
|
|
@@ -108,7 +107,7 @@ function getComponentName(fiber) {
|
|
|
108
107
|
}
|
|
109
108
|
|
|
110
109
|
//#endregion
|
|
111
|
-
//#region src/utils/safe-
|
|
110
|
+
//#region src/utils/safe-selector.ts
|
|
112
111
|
function safeMatches(element, selector) {
|
|
113
112
|
try {
|
|
114
113
|
return element.matches(selector);
|
|
@@ -116,6 +115,13 @@ function safeMatches(element, selector) {
|
|
|
116
115
|
return false;
|
|
117
116
|
}
|
|
118
117
|
}
|
|
118
|
+
function safeClosest(element, selector) {
|
|
119
|
+
try {
|
|
120
|
+
return element.closest(selector) !== null;
|
|
121
|
+
} catch {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
119
125
|
|
|
120
126
|
//#endregion
|
|
121
127
|
//#region src/filter/filter-engine.ts
|
|
@@ -158,10 +164,10 @@ function findTrackableElement(params) {
|
|
|
158
164
|
}
|
|
159
165
|
}
|
|
160
166
|
function findPointerTarget(target, ignoreSelectors, eventType) {
|
|
167
|
+
if (isIgnored(target, ignoreSelectors)) return null;
|
|
161
168
|
let current = target;
|
|
162
169
|
let depth = 0;
|
|
163
170
|
while (current !== null && depth <= MAX_ANCESTOR_DEPTH) {
|
|
164
|
-
if (isIgnored(current, ignoreSelectors)) return null;
|
|
165
171
|
if (isDisabled(current)) return null;
|
|
166
172
|
const rawFiber = findInteractiveFiber(current, eventType);
|
|
167
173
|
if (rawFiber !== void 0) return {
|
|
@@ -201,14 +207,14 @@ function findInteractiveFiber(el, eventType) {
|
|
|
201
207
|
const fiber = resolveFiber(el);
|
|
202
208
|
if (fiber !== null) {
|
|
203
209
|
const props = fiber.memoizedProps;
|
|
204
|
-
if (props
|
|
210
|
+
if (props != null) {
|
|
205
211
|
for (const handler of handlers) if (typeof props[handler] === "function") return fiber;
|
|
206
212
|
}
|
|
207
213
|
}
|
|
208
214
|
}
|
|
209
215
|
}
|
|
210
216
|
function isIgnored(element, ignoreSelectors) {
|
|
211
|
-
return ignoreSelectors.some((selector) =>
|
|
217
|
+
return ignoreSelectors.some((selector) => safeClosest(element, selector));
|
|
212
218
|
}
|
|
213
219
|
function isDisabled(el) {
|
|
214
220
|
return el.hasAttribute("disabled") || el.getAttribute("aria-disabled") === "true";
|
|
@@ -374,7 +380,7 @@ function createTracker(config) {
|
|
|
374
380
|
pipeline.handleEvent(event);
|
|
375
381
|
if (debug) {
|
|
376
382
|
const lastEvent = pipeline.getLastEvent();
|
|
377
|
-
if (lastEvent?.nativeEvent === event) console.debug("[react-
|
|
383
|
+
if (lastEvent?.nativeEvent === event) console.debug("[react-global-tracking]", lastEvent);
|
|
378
384
|
}
|
|
379
385
|
};
|
|
380
386
|
document.addEventListener(eventType, handler, true);
|
package/dist/index.mjs
CHANGED
|
@@ -70,7 +70,6 @@ function getFiberFromElement(element) {
|
|
|
70
70
|
if (cachedKey !== null) {
|
|
71
71
|
const fiber = element[cachedKey];
|
|
72
72
|
if (fiber != null) return fiber;
|
|
73
|
-
return null;
|
|
74
73
|
}
|
|
75
74
|
for (const key of Object.keys(element)) for (const prefix of FIBER_PREFIXES) if (key.startsWith(prefix)) {
|
|
76
75
|
cachedKey = key;
|
|
@@ -106,7 +105,7 @@ function getComponentName(fiber) {
|
|
|
106
105
|
}
|
|
107
106
|
|
|
108
107
|
//#endregion
|
|
109
|
-
//#region src/utils/safe-
|
|
108
|
+
//#region src/utils/safe-selector.ts
|
|
110
109
|
function safeMatches(element, selector) {
|
|
111
110
|
try {
|
|
112
111
|
return element.matches(selector);
|
|
@@ -114,6 +113,13 @@ function safeMatches(element, selector) {
|
|
|
114
113
|
return false;
|
|
115
114
|
}
|
|
116
115
|
}
|
|
116
|
+
function safeClosest(element, selector) {
|
|
117
|
+
try {
|
|
118
|
+
return element.closest(selector) !== null;
|
|
119
|
+
} catch {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
117
123
|
|
|
118
124
|
//#endregion
|
|
119
125
|
//#region src/filter/filter-engine.ts
|
|
@@ -156,10 +162,10 @@ function findTrackableElement(params) {
|
|
|
156
162
|
}
|
|
157
163
|
}
|
|
158
164
|
function findPointerTarget(target, ignoreSelectors, eventType) {
|
|
165
|
+
if (isIgnored(target, ignoreSelectors)) return null;
|
|
159
166
|
let current = target;
|
|
160
167
|
let depth = 0;
|
|
161
168
|
while (current !== null && depth <= MAX_ANCESTOR_DEPTH) {
|
|
162
|
-
if (isIgnored(current, ignoreSelectors)) return null;
|
|
163
169
|
if (isDisabled(current)) return null;
|
|
164
170
|
const rawFiber = findInteractiveFiber(current, eventType);
|
|
165
171
|
if (rawFiber !== void 0) return {
|
|
@@ -199,14 +205,14 @@ function findInteractiveFiber(el, eventType) {
|
|
|
199
205
|
const fiber = resolveFiber(el);
|
|
200
206
|
if (fiber !== null) {
|
|
201
207
|
const props = fiber.memoizedProps;
|
|
202
|
-
if (props
|
|
208
|
+
if (props != null) {
|
|
203
209
|
for (const handler of handlers) if (typeof props[handler] === "function") return fiber;
|
|
204
210
|
}
|
|
205
211
|
}
|
|
206
212
|
}
|
|
207
213
|
}
|
|
208
214
|
function isIgnored(element, ignoreSelectors) {
|
|
209
|
-
return ignoreSelectors.some((selector) =>
|
|
215
|
+
return ignoreSelectors.some((selector) => safeClosest(element, selector));
|
|
210
216
|
}
|
|
211
217
|
function isDisabled(el) {
|
|
212
218
|
return el.hasAttribute("disabled") || el.getAttribute("aria-disabled") === "true";
|
|
@@ -372,7 +378,7 @@ function createTracker(config) {
|
|
|
372
378
|
pipeline.handleEvent(event);
|
|
373
379
|
if (debug) {
|
|
374
380
|
const lastEvent = pipeline.getLastEvent();
|
|
375
|
-
if (lastEvent?.nativeEvent === event) console.debug("[react-
|
|
381
|
+
if (lastEvent?.nativeEvent === event) console.debug("[react-global-tracking]", lastEvent);
|
|
376
382
|
}
|
|
377
383
|
};
|
|
378
384
|
document.addEventListener(eventType, handler, true);
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/filter/event-categories.ts","../src/extract/fiber.ts","../src/utils/safe-matches.ts","../src/filter/filter-engine.ts","../src/utils/debounce.ts","../src/utils/throttle.ts","../src/core/registry.ts","../src/core/pipeline.ts","../src/core/tracker.ts"],"sourcesContent":["export const EventCategory = {\n Pointer: 'pointer',\n Form: 'form',\n Ambient: 'ambient',\n} as const\n\nexport type EventCategory = (typeof EventCategory)[keyof typeof EventCategory]\n\n// Maps DOM event types to React handler prop names used to detect interactivity.\n// For click, we include mouse/pointer handlers because an element with only\n// onMouseDown (no onClick) is still interactive from a tracking perspective.\nconst EVENT_HANDLER_MAP: Readonly<Record<string, readonly string[]>> = {\n // Pointer — includes related mouse/pointer handlers to catch all interactive patterns\n click: ['onClick', 'onMouseDown', 'onMouseUp', 'onPointerDown', 'onPointerUp'],\n touchstart: ['onTouchStart'],\n touchend: ['onTouchEnd'],\n\n // Form\n input: ['onChange', 'onInput'],\n change: ['onChange'],\n focus: ['onFocus'],\n blur: ['onBlur'],\n submit: ['onSubmit'],\n\n // Ambient\n scroll: ['onScroll'],\n keydown: ['onKeyDown'],\n keyup: ['onKeyUp'],\n copy: ['onCopy'],\n paste: ['onPaste'],\n}\n\nconst CATEGORY_MAP: Readonly<Record<string, EventCategory>> = {\n click: EventCategory.Pointer,\n touchstart: EventCategory.Pointer,\n touchend: EventCategory.Pointer,\n\n input: EventCategory.Form,\n change: EventCategory.Form,\n focus: EventCategory.Form,\n blur: EventCategory.Form,\n submit: EventCategory.Form,\n\n scroll: EventCategory.Ambient,\n keydown: EventCategory.Ambient,\n keyup: EventCategory.Ambient,\n copy: EventCategory.Ambient,\n paste: EventCategory.Ambient,\n resize: EventCategory.Ambient,\n popstate: EventCategory.Ambient,\n hashchange: EventCategory.Ambient,\n}\n\nexport function getEventCategory(eventType: string): EventCategory {\n return CATEGORY_MAP[eventType] ?? EventCategory.Ambient\n}\n\nexport function getHandlersForEvent(eventType: string): readonly string[] {\n return EVENT_HANDLER_MAP[eventType] ?? []\n}\n","import type { FiberInfo } from '../types'\n\n// === Resolver: DOM element → raw fiber node ===\n\nconst FIBER_PREFIXES = ['__reactFiber$', '__reactInternalInstance$'] as const\nconst MAX_PARENT_DEPTH = 10\n\nlet cachedKey: string | null = null\n\nexport function resolveFiber(element: Element): object | null {\n let current: Element | null = element\n let depth = 0\n\n while (current !== null && depth <= MAX_PARENT_DEPTH) {\n const fiber = getFiberFromElement(current)\n if (fiber !== null) {\n return fiber\n }\n current = current.parentElement\n depth++\n }\n\n return null\n}\n\nfunction getFiberFromElement(element: Element): object | null {\n if (cachedKey !== null) {\n const fiber = (element as any)[cachedKey]\n if (fiber != null) return fiber as object\n return null\n }\n\n for (const key of Object.keys(element)) {\n for (const prefix of FIBER_PREFIXES) {\n if (key.startsWith(prefix)) {\n cachedKey = key\n return (element as any)[key] as object\n }\n }\n }\n\n return null\n}\n\nexport function resetFiberKeyCache(): void {\n cachedKey = null\n}\n\n// === Extractor: raw fiber node → FiberInfo ===\n\nconst MAX_STACK_DEPTH = 50\n\ninterface FiberNode {\n type: unknown\n memoizedProps: Record<string, unknown> | null\n return: FiberNode | null\n}\n\nexport function extractFiberInfo(rawFiber: object | null): FiberInfo | null {\n if (rawFiber === null) return null\n\n const fiber = rawFiber as FiberNode\n return {\n componentName: findNearestComponentName(fiber),\n props: fiber.memoizedProps ?? {},\n }\n}\n\nfunction findNearestComponentName(fiber: FiberNode): string | null {\n let current: FiberNode | null = fiber.return\n let depth = 0\n\n while (current !== null && depth < MAX_STACK_DEPTH) {\n const name = getComponentName(current)\n if (name !== null) return name\n current = current.return\n depth++\n }\n\n return null\n}\n\nfunction getComponentName(fiber: FiberNode): string | null {\n const type = fiber.type\n if (typeof type === 'string') return null\n if (typeof type === 'function') {\n return (type as any).displayName ?? type.name ?? null\n }\n return null\n}\n","export function safeMatches(element: Element, selector: string): boolean {\n try {\n return element.matches(selector)\n } catch {\n return false\n }\n}\n","import type { FiberInfo } from '../types'\nimport { getEventCategory, getHandlersForEvent, EventCategory } from './event-categories'\nimport { resolveFiber, extractFiberInfo } from '../extract/fiber'\nimport { safeMatches } from '../utils/safe-matches'\n\nconst INTERACTIVE_TAGS = new Set([\n 'BUTTON',\n 'A',\n 'INPUT',\n 'SELECT',\n 'TEXTAREA',\n 'SUMMARY',\n 'DETAILS',\n])\n\nconst INTERACTIVE_ROLES = new Set([\n // Original widget roles\n 'button',\n 'link',\n 'menuitem',\n 'tab',\n 'checkbox',\n 'radio',\n 'combobox',\n 'listbox',\n 'option',\n 'switch',\n 'slider',\n 'spinbutton',\n // Composite widget variants\n 'menuitemcheckbox',\n 'menuitemradio',\n 'treeitem',\n 'gridcell',\n // Input widget roles\n 'textbox',\n 'searchbox',\n])\n\nconst MAX_ANCESTOR_DEPTH = 10\n\nexport interface FilterResult {\n readonly element: Element\n readonly fiber: FiberInfo | null\n}\n\ninterface FindTrackableParams {\n readonly target: Element\n readonly ignoreSelectors: readonly string[]\n readonly eventType: string\n}\n\nexport function findTrackableElement(params: FindTrackableParams): FilterResult | null {\n const { target, ignoreSelectors, eventType } = params\n const category = getEventCategory(eventType)\n\n switch (category) {\n case EventCategory.Pointer:\n return findPointerTarget(target, ignoreSelectors, eventType)\n case EventCategory.Form:\n return findFormTarget(target, ignoreSelectors)\n case EventCategory.Ambient:\n return findAmbientTarget(target, ignoreSelectors)\n }\n}\n\nfunction findPointerTarget(\n target: Element,\n ignoreSelectors: readonly string[],\n eventType: string,\n): FilterResult | null {\n let current: Element | null = target\n let depth = 0\n\n while (current !== null && depth <= MAX_ANCESTOR_DEPTH) {\n if (isIgnored(current, ignoreSelectors)) return null\n if (isDisabled(current)) return null\n\n const rawFiber = findInteractiveFiber(current, eventType)\n if (rawFiber !== undefined) {\n return { element: current, fiber: extractFiberInfo(rawFiber) }\n }\n\n current = current.parentElement\n depth++\n }\n\n return null\n}\n\nfunction findFormTarget(target: Element, ignoreSelectors: readonly string[]): FilterResult | null {\n if (isIgnored(target, ignoreSelectors)) return null\n if (isDisabled(target)) return null\n return { element: target, fiber: extractFiberInfo(resolveFiber(target)) }\n}\n\nfunction findAmbientTarget(\n target: Element,\n ignoreSelectors: readonly string[],\n): FilterResult | null {\n if (isIgnored(target, ignoreSelectors)) return null\n return { element: target, fiber: extractFiberInfo(resolveFiber(target)) }\n}\n\n/**\n * Returns the raw fiber if the element is interactive, or undefined if not.\n * null means \"interactive but no fiber found\" (semantic tag / ARIA role).\n */\nfunction findInteractiveFiber(el: Element, eventType: string): object | null | undefined {\n // 1. Semantic tag\n if (INTERACTIVE_TAGS.has(el.tagName)) return resolveFiber(el)\n\n // 2. ARIA role\n const role = el.getAttribute('role')\n if (role !== null && INTERACTIVE_ROLES.has(role)) return resolveFiber(el)\n\n // 3. React event handler via fiber\n const handlers = getHandlersForEvent(eventType)\n if (handlers.length > 0) {\n const fiber = resolveFiber(el)\n if (fiber !== null) {\n const props = (fiber as any).memoizedProps\n if (props !== null && props !== undefined) {\n for (const handler of handlers) {\n if (typeof props[handler] === 'function') return fiber\n }\n }\n }\n }\n\n return undefined\n}\n\nexport function isIgnored(element: Element, ignoreSelectors: readonly string[]): boolean {\n return ignoreSelectors.some((selector) => safeMatches(element, selector))\n}\n\nexport function isDisabled(el: Element): boolean {\n return el.hasAttribute('disabled') || el.getAttribute('aria-disabled') === 'true'\n}\n","// eslint-disable-next-line @typescript-eslint/no-explicit-any\ninterface DebouncedFn<T extends (...args: any[]) => void> {\n (...args: Parameters<T>): void\n cancel(): void\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function debounce<T extends (...args: any[]) => void>(fn: T, ms: number): DebouncedFn<T> {\n let timeoutId: ReturnType<typeof setTimeout> | null = null\n\n const debounced = (...args: Parameters<T>): void => {\n if (timeoutId !== null) {\n clearTimeout(timeoutId)\n }\n timeoutId = setTimeout(() => {\n timeoutId = null\n fn(...args)\n }, ms)\n }\n\n debounced.cancel = (): void => {\n if (timeoutId !== null) {\n clearTimeout(timeoutId)\n timeoutId = null\n }\n }\n\n return debounced\n}\n","// eslint-disable-next-line @typescript-eslint/no-explicit-any\ninterface ThrottledFn<T extends (...args: any[]) => void> {\n (...args: Parameters<T>): void\n cancel(): void\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function throttle<T extends (...args: any[]) => void>(fn: T, ms: number): ThrottledFn<T> {\n let lastCallTime = 0\n let timeoutId: ReturnType<typeof setTimeout> | null = null\n let lastArgs: Parameters<T> | null = null\n\n const throttled = (...args: Parameters<T>): void => {\n const now = Date.now()\n const elapsed = now - lastCallTime\n\n if (elapsed >= ms) {\n lastCallTime = now\n fn(...args)\n } else {\n lastArgs = args\n if (timeoutId === null) {\n timeoutId = setTimeout(() => {\n timeoutId = null\n lastCallTime = Date.now()\n if (lastArgs !== null) {\n fn(...lastArgs)\n lastArgs = null\n }\n }, ms - elapsed)\n }\n }\n }\n\n throttled.cancel = (): void => {\n if (timeoutId !== null) {\n clearTimeout(timeoutId)\n timeoutId = null\n }\n lastArgs = null\n }\n\n return throttled\n}\n","import type { TrackEvent, TrackCallback, ListenerOptions } from '../types'\nimport { debounce } from '../utils/debounce'\nimport { throttle } from '../utils/throttle'\nimport { safeMatches } from '../utils/safe-matches'\n\ninterface RegistryEntry {\n readonly eventType: string\n readonly wrappedCallback: TrackCallback & { cancel?: () => void }\n readonly options: ListenerOptions\n readonly unsubscribe: () => void\n}\n\nexport interface Registry {\n add(eventType: string, callback: TrackCallback, options: ListenerOptions): () => void\n invoke(event: TrackEvent): void\n getEventTypes(): Set<string>\n clear(): void\n}\n\nexport function createRegistry(): Registry {\n let entries: RegistryEntry[] = []\n\n function createEntry(\n eventType: string,\n callback: TrackCallback,\n options: ListenerOptions,\n ): RegistryEntry {\n const wrappedCallback = wrapCallback(callback, options)\n const entry: RegistryEntry = {\n eventType,\n wrappedCallback,\n options,\n unsubscribe: () => {\n wrappedCallback.cancel?.()\n entries = entries.filter((e) => e !== entry)\n },\n }\n return entry\n }\n\n return {\n add(eventType: string, callback: TrackCallback, options: ListenerOptions): () => void {\n const entry = createEntry(eventType, callback, options)\n entries = [...entries, entry]\n return entry.unsubscribe\n },\n\n invoke(event: TrackEvent): void {\n for (const entry of entries) {\n if (entry.eventType !== event.nativeEvent.type) continue\n\n if (entry.options.selector != null) {\n if (!safeMatches(event.targetElement, entry.options.selector)) {\n continue\n }\n }\n\n entry.wrappedCallback(event)\n\n // once: auto-unsubscribe after first fire\n if (entry.options.once === true) {\n entry.unsubscribe()\n }\n }\n },\n\n getEventTypes(): Set<string> {\n return new Set(entries.map((e) => e.eventType))\n },\n\n clear(): void {\n for (const entry of entries) {\n entry.wrappedCallback.cancel?.()\n }\n entries = []\n },\n }\n}\n\nfunction wrapCallback(\n callback: TrackCallback,\n options: ListenerOptions,\n): TrackCallback & { cancel?: () => void } {\n if (options.debounce != null) {\n return debounce(callback, options.debounce)\n }\n if (options.throttle != null) {\n return throttle(callback, options.throttle)\n }\n return callback\n}\n","import type { TrackEvent, TrackCallback, ListenerOptions } from '../types'\nimport { findTrackableElement } from '../filter/filter-engine'\nimport { createRegistry } from './registry'\n\nexport interface Pipeline {\n handleEvent(domEvent: Event): void\n getLastEvent(): TrackEvent | null\n addListener(eventType: string, callback: TrackCallback, options: ListenerOptions): () => void\n getEventTypes(): Set<string>\n clear(): void\n}\n\nexport interface PipelineConfig {\n readonly ignoreSelectors: readonly string[]\n}\n\nexport function createPipeline(config: PipelineConfig): Pipeline {\n const registry = createRegistry()\n let lastEvent: TrackEvent | null = null\n\n return {\n handleEvent(domEvent: Event): void {\n const target = domEvent.target\n if (!(target instanceof Element)) return\n\n const result = findTrackableElement({\n target,\n ignoreSelectors: config.ignoreSelectors,\n eventType: domEvent.type,\n })\n if (result === null) return\n\n const trackEvent: TrackEvent = {\n nativeEvent: domEvent,\n targetElement: result.element,\n fiber: result.fiber,\n }\n\n registry.invoke(trackEvent)\n lastEvent = trackEvent\n },\n\n getLastEvent(): TrackEvent | null {\n return lastEvent\n },\n\n addListener(eventType: string, callback: TrackCallback, options: ListenerOptions): () => void {\n return registry.add(eventType, callback, options)\n },\n\n getEventTypes(): Set<string> {\n return registry.getEventTypes()\n },\n\n clear(): void {\n registry.clear()\n },\n }\n}\n","import type { Tracker, TrackerConfig, TrackCallback, ListenerOptions } from '../types'\nimport { createPipeline } from './pipeline'\n\nexport function createTracker(config?: TrackerConfig): Tracker {\n const enabled = config?.enabled ?? true\n const ignoreSelectors = config?.ignoreSelectors ?? []\n const debug = config?.debug ?? false\n\n if (!enabled) {\n return {\n on: () => () => {},\n getLastEvent: () => null,\n destroy: () => {},\n }\n }\n\n const pipeline = createPipeline({ ignoreSelectors })\n const domListeners = new Map<string, (event: Event) => void>()\n let destroyed = false\n\n // Lazily attaches one capture-phase listener per event type on document.\n // Multiple on() calls for the same type share a single DOM listener;\n // the pipeline fans out to all registered callbacks internally.\n function ensureDomListener(eventType: string): void {\n if (domListeners.has(eventType)) return\n\n const handler = (event: Event): void => {\n pipeline.handleEvent(event)\n\n if (debug) {\n const lastEvent = pipeline.getLastEvent()\n if (lastEvent?.nativeEvent === event) {\n console.debug('[react-auto-tracking]', lastEvent)\n }\n }\n }\n\n document.addEventListener(eventType, handler, true)\n domListeners.set(eventType, handler)\n }\n\n function removeDomListener(eventType: string): void {\n const handler = domListeners.get(eventType)\n if (handler === undefined) return\n\n // Only remove if no more listeners for this type\n if (!pipeline.getEventTypes().has(eventType)) {\n document.removeEventListener(eventType, handler, true)\n domListeners.delete(eventType)\n }\n }\n\n return {\n on(eventType: string, callback: TrackCallback, options?: ListenerOptions): () => void {\n if (destroyed) return () => {}\n\n ensureDomListener(eventType)\n const unsub = pipeline.addListener(eventType, callback, options ?? {})\n\n return () => {\n unsub()\n removeDomListener(eventType)\n }\n },\n\n getLastEvent() {\n return pipeline.getLastEvent()\n },\n\n destroy(): void {\n if (destroyed) return\n destroyed = true\n\n pipeline.clear()\n\n for (const [eventType, handler] of domListeners) {\n document.removeEventListener(eventType, handler, true)\n }\n domListeners.clear()\n },\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/filter/event-categories.ts","../src/extract/fiber.ts","../src/utils/safe-selector.ts","../src/filter/filter-engine.ts","../src/utils/debounce.ts","../src/utils/throttle.ts","../src/core/registry.ts","../src/core/pipeline.ts","../src/core/tracker.ts"],"sourcesContent":["export const EventCategory = {\n Pointer: 'pointer',\n Form: 'form',\n Ambient: 'ambient',\n} as const\n\nexport type EventCategory = (typeof EventCategory)[keyof typeof EventCategory]\n\n// Maps DOM event types to React handler prop names used to detect interactivity.\n// For click, we include mouse/pointer handlers because an element with only\n// onMouseDown (no onClick) is still interactive from a tracking perspective.\nconst EVENT_HANDLER_MAP: Readonly<Record<string, readonly string[]>> = {\n // Pointer — includes related mouse/pointer handlers to catch all interactive patterns\n click: ['onClick', 'onMouseDown', 'onMouseUp', 'onPointerDown', 'onPointerUp'],\n touchstart: ['onTouchStart'],\n touchend: ['onTouchEnd'],\n\n // Form\n input: ['onChange', 'onInput'],\n change: ['onChange'],\n focus: ['onFocus'],\n blur: ['onBlur'],\n submit: ['onSubmit'],\n\n // Ambient\n scroll: ['onScroll'],\n keydown: ['onKeyDown'],\n keyup: ['onKeyUp'],\n copy: ['onCopy'],\n paste: ['onPaste'],\n}\n\nconst CATEGORY_MAP: Readonly<Record<string, EventCategory>> = {\n click: EventCategory.Pointer,\n touchstart: EventCategory.Pointer,\n touchend: EventCategory.Pointer,\n\n input: EventCategory.Form,\n change: EventCategory.Form,\n focus: EventCategory.Form,\n blur: EventCategory.Form,\n submit: EventCategory.Form,\n\n scroll: EventCategory.Ambient,\n keydown: EventCategory.Ambient,\n keyup: EventCategory.Ambient,\n copy: EventCategory.Ambient,\n paste: EventCategory.Ambient,\n resize: EventCategory.Ambient,\n popstate: EventCategory.Ambient,\n hashchange: EventCategory.Ambient,\n}\n\nexport function getEventCategory(eventType: string): EventCategory {\n return CATEGORY_MAP[eventType] ?? EventCategory.Ambient\n}\n\nexport function getHandlersForEvent(eventType: string): readonly string[] {\n return EVENT_HANDLER_MAP[eventType] ?? []\n}\n","import type { FiberInfo } from '../types'\n\n// === Resolver: DOM element → raw fiber node ===\n\nconst FIBER_PREFIXES = ['__reactFiber$', '__reactInternalInstance$'] as const\nconst MAX_PARENT_DEPTH = 10\n\nlet cachedKey: string | null = null\n\nexport function resolveFiber(element: Element): object | null {\n let current: Element | null = element\n let depth = 0\n\n while (current !== null && depth <= MAX_PARENT_DEPTH) {\n const fiber = getFiberFromElement(current)\n if (fiber !== null) {\n return fiber\n }\n current = current.parentElement\n depth++\n }\n\n return null\n}\n\nfunction getFiberFromElement(element: Element): object | null {\n if (cachedKey !== null) {\n const fiber = (element as any)[cachedKey]\n if (fiber != null) return fiber as object\n }\n\n for (const key of Object.keys(element)) {\n for (const prefix of FIBER_PREFIXES) {\n if (key.startsWith(prefix)) {\n cachedKey = key\n return (element as any)[key] as object\n }\n }\n }\n\n return null\n}\n\nexport function resetFiberKeyCache(): void {\n cachedKey = null\n}\n\n// === Extractor: raw fiber node → FiberInfo ===\n\nconst MAX_STACK_DEPTH = 50\n\ninterface FiberNode {\n type: unknown\n memoizedProps: Record<string, unknown> | null\n return: FiberNode | null\n}\n\nexport function extractFiberInfo(rawFiber: object | null): FiberInfo | null {\n if (rawFiber === null) return null\n\n const fiber = rawFiber as FiberNode\n return {\n componentName: findNearestComponentName(fiber),\n props: fiber.memoizedProps ?? {},\n }\n}\n\nfunction findNearestComponentName(fiber: FiberNode): string | null {\n let current: FiberNode | null = fiber.return\n let depth = 0\n\n while (current !== null && depth < MAX_STACK_DEPTH) {\n const name = getComponentName(current)\n if (name !== null) return name\n current = current.return\n depth++\n }\n\n return null\n}\n\nfunction getComponentName(fiber: FiberNode): string | null {\n const type = fiber.type\n if (typeof type === 'string') return null\n if (typeof type === 'function') {\n return (type as any).displayName ?? type.name ?? null\n }\n return null\n}\n","export function safeMatches(element: Element, selector: string): boolean {\n try {\n return element.matches(selector)\n } catch {\n return false\n }\n}\n\nexport function safeClosest(element: Element, selector: string): boolean {\n try {\n return element.closest(selector) !== null\n } catch {\n return false\n }\n}\n","import type { FiberInfo } from '../types'\nimport { getEventCategory, getHandlersForEvent, EventCategory } from './event-categories'\nimport { resolveFiber, extractFiberInfo } from '../extract/fiber'\nimport { safeClosest } from '../utils/safe-selector'\n\nconst INTERACTIVE_TAGS = new Set([\n 'BUTTON',\n 'A',\n 'INPUT',\n 'SELECT',\n 'TEXTAREA',\n 'SUMMARY',\n 'DETAILS',\n])\n\nconst INTERACTIVE_ROLES = new Set([\n // Original widget roles\n 'button',\n 'link',\n 'menuitem',\n 'tab',\n 'checkbox',\n 'radio',\n 'combobox',\n 'listbox',\n 'option',\n 'switch',\n 'slider',\n 'spinbutton',\n // Composite widget variants\n 'menuitemcheckbox',\n 'menuitemradio',\n 'treeitem',\n 'gridcell',\n // Input widget roles\n 'textbox',\n 'searchbox',\n])\n\nconst MAX_ANCESTOR_DEPTH = 10\n\nexport interface FilterResult {\n readonly element: Element\n readonly fiber: FiberInfo | null\n}\n\ninterface FindTrackableParams {\n readonly target: Element\n readonly ignoreSelectors: readonly string[]\n readonly eventType: string\n}\n\nexport function findTrackableElement(params: FindTrackableParams): FilterResult | null {\n const { target, ignoreSelectors, eventType } = params\n const category = getEventCategory(eventType)\n\n switch (category) {\n case EventCategory.Pointer:\n return findPointerTarget(target, ignoreSelectors, eventType)\n case EventCategory.Form:\n return findFormTarget(target, ignoreSelectors)\n case EventCategory.Ambient:\n return findAmbientTarget(target, ignoreSelectors)\n }\n}\n\nfunction findPointerTarget(\n target: Element,\n ignoreSelectors: readonly string[],\n eventType: string,\n): FilterResult | null {\n if (isIgnored(target, ignoreSelectors)) return null\n\n let current: Element | null = target\n let depth = 0\n\n while (current !== null && depth <= MAX_ANCESTOR_DEPTH) {\n if (isDisabled(current)) return null\n\n const rawFiber = findInteractiveFiber(current, eventType)\n if (rawFiber !== undefined) {\n return { element: current, fiber: extractFiberInfo(rawFiber) }\n }\n\n current = current.parentElement\n depth++\n }\n\n return null\n}\n\nfunction findFormTarget(target: Element, ignoreSelectors: readonly string[]): FilterResult | null {\n if (isIgnored(target, ignoreSelectors)) return null\n if (isDisabled(target)) return null\n return { element: target, fiber: extractFiberInfo(resolveFiber(target)) }\n}\n\nfunction findAmbientTarget(\n target: Element,\n ignoreSelectors: readonly string[],\n): FilterResult | null {\n if (isIgnored(target, ignoreSelectors)) return null\n return { element: target, fiber: extractFiberInfo(resolveFiber(target)) }\n}\n\n/**\n * Returns the raw fiber if the element is interactive, or undefined if not.\n * null means \"interactive but no fiber found\" (semantic tag / ARIA role).\n */\nfunction findInteractiveFiber(el: Element, eventType: string): object | null | undefined {\n // 1. Semantic tag\n if (INTERACTIVE_TAGS.has(el.tagName)) return resolveFiber(el)\n\n // 2. ARIA role\n const role = el.getAttribute('role')\n if (role !== null && INTERACTIVE_ROLES.has(role)) return resolveFiber(el)\n\n // 3. React event handler via fiber\n const handlers = getHandlersForEvent(eventType)\n if (handlers.length > 0) {\n const fiber = resolveFiber(el)\n if (fiber !== null) {\n const props = (fiber as any).memoizedProps\n if (props != null) {\n for (const handler of handlers) {\n if (typeof props[handler] === 'function') return fiber\n }\n }\n }\n }\n\n return undefined\n}\n\nexport function isIgnored(element: Element, ignoreSelectors: readonly string[]): boolean {\n return ignoreSelectors.some((selector) => safeClosest(element, selector))\n}\n\nexport function isDisabled(el: Element): boolean {\n return el.hasAttribute('disabled') || el.getAttribute('aria-disabled') === 'true'\n}\n","// eslint-disable-next-line @typescript-eslint/no-explicit-any\ninterface DebouncedFn<T extends (...args: any[]) => void> {\n (...args: Parameters<T>): void\n cancel(): void\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function debounce<T extends (...args: any[]) => void>(fn: T, ms: number): DebouncedFn<T> {\n let timeoutId: ReturnType<typeof setTimeout> | null = null\n\n const debounced = (...args: Parameters<T>): void => {\n if (timeoutId !== null) {\n clearTimeout(timeoutId)\n }\n timeoutId = setTimeout(() => {\n timeoutId = null\n fn(...args)\n }, ms)\n }\n\n debounced.cancel = (): void => {\n if (timeoutId !== null) {\n clearTimeout(timeoutId)\n timeoutId = null\n }\n }\n\n return debounced\n}\n","// eslint-disable-next-line @typescript-eslint/no-explicit-any\ninterface ThrottledFn<T extends (...args: any[]) => void> {\n (...args: Parameters<T>): void\n cancel(): void\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function throttle<T extends (...args: any[]) => void>(fn: T, ms: number): ThrottledFn<T> {\n let lastCallTime = 0\n let timeoutId: ReturnType<typeof setTimeout> | null = null\n let lastArgs: Parameters<T> | null = null\n\n const throttled = (...args: Parameters<T>): void => {\n const now = Date.now()\n const elapsed = now - lastCallTime\n\n if (elapsed >= ms) {\n lastCallTime = now\n fn(...args)\n } else {\n lastArgs = args\n if (timeoutId === null) {\n timeoutId = setTimeout(() => {\n timeoutId = null\n lastCallTime = Date.now()\n if (lastArgs !== null) {\n fn(...lastArgs)\n lastArgs = null\n }\n }, ms - elapsed)\n }\n }\n }\n\n throttled.cancel = (): void => {\n if (timeoutId !== null) {\n clearTimeout(timeoutId)\n timeoutId = null\n }\n lastArgs = null\n }\n\n return throttled\n}\n","import type { TrackEvent, TrackCallback, ListenerOptions } from '../types'\nimport { debounce } from '../utils/debounce'\nimport { throttle } from '../utils/throttle'\nimport { safeMatches } from '../utils/safe-selector'\n\ninterface RegistryEntry {\n readonly eventType: string\n readonly wrappedCallback: TrackCallback & { cancel?: () => void }\n readonly options: ListenerOptions\n readonly unsubscribe: () => void\n}\n\nexport interface Registry {\n add(eventType: string, callback: TrackCallback, options: ListenerOptions): () => void\n invoke(event: TrackEvent): void\n getEventTypes(): Set<string>\n clear(): void\n}\n\nexport function createRegistry(): Registry {\n let entries: RegistryEntry[] = []\n\n function createEntry(\n eventType: string,\n callback: TrackCallback,\n options: ListenerOptions,\n ): RegistryEntry {\n const wrappedCallback = wrapCallback(callback, options)\n const entry: RegistryEntry = {\n eventType,\n wrappedCallback,\n options,\n unsubscribe: () => {\n wrappedCallback.cancel?.()\n entries = entries.filter((e) => e !== entry)\n },\n }\n return entry\n }\n\n return {\n add(eventType: string, callback: TrackCallback, options: ListenerOptions): () => void {\n const entry = createEntry(eventType, callback, options)\n entries = [...entries, entry]\n return entry.unsubscribe\n },\n\n invoke(event: TrackEvent): void {\n for (const entry of entries) {\n if (entry.eventType !== event.nativeEvent.type) continue\n\n if (entry.options.selector != null) {\n if (!safeMatches(event.targetElement, entry.options.selector)) {\n continue\n }\n }\n\n entry.wrappedCallback(event)\n\n // once: auto-unsubscribe after first fire\n if (entry.options.once === true) {\n entry.unsubscribe()\n }\n }\n },\n\n getEventTypes(): Set<string> {\n return new Set(entries.map((e) => e.eventType))\n },\n\n clear(): void {\n for (const entry of entries) {\n entry.wrappedCallback.cancel?.()\n }\n entries = []\n },\n }\n}\n\nfunction wrapCallback(\n callback: TrackCallback,\n options: ListenerOptions,\n): TrackCallback & { cancel?: () => void } {\n if (options.debounce != null) {\n return debounce(callback, options.debounce)\n }\n if (options.throttle != null) {\n return throttle(callback, options.throttle)\n }\n return callback\n}\n","import type { TrackEvent, TrackCallback, ListenerOptions } from '../types'\nimport { findTrackableElement } from '../filter/filter-engine'\nimport { createRegistry } from './registry'\n\nexport interface Pipeline {\n handleEvent(domEvent: Event): void\n getLastEvent(): TrackEvent | null\n addListener(eventType: string, callback: TrackCallback, options: ListenerOptions): () => void\n getEventTypes(): Set<string>\n clear(): void\n}\n\nexport interface PipelineConfig {\n readonly ignoreSelectors: readonly string[]\n}\n\nexport function createPipeline(config: PipelineConfig): Pipeline {\n const registry = createRegistry()\n let lastEvent: TrackEvent | null = null\n\n return {\n handleEvent(domEvent: Event): void {\n const target = domEvent.target\n if (!(target instanceof Element)) return\n\n const result = findTrackableElement({\n target,\n ignoreSelectors: config.ignoreSelectors,\n eventType: domEvent.type,\n })\n if (result === null) return\n\n const trackEvent: TrackEvent = {\n nativeEvent: domEvent,\n targetElement: result.element,\n fiber: result.fiber,\n }\n\n registry.invoke(trackEvent)\n lastEvent = trackEvent\n },\n\n getLastEvent(): TrackEvent | null {\n return lastEvent\n },\n\n addListener(eventType: string, callback: TrackCallback, options: ListenerOptions): () => void {\n return registry.add(eventType, callback, options)\n },\n\n getEventTypes(): Set<string> {\n return registry.getEventTypes()\n },\n\n clear(): void {\n registry.clear()\n },\n }\n}\n","import type { Tracker, TrackerConfig, TrackCallback, ListenerOptions } from '../types'\nimport { createPipeline } from './pipeline'\n\nexport function createTracker(config?: TrackerConfig): Tracker {\n const enabled = config?.enabled ?? true\n const ignoreSelectors = config?.ignoreSelectors ?? []\n const debug = config?.debug ?? false\n\n if (!enabled) {\n return {\n on: () => () => {},\n getLastEvent: () => null,\n destroy: () => {},\n }\n }\n\n const pipeline = createPipeline({ ignoreSelectors })\n const domListeners = new Map<string, (event: Event) => void>()\n let destroyed = false\n\n // Lazily attaches one capture-phase listener per event type on document.\n // Multiple on() calls for the same type share a single DOM listener;\n // the pipeline fans out to all registered callbacks internally.\n function ensureDomListener(eventType: string): void {\n if (domListeners.has(eventType)) return\n\n const handler = (event: Event): void => {\n pipeline.handleEvent(event)\n\n if (debug) {\n const lastEvent = pipeline.getLastEvent()\n if (lastEvent?.nativeEvent === event) {\n console.debug('[react-global-tracking]', lastEvent)\n }\n }\n }\n\n document.addEventListener(eventType, handler, true)\n domListeners.set(eventType, handler)\n }\n\n function removeDomListener(eventType: string): void {\n const handler = domListeners.get(eventType)\n if (handler === undefined) return\n\n // Only remove if no more listeners for this type\n if (!pipeline.getEventTypes().has(eventType)) {\n document.removeEventListener(eventType, handler, true)\n domListeners.delete(eventType)\n }\n }\n\n return {\n on(eventType: string, callback: TrackCallback, options?: ListenerOptions): () => void {\n if (destroyed) return () => {}\n\n ensureDomListener(eventType)\n const unsub = pipeline.addListener(eventType, callback, options ?? {})\n\n return () => {\n unsub()\n removeDomListener(eventType)\n }\n },\n\n getLastEvent() {\n return pipeline.getLastEvent()\n },\n\n destroy(): void {\n if (destroyed) return\n destroyed = true\n\n pipeline.clear()\n\n for (const [eventType, handler] of domListeners) {\n document.removeEventListener(eventType, handler, true)\n }\n domListeners.clear()\n },\n }\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-global-tracking",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Global user interaction tracking for React.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -21,17 +21,6 @@
|
|
|
21
21
|
"files": [
|
|
22
22
|
"dist"
|
|
23
23
|
],
|
|
24
|
-
"scripts": {
|
|
25
|
-
"build": "tsdown",
|
|
26
|
-
"test": "vitest run",
|
|
27
|
-
"test:watch": "vitest",
|
|
28
|
-
"test:coverage": "vitest run --coverage",
|
|
29
|
-
"lint": "oxlint src/",
|
|
30
|
-
"format": "oxfmt --write src/",
|
|
31
|
-
"format:check": "oxfmt --check src/",
|
|
32
|
-
"typecheck": "tsc --noEmit",
|
|
33
|
-
"check": "pnpm lint && pnpm format:check && pnpm typecheck"
|
|
34
|
-
},
|
|
35
24
|
"simple-git-hooks": {
|
|
36
25
|
"pre-push": "pnpm check"
|
|
37
26
|
},
|
|
@@ -60,5 +49,16 @@
|
|
|
60
49
|
"tsdown": "^0.20.3",
|
|
61
50
|
"typescript": "^5.9.3",
|
|
62
51
|
"vitest": "^4.0.18"
|
|
52
|
+
},
|
|
53
|
+
"scripts": {
|
|
54
|
+
"build": "tsdown",
|
|
55
|
+
"test": "vitest run",
|
|
56
|
+
"test:watch": "vitest",
|
|
57
|
+
"test:coverage": "vitest run --coverage",
|
|
58
|
+
"lint": "oxlint src/",
|
|
59
|
+
"format": "oxfmt --write src/",
|
|
60
|
+
"format:check": "oxfmt --check src/",
|
|
61
|
+
"typecheck": "tsc --noEmit",
|
|
62
|
+
"check": "pnpm lint && pnpm format:check && pnpm typecheck"
|
|
63
63
|
}
|
|
64
|
-
}
|
|
64
|
+
}
|