@mcp-fe/react-event-tracker 0.0.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/.babelrc +12 -0
- package/README.md +7 -0
- package/eslint.config.mjs +12 -0
- package/package.json +15 -0
- package/project.json +9 -0
- package/src/hooks/useReactRouterEventTracker.ts +97 -0
- package/src/hooks/useTanstackRouterEventTracker.ts +97 -0
- package/src/index.ts +2 -0
- package/tsconfig.json +22 -0
- package/tsconfig.lib.json +36 -0
- package/tsconfig.spec.json +28 -0
- package/vite.config.mts +61 -0
package/.babelrc
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import nx from '@nx/eslint-plugin';
|
|
2
|
+
import baseConfig from '../../eslint.config.mjs';
|
|
3
|
+
|
|
4
|
+
export default [
|
|
5
|
+
...baseConfig,
|
|
6
|
+
...nx.configs['flat/react'],
|
|
7
|
+
{
|
|
8
|
+
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
|
|
9
|
+
// Override or add rules here
|
|
10
|
+
rules: {},
|
|
11
|
+
},
|
|
12
|
+
];
|
package/package.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mcp-fe/react-event-tracker",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"main": "./index.js",
|
|
5
|
+
"types": "./index.d.ts",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"import": "./index.mjs",
|
|
9
|
+
"require": "./index.js"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"publishConfig": {
|
|
13
|
+
"access": "public"
|
|
14
|
+
}
|
|
15
|
+
}
|
package/project.json
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "react-event-tracker",
|
|
3
|
+
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
|
4
|
+
"sourceRoot": "libs/react-event-tracker/src",
|
|
5
|
+
"projectType": "library",
|
|
6
|
+
"tags": [],
|
|
7
|
+
"// targets": "to see all targets run: nx show project react-event-tracker --web",
|
|
8
|
+
"targets": {}
|
|
9
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { useLocation } from 'react-router-dom';
|
|
3
|
+
import {
|
|
4
|
+
initEventTracker,
|
|
5
|
+
trackNavigation,
|
|
6
|
+
trackClick,
|
|
7
|
+
trackInput,
|
|
8
|
+
} from '@mcp-fe/event-tracker';
|
|
9
|
+
|
|
10
|
+
export function useReactRouterEventTracker(): void {
|
|
11
|
+
const location = useLocation();
|
|
12
|
+
const [isInitialized, setIsInitialized] = useState(false);
|
|
13
|
+
|
|
14
|
+
const currentPathRef = useRef(location.pathname);
|
|
15
|
+
const prevPathRef = useRef<string | null>(null);
|
|
16
|
+
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
prevPathRef.current = currentPathRef.current;
|
|
19
|
+
currentPathRef.current = location.pathname;
|
|
20
|
+
}, [location.pathname]);
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
initEventTracker()
|
|
24
|
+
.then(() => {
|
|
25
|
+
setIsInitialized(true);
|
|
26
|
+
})
|
|
27
|
+
.catch((error) => {
|
|
28
|
+
console.error('Worker initialization failed:', error);
|
|
29
|
+
});
|
|
30
|
+
}, []);
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
if (!isInitialized) return;
|
|
34
|
+
|
|
35
|
+
const prevPath = prevPathRef.current;
|
|
36
|
+
const currentPath = currentPathRef.current;
|
|
37
|
+
|
|
38
|
+
if (prevPath && prevPath !== currentPath) {
|
|
39
|
+
trackNavigation(prevPath, currentPath, currentPath).catch((error) => {
|
|
40
|
+
console.error('Failed to track navigation:', error);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}, [location.pathname, isInitialized]);
|
|
44
|
+
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (!isInitialized) return;
|
|
47
|
+
|
|
48
|
+
const handleClick = (event: MouseEvent): void => {
|
|
49
|
+
const target = event.target as HTMLElement;
|
|
50
|
+
if (!target) return;
|
|
51
|
+
|
|
52
|
+
const isInteractive =
|
|
53
|
+
target.tagName === 'BUTTON' ||
|
|
54
|
+
target.tagName === 'A' ||
|
|
55
|
+
target.closest('button') ||
|
|
56
|
+
target.closest('a');
|
|
57
|
+
|
|
58
|
+
if (!isInteractive) {
|
|
59
|
+
trackClick(target, currentPathRef.current).catch((error) => {
|
|
60
|
+
console.error('Failed to track click:', error);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
document.addEventListener('click', handleClick, true);
|
|
66
|
+
return () => document.removeEventListener('click', handleClick, true);
|
|
67
|
+
}, [isInitialized]);
|
|
68
|
+
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
if (!isInitialized) return;
|
|
71
|
+
|
|
72
|
+
let timeoutId: ReturnType<typeof setTimeout>;
|
|
73
|
+
|
|
74
|
+
const handleInput = (event: Event): void => {
|
|
75
|
+
const target = event.target as HTMLInputElement | HTMLTextAreaElement;
|
|
76
|
+
if (
|
|
77
|
+
target &&
|
|
78
|
+
(target.tagName === 'INPUT' || target.tagName === 'TEXTAREA')
|
|
79
|
+
) {
|
|
80
|
+
clearTimeout(timeoutId);
|
|
81
|
+
timeoutId = setTimeout(() => {
|
|
82
|
+
trackInput(target, target.value, currentPathRef.current).catch(
|
|
83
|
+
(error) => {
|
|
84
|
+
console.error('Failed to track input:', error);
|
|
85
|
+
},
|
|
86
|
+
);
|
|
87
|
+
}, 1000); // Debounce by 1 second
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
document.addEventListener('input', handleInput, true);
|
|
92
|
+
return () => {
|
|
93
|
+
clearTimeout(timeoutId);
|
|
94
|
+
document.removeEventListener('input', handleInput, true);
|
|
95
|
+
};
|
|
96
|
+
}, [isInitialized]);
|
|
97
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { useLocation } from '@tanstack/react-router';
|
|
3
|
+
import {
|
|
4
|
+
initEventTracker,
|
|
5
|
+
trackNavigation,
|
|
6
|
+
trackClick,
|
|
7
|
+
trackInput,
|
|
8
|
+
} from '@mcp-fe/event-tracker';
|
|
9
|
+
|
|
10
|
+
export function useTanstackRouterEventTracker(): void {
|
|
11
|
+
const location = useLocation();
|
|
12
|
+
const [isInitialized, setIsInitialized] = useState(false);
|
|
13
|
+
|
|
14
|
+
const currentPathRef = useRef(location.pathname);
|
|
15
|
+
const prevPathRef = useRef<string | null>(null);
|
|
16
|
+
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
if (currentPathRef.current !== location.pathname) {
|
|
19
|
+
prevPathRef.current = currentPathRef.current;
|
|
20
|
+
currentPathRef.current = location.pathname;
|
|
21
|
+
}
|
|
22
|
+
}, [location.pathname]);
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
initEventTracker()
|
|
26
|
+
.then(() => setIsInitialized(true))
|
|
27
|
+
.catch((error) => {
|
|
28
|
+
console.error('Worker initialization failed:', error);
|
|
29
|
+
});
|
|
30
|
+
}, []);
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
if (!isInitialized) return;
|
|
34
|
+
|
|
35
|
+
const prevPath = prevPathRef.current;
|
|
36
|
+
const currentPath = currentPathRef.current;
|
|
37
|
+
|
|
38
|
+
if (prevPath && prevPath !== currentPath) {
|
|
39
|
+
trackNavigation(prevPath, currentPath, currentPath).catch((error) => {
|
|
40
|
+
console.error('Failed to track navigation:', error);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}, [location.pathname, isInitialized]);
|
|
44
|
+
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (!isInitialized) return;
|
|
47
|
+
|
|
48
|
+
const handleClick = (event: MouseEvent): void => {
|
|
49
|
+
const target = event.target as HTMLElement;
|
|
50
|
+
if (!target) return;
|
|
51
|
+
|
|
52
|
+
const isInteractive =
|
|
53
|
+
target.tagName === 'BUTTON' ||
|
|
54
|
+
target.tagName === 'A' ||
|
|
55
|
+
target.closest('button') ||
|
|
56
|
+
target.closest('a');
|
|
57
|
+
|
|
58
|
+
if (!isInteractive) {
|
|
59
|
+
trackClick(target, currentPathRef.current).catch((error) => {
|
|
60
|
+
console.error('Failed to track click:', error);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
document.addEventListener('click', handleClick, true);
|
|
66
|
+
return () => document.removeEventListener('click', handleClick, true);
|
|
67
|
+
}, [isInitialized]);
|
|
68
|
+
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
if (!isInitialized) return;
|
|
71
|
+
|
|
72
|
+
let timeoutId: ReturnType<typeof setTimeout>;
|
|
73
|
+
|
|
74
|
+
const handleInput = (event: Event): void => {
|
|
75
|
+
const target = event.target as HTMLInputElement | HTMLTextAreaElement;
|
|
76
|
+
if (
|
|
77
|
+
target &&
|
|
78
|
+
(target.tagName === 'INPUT' || target.tagName === 'TEXTAREA')
|
|
79
|
+
) {
|
|
80
|
+
clearTimeout(timeoutId);
|
|
81
|
+
timeoutId = setTimeout(() => {
|
|
82
|
+
trackInput(target, target.value, currentPathRef.current).catch(
|
|
83
|
+
(error) => {
|
|
84
|
+
console.error('Failed to track input:', error);
|
|
85
|
+
},
|
|
86
|
+
);
|
|
87
|
+
}, 1000);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
document.addEventListener('input', handleInput, true);
|
|
92
|
+
return () => {
|
|
93
|
+
clearTimeout(timeoutId);
|
|
94
|
+
document.removeEventListener('input', handleInput, true);
|
|
95
|
+
};
|
|
96
|
+
}, [isInitialized]);
|
|
97
|
+
}
|
package/src/index.ts
ADDED
package/tsconfig.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"jsx": "react-jsx",
|
|
4
|
+
"allowJs": false,
|
|
5
|
+
"esModuleInterop": false,
|
|
6
|
+
"allowSyntheticDefaultImports": true,
|
|
7
|
+
"strict": true,
|
|
8
|
+
"types": ["vite/client", "vitest"],
|
|
9
|
+
"lib": ["dom", "esnext"]
|
|
10
|
+
},
|
|
11
|
+
"files": [],
|
|
12
|
+
"include": [],
|
|
13
|
+
"references": [
|
|
14
|
+
{
|
|
15
|
+
"path": "./tsconfig.lib.json"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"path": "./tsconfig.spec.json"
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"extends": "../../tsconfig.base.json"
|
|
22
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "./tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"outDir": "../../dist/out-tsc",
|
|
5
|
+
"types": [
|
|
6
|
+
"node",
|
|
7
|
+
"@nx/react/typings/cssmodule.d.ts",
|
|
8
|
+
"@nx/react/typings/image.d.ts",
|
|
9
|
+
"vite/client"
|
|
10
|
+
]
|
|
11
|
+
},
|
|
12
|
+
"exclude": [
|
|
13
|
+
"**/*.spec.ts",
|
|
14
|
+
"**/*.test.ts",
|
|
15
|
+
"**/*.spec.tsx",
|
|
16
|
+
"**/*.test.tsx",
|
|
17
|
+
"**/*.spec.js",
|
|
18
|
+
"**/*.test.js",
|
|
19
|
+
"**/*.spec.jsx",
|
|
20
|
+
"**/*.test.jsx",
|
|
21
|
+
"vite.config.ts",
|
|
22
|
+
"vite.config.mts",
|
|
23
|
+
"vitest.config.ts",
|
|
24
|
+
"vitest.config.mts",
|
|
25
|
+
"src/**/*.test.ts",
|
|
26
|
+
"src/**/*.spec.ts",
|
|
27
|
+
"src/**/*.test.tsx",
|
|
28
|
+
"src/**/*.spec.tsx",
|
|
29
|
+
"src/**/*.test.js",
|
|
30
|
+
"src/**/*.spec.js",
|
|
31
|
+
"src/**/*.test.jsx",
|
|
32
|
+
"src/**/*.spec.jsx"
|
|
33
|
+
],
|
|
34
|
+
"include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.tsx"],
|
|
35
|
+
"references": []
|
|
36
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "./tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"outDir": "../../dist/out-tsc",
|
|
5
|
+
"types": [
|
|
6
|
+
"vitest/globals",
|
|
7
|
+
"vitest/importMeta",
|
|
8
|
+
"vite/client",
|
|
9
|
+
"node",
|
|
10
|
+
"vitest"
|
|
11
|
+
]
|
|
12
|
+
},
|
|
13
|
+
"include": [
|
|
14
|
+
"vite.config.ts",
|
|
15
|
+
"vite.config.mts",
|
|
16
|
+
"vitest.config.ts",
|
|
17
|
+
"vitest.config.mts",
|
|
18
|
+
"src/**/*.test.ts",
|
|
19
|
+
"src/**/*.spec.ts",
|
|
20
|
+
"src/**/*.test.tsx",
|
|
21
|
+
"src/**/*.spec.tsx",
|
|
22
|
+
"src/**/*.test.js",
|
|
23
|
+
"src/**/*.spec.js",
|
|
24
|
+
"src/**/*.test.jsx",
|
|
25
|
+
"src/**/*.spec.jsx",
|
|
26
|
+
"src/**/*.d.ts"
|
|
27
|
+
]
|
|
28
|
+
}
|
package/vite.config.mts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/// <reference types='vitest' />
|
|
2
|
+
import { defineConfig } from 'vite';
|
|
3
|
+
import react from '@vitejs/plugin-react';
|
|
4
|
+
import dts from 'vite-plugin-dts';
|
|
5
|
+
import * as path from 'path';
|
|
6
|
+
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
|
7
|
+
import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
|
|
8
|
+
|
|
9
|
+
export default defineConfig(() => ({
|
|
10
|
+
root: import.meta.dirname,
|
|
11
|
+
cacheDir: '../../node_modules/.vite/libs/react-event-tracker',
|
|
12
|
+
plugins: [
|
|
13
|
+
react(),
|
|
14
|
+
nxViteTsPaths(),
|
|
15
|
+
nxCopyAssetsPlugin(['*.md']),
|
|
16
|
+
dts({
|
|
17
|
+
entryRoot: 'src',
|
|
18
|
+
tsconfigPath: path.join(import.meta.dirname, 'tsconfig.lib.json'),
|
|
19
|
+
pathsToAliases: false,
|
|
20
|
+
}),
|
|
21
|
+
],
|
|
22
|
+
// Uncomment this if you are using workers.
|
|
23
|
+
// worker: {
|
|
24
|
+
// plugins: () => [ nxViteTsPaths() ],
|
|
25
|
+
// },
|
|
26
|
+
// Configuration for building your library.
|
|
27
|
+
// See: https://vite.dev/guide/build.html#library-mode
|
|
28
|
+
build: {
|
|
29
|
+
outDir: '../../dist/libs/react-event-tracker',
|
|
30
|
+
emptyOutDir: true,
|
|
31
|
+
reportCompressedSize: true,
|
|
32
|
+
commonjsOptions: {
|
|
33
|
+
transformMixedEsModules: true,
|
|
34
|
+
},
|
|
35
|
+
lib: {
|
|
36
|
+
// Could also be a dictionary or array of multiple entry points.
|
|
37
|
+
entry: 'src/index.ts',
|
|
38
|
+
name: 'react-event-tracker',
|
|
39
|
+
fileName: 'index',
|
|
40
|
+
// Change this to the formats you want to support.
|
|
41
|
+
// Don't forget to update your package.json as well.
|
|
42
|
+
formats: ['es' as const],
|
|
43
|
+
},
|
|
44
|
+
rollupOptions: {
|
|
45
|
+
// External packages that should not be bundled into your library.
|
|
46
|
+
external: ['react', 'react-dom', 'react/jsx-runtime'],
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
test: {
|
|
50
|
+
name: 'react-event-tracker',
|
|
51
|
+
watch: false,
|
|
52
|
+
globals: true,
|
|
53
|
+
environment: 'jsdom',
|
|
54
|
+
include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
|
|
55
|
+
reporters: ['default'],
|
|
56
|
+
coverage: {
|
|
57
|
+
reportsDirectory: '../../coverage/libs/react-event-tracker',
|
|
58
|
+
provider: 'v8' as const,
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
}));
|