{"_id":"@absmartly/dom-tracker","_rev":"4-b6bf199128d67b3031deb481873ea248","name":"@absmartly/dom-tracker","dist-tags":{"latest":"0.5.0"},"versions":{"0.1.0":{"name":"@absmartly/dom-tracker","version":"0.1.0","keywords":["analytics","tracking","dom","data-attributes","events"],"author":{"name":"ABsmartly"},"license":"MIT","_id":"@absmartly/dom-tracker@0.1.0","maintainers":[{"name":"marcioapm","email":"marcioapm@gmail.com"},{"name":"joalves","email":"jonas@absmartly.com"},{"name":"caltheabtester","email":"cal@absmartly.com"},{"name":"marcioabs","email":"marcio@absmartly.com"}],"dist":{"shasum":"75735394218065658a479a2f6ee1f02c9f28acf4","tarball":"https://registry.npmjs.org/@absmartly/dom-tracker/-/dom-tracker-0.1.0.tgz","fileCount":99,"integrity":"sha512-fWYBmop5rby0Pzdhm1Qpc902i7IksorifOFoDzOJUjh2JbpWIKppBz9TLFnihKyPH+UGJ/nU5Hj62wFe6Fm4Vg==","signatures":[{"sig":"MEUCIBb4m1shIOH9ov2wiMVrISNQmSCRya0yOxR3fQZzxlKhAiEA/Y6fvjKTrls7MD/qwzk06f/UQfm/9WYgFj+bPa7zR1A=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":282698},"main":"lib/index.js","types":"types/index.d.ts","module":"es/index.js","browser":"dist/dom-tracker.min.js","exports":{".":{"types":"./types/index.d.ts","import":"./es/index.js","source":"./src/index.ts","require":"./lib/index.js"},"./presets":{"types":"./types/presets/index.d.ts","import":"./es/presets/index.js","source":"./src/presets/index.ts","require":"./lib/presets/index.js"},"./trackers/time":{"types":"./types/trackers/time.d.ts","import":"./es/trackers/time.js","source":"./src/trackers/time.ts","require":"./lib/trackers/time.js"},"./trackers/forms":{"types":"./types/trackers/forms.d.ts","import":"./es/trackers/forms.js","source":"./src/trackers/forms.ts","require":"./lib/trackers/forms.js"},"./presets/hubspot":{"types":"./types/presets/hubspot.d.ts","import":"./es/presets/hubspot.js","source":"./src/presets/hubspot.ts","require":"./lib/presets/hubspot.js"},"./trackers/scroll":{"types":"./types/trackers/scroll.d.ts","import":"./es/trackers/scroll.js","source":"./src/trackers/scroll.ts","require":"./lib/trackers/scroll.js"},"./trackers/session":{"types":"./types/trackers/session.d.ts","import":"./es/trackers/session.js","source":"./src/trackers/session.ts","require":"./lib/trackers/session.js"},"./trackers/page-views":{"types":"./types/trackers/page-views.d.ts","import":"./es/trackers/page-views.js","source":"./src/trackers/page-views.ts","require":"./lib/trackers/page-views.js"}},"gitHead":"a96686dce970cb0bf7c8fe55058d16b8735d53a6","scripts":{"lint":"eslint src --ext .ts,.tsx","test":"jest","build":"bun run clean && bun run format && bun run lint && bun run compile && bun run bundle","clean":"rm -rf lib es dist types","bundle":"webpack --mode production --env BUILD_TYPE=all","format":"prettier --write \"src/**/*.{js,ts,tsx}\"","compile":"bun run compile:cjs && bun run compile:es && bun run compile:types","release":"bun run build && npm publish --provenance --access public","compile:es":"tsc -p tsconfig.es.json","test:watch":"jest --watch","compile:cjs":"tsc -p tsconfig.cjs.json","format:check":"prettier --check \"src/**/*.{js,ts,tsx}\"","compile:types":"tsc -p tsconfig.types.json","test:coverage":"jest --coverage"},"_npmUser":{"name":"joalves","email":"jonas@absmartly.com"},"_npmVersion":"11.3.0","description":"Standalone DOM analytics tracking library using data-attributes and selector rules","directories":{},"_nodeVersion":"22.15.0","_hasShrinkwrap":false,"devDependencies":{"jest":"^29.7.0","eslint":"^8.50.0","ts-jest":"^29.4.1","webpack":"^5.88.2","prettier":"^3.0.3","ts-loader":"^9.4.4","typescript":"^5.2.2","@types/jest":"^29.5.14","@types/node":"^20.8.0","webpack-cli":"^5.1.4","eslint-config-prettier":"^9.0.0","eslint-plugin-prettier":"^5.0.0","jest-environment-jsdom":"^29.7.0","@typescript-eslint/parser":"^6.7.4","@typescript-eslint/eslint-plugin":"^6.7.4"},"_npmOperationalInternal":{"tmp":"tmp/dom-tracker_0.1.0_1776180545291_0.01865225046319785","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@absmartly/dom-tracker","version":"0.3.0","keywords":["analytics","tracking","dom","data-attributes","events"],"author":{"name":"ABsmartly"},"license":"MIT","_id":"@absmartly/dom-tracker@0.3.0","maintainers":[{"name":"marcioapm","email":"marcioapm@gmail.com"},{"name":"joalves","email":"jonas@absmartly.com"},{"name":"caltheabtester","email":"cal@absmartly.com"},{"name":"marcioabs","email":"marcio@absmartly.com"}],"homepage":"https://github.com/absmartly/dom-tracker#readme","bugs":{"url":"https://github.com/absmartly/dom-tracker/issues"},"dist":{"shasum":"1580ba441293a365c34930b3b2985f6a2efcd08f","tarball":"https://registry.npmjs.org/@absmartly/dom-tracker/-/dom-tracker-0.3.0.tgz","fileCount":100,"integrity":"sha512-1DqJ+FFRSNQlk06nAeDyqWYLxgYEA+oZ1q6F7c6NaKngQ7FhpZ1FSuIKyiZo7q7bziggp1M82lc/yVI9skMQ6Q==","signatures":[{"sig":"MEUCIEZ48IorgTI9vwfONv0t7cvueCeU0le3u9r+zyEySLNuAiEA8eq59AmB9AueZ1tUPCc116nM1xttw70WfRtXmICuMJM=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@absmartly%2fdom-tracker@0.3.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":297534},"main":"lib/index.js","types":"types/index.d.ts","module":"es/index.js","browser":"dist/dom-tracker.min.js","exports":{".":{"types":"./types/index.d.ts","import":"./es/index.js","source":"./src/index.ts","require":"./lib/index.js"},"./presets":{"types":"./types/presets/index.d.ts","import":"./es/presets/index.js","source":"./src/presets/index.ts","require":"./lib/presets/index.js"},"./trackers/time":{"types":"./types/trackers/time.d.ts","import":"./es/trackers/time.js","source":"./src/trackers/time.ts","require":"./lib/trackers/time.js"},"./trackers/forms":{"types":"./types/trackers/forms.d.ts","import":"./es/trackers/forms.js","source":"./src/trackers/forms.ts","require":"./lib/trackers/forms.js"},"./presets/hubspot":{"types":"./types/presets/hubspot.d.ts","import":"./es/presets/hubspot.js","source":"./src/presets/hubspot.ts","require":"./lib/presets/hubspot.js"},"./trackers/scroll":{"types":"./types/trackers/scroll.d.ts","import":"./es/trackers/scroll.js","source":"./src/trackers/scroll.ts","require":"./lib/trackers/scroll.js"},"./trackers/session":{"types":"./types/trackers/session.d.ts","import":"./es/trackers/session.js","source":"./src/trackers/session.ts","require":"./lib/trackers/session.js"},"./trackers/page-views":{"types":"./types/trackers/page-views.d.ts","import":"./es/trackers/page-views.js","source":"./src/trackers/page-views.ts","require":"./lib/trackers/page-views.js"}},"gitHead":"cba1625f07e50f50a5bcccdd5100ba36dc8487aa","scripts":{"lint":"eslint src --ext .ts,.tsx","test":"jest","build":"bun run clean && bun run format && bun run lint && bun run compile && bun run bundle","clean":"rm -rf lib es dist types","bundle":"webpack --mode production --env BUILD_TYPE=all","format":"prettier --write \"src/**/*.{js,ts,tsx}\"","compile":"bun run compile:cjs && bun run compile:es && bun run compile:types","release":"bun run build","compile:es":"tsc -p tsconfig.es.json","test:watch":"jest --watch","compile:cjs":"tsc -p tsconfig.cjs.json","format:check":"prettier --check \"src/**/*.{js,ts,tsx}\"","compile:types":"tsc -p tsconfig.types.json","test:coverage":"jest --coverage"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:f71ef04f-d392-41da-8154-e7915d4495b6"}},"repository":{"url":"git+https://github.com/absmartly/dom-tracker.git","type":"git"},"_npmVersion":"11.11.0","description":"Standalone DOM analytics tracking library using data-attributes and selector rules","directories":{},"_nodeVersion":"24.14.1","_hasShrinkwrap":false,"devDependencies":{"jest":"^29.7.0","eslint":"^8.50.0","ts-jest":"^29.4.1","webpack":"^5.88.2","prettier":"^3.0.3","ts-loader":"^9.4.4","typescript":"^5.2.2","@types/jest":"^29.5.14","@types/node":"^20.8.0","webpack-cli":"^5.1.4","eslint-config-prettier":"^9.0.0","eslint-plugin-prettier":"^5.0.0","jest-environment-jsdom":"^29.7.0","@typescript-eslint/parser":"^6.7.4","@typescript-eslint/eslint-plugin":"^6.7.4"},"_npmOperationalInternal":{"tmp":"tmp/dom-tracker_0.3.0_1776209764303_0.8093161421391812","host":"s3://npm-registry-packages-npm-production"}},"0.4.0":{"name":"@absmartly/dom-tracker","version":"0.4.0","keywords":["analytics","tracking","dom","data-attributes","events"],"author":{"name":"ABsmartly"},"license":"MIT","_id":"@absmartly/dom-tracker@0.4.0","maintainers":[{"name":"marcioapm","email":"marcioapm@gmail.com"},{"name":"joalves","email":"jonas@absmartly.com"},{"name":"caltheabtester","email":"cal@absmartly.com"},{"name":"marcioabs","email":"marcio@absmartly.com"}],"homepage":"https://github.com/absmartly/dom-tracker#readme","bugs":{"url":"https://github.com/absmartly/dom-tracker/issues"},"dist":{"shasum":"2848fb8c1b5d2e2fd5b667d94688b90294f6198c","tarball":"https://registry.npmjs.org/@absmartly/dom-tracker/-/dom-tracker-0.4.0.tgz","fileCount":100,"integrity":"sha512-ddmyo0HjZrCu6GrJLCDzKrwh6+AlsFnE+dj5jNEsh5pFlFMxb+ylLI2wPykhNATwXaG9YCWbHk2GaoSIlBEa3g==","signatures":[{"sig":"MEYCIQDdzInNZJpAqJ48tvdMRtLmQqa2YsjlaZDqA/GvqTmKowIhAMCxLf4Hso+2dq5RxuppD9/Vah2BH7aJGigWaMxRBV1a","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@absmartly%2fdom-tracker@0.4.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":303214},"main":"lib/index.js","types":"types/index.d.ts","module":"es/index.js","browser":"dist/dom-tracker.min.js","exports":{".":{"types":"./types/index.d.ts","import":"./es/index.js","source":"./src/index.ts","require":"./lib/index.js"},"./presets":{"types":"./types/presets/index.d.ts","import":"./es/presets/index.js","source":"./src/presets/index.ts","require":"./lib/presets/index.js"},"./trackers/time":{"types":"./types/trackers/time.d.ts","import":"./es/trackers/time.js","source":"./src/trackers/time.ts","require":"./lib/trackers/time.js"},"./trackers/forms":{"types":"./types/trackers/forms.d.ts","import":"./es/trackers/forms.js","source":"./src/trackers/forms.ts","require":"./lib/trackers/forms.js"},"./presets/hubspot":{"types":"./types/presets/hubspot.d.ts","import":"./es/presets/hubspot.js","source":"./src/presets/hubspot.ts","require":"./lib/presets/hubspot.js"},"./trackers/scroll":{"types":"./types/trackers/scroll.d.ts","import":"./es/trackers/scroll.js","source":"./src/trackers/scroll.ts","require":"./lib/trackers/scroll.js"},"./trackers/session":{"types":"./types/trackers/session.d.ts","import":"./es/trackers/session.js","source":"./src/trackers/session.ts","require":"./lib/trackers/session.js"},"./trackers/page-views":{"types":"./types/trackers/page-views.d.ts","import":"./es/trackers/page-views.js","source":"./src/trackers/page-views.ts","require":"./lib/trackers/page-views.js"}},"gitHead":"1e59c086ef623ddb238066c6165b204ebcc123d0","scripts":{"lint":"eslint src --ext .ts,.tsx","test":"jest","build":"bun run clean && bun run format && bun run lint && bun run compile && bun run bundle","clean":"rm -rf lib es dist types","bundle":"webpack --mode production --env BUILD_TYPE=all","format":"prettier --write \"src/**/*.{js,ts,tsx}\"","compile":"bun run compile:cjs && bun run compile:es && bun run compile:types","prepare":"git config core.hooksPath .githooks","release":"bun run build","compile:es":"tsc -p tsconfig.es.json","test:watch":"jest --watch","compile:cjs":"tsc -p tsconfig.cjs.json","format:check":"prettier --check \"src/**/*.{js,ts,tsx}\"","compile:types":"tsc -p tsconfig.types.json","test:coverage":"jest --coverage"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:f71ef04f-d392-41da-8154-e7915d4495b6"}},"repository":{"url":"git+https://github.com/absmartly/dom-tracker.git","type":"git"},"_npmVersion":"11.11.0","description":"Standalone DOM analytics tracking library using data-attributes and selector rules","directories":{},"_nodeVersion":"24.14.1","_hasShrinkwrap":false,"devDependencies":{"jest":"^29.7.0","eslint":"^8.50.0","ts-jest":"^29.4.1","webpack":"^5.88.2","prettier":"^3.0.3","ts-loader":"^9.4.4","typescript":"^5.2.2","@types/jest":"^29.5.14","@types/node":"^20.8.0","webpack-cli":"^5.1.4","eslint-config-prettier":"^9.0.0","eslint-plugin-prettier":"^5.0.0","jest-environment-jsdom":"^29.7.0","@typescript-eslint/parser":"^6.7.4","@typescript-eslint/eslint-plugin":"^6.7.4"},"_npmOperationalInternal":{"tmp":"tmp/dom-tracker_0.4.0_1776211279794_0.31905867761150897","host":"s3://npm-registry-packages-npm-production"}},"0.5.0":{"name":"@absmartly/dom-tracker","version":"0.5.0","description":"Standalone DOM analytics tracking library using data-attributes and selector rules","main":"lib/index.js","module":"es/index.js","browser":"dist/dom-tracker.min.js","types":"types/index.d.ts","exports":{".":{"source":"./src/index.ts","types":"./types/index.d.ts","import":"./es/index.js","require":"./lib/index.js"},"./trackers/page-views":{"source":"./src/trackers/page-views.ts","types":"./types/trackers/page-views.d.ts","import":"./es/trackers/page-views.js","require":"./lib/trackers/page-views.js"},"./trackers/scroll":{"source":"./src/trackers/scroll.ts","types":"./types/trackers/scroll.d.ts","import":"./es/trackers/scroll.js","require":"./lib/trackers/scroll.js"},"./trackers/time":{"source":"./src/trackers/time.ts","types":"./types/trackers/time.d.ts","import":"./es/trackers/time.js","require":"./lib/trackers/time.js"},"./trackers/forms":{"source":"./src/trackers/forms.ts","types":"./types/trackers/forms.d.ts","import":"./es/trackers/forms.js","require":"./lib/trackers/forms.js"},"./trackers/session":{"source":"./src/trackers/session.ts","types":"./types/trackers/session.d.ts","import":"./es/trackers/session.js","require":"./lib/trackers/session.js"},"./trackers/rage-clicks":{"source":"./src/trackers/rage-clicks.ts","types":"./types/trackers/rage-clicks.d.ts","import":"./es/trackers/rage-clicks.js","require":"./lib/trackers/rage-clicks.js"},"./trackers/dead-clicks":{"source":"./src/trackers/dead-clicks.ts","types":"./types/trackers/dead-clicks.d.ts","import":"./es/trackers/dead-clicks.js","require":"./lib/trackers/dead-clicks.js"},"./trackers/element-visibility":{"source":"./src/trackers/element-visibility.ts","types":"./types/trackers/element-visibility.d.ts","import":"./es/trackers/element-visibility.js","require":"./lib/trackers/element-visibility.js"},"./trackers/outbound-links":{"source":"./src/trackers/outbound-links.ts","types":"./types/trackers/outbound-links.d.ts","import":"./es/trackers/outbound-links.js","require":"./lib/trackers/outbound-links.js"},"./trackers/error-tracking":{"source":"./src/trackers/error-tracking.ts","types":"./types/trackers/error-tracking.d.ts","import":"./es/trackers/error-tracking.js","require":"./lib/trackers/error-tracking.js"},"./presets":{"source":"./src/presets/index.ts","types":"./types/presets/index.d.ts","import":"./es/presets/index.js","require":"./lib/presets/index.js"},"./presets/hubspot":{"source":"./src/presets/hubspot.ts","types":"./types/presets/hubspot.d.ts","import":"./es/presets/hubspot.js","require":"./lib/presets/hubspot.js"}},"scripts":{"clean":"rm -rf lib es dist types","format":"prettier --write \"src/**/*.{js,ts,tsx}\"","format:check":"prettier --check \"src/**/*.{js,ts,tsx}\"","lint":"eslint src --ext .ts,.tsx","compile:cjs":"tsc -p tsconfig.cjs.json","compile:es":"tsc -p tsconfig.es.json","compile:types":"tsc -p tsconfig.types.json","compile":"bun run compile:cjs && bun run compile:es && bun run compile:types","bundle":"webpack --mode production --env BUILD_TYPE=all","build":"bun run clean && bun run format && bun run lint && bun run compile && bun run bundle","test":"jest","test:watch":"jest --watch","test:coverage":"jest --coverage","release":"bun run build","prepare":"git config core.hooksPath .githooks"},"repository":{"type":"git","url":"git+https://github.com/absmartly/dom-tracker.git"},"keywords":["analytics","tracking","dom","data-attributes","events"],"author":{"name":"ABsmartly"},"license":"MIT","devDependencies":{"@types/jest":"^29.5.14","@types/node":"^20.8.0","@typescript-eslint/eslint-plugin":"^6.7.4","@typescript-eslint/parser":"^6.7.4","eslint":"^8.50.0","eslint-config-prettier":"^9.0.0","eslint-plugin-prettier":"^5.0.0","jest":"^29.7.0","jest-environment-jsdom":"^29.7.0","prettier":"^3.0.3","ts-jest":"^29.4.1","ts-loader":"^9.4.4","typescript":"^5.2.2","webpack":"^5.88.2","webpack-cli":"^5.1.4"},"gitHead":"d46bd49016350cf019177084e44a91cca70e42e2","_id":"@absmartly/dom-tracker@0.5.0","bugs":{"url":"https://github.com/absmartly/dom-tracker/issues"},"homepage":"https://github.com/absmartly/dom-tracker#readme","_nodeVersion":"24.14.1","_npmVersion":"11.11.0","dist":{"integrity":"sha512-o/3w+UbumqJ8PEog2/nfwag9urpPTZCLCcJRZuYEdkoSVIj+RSoWuH5ZHVDvKagqZkCfAc0Jy+bAcaEM5Bp0jQ==","shasum":"0b262c477258f967baa45e3b5dd3d20f39a0ada1","tarball":"https://registry.npmjs.org/@absmartly/dom-tracker/-/dom-tracker-0.5.0.tgz","fileCount":130,"unpackedSize":386867,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@absmartly%2fdom-tracker@0.5.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIHP2Q9PAVaMagCOymu+FmRHz6vwMDSCbIGiaaaF8So6PAiANG3RGivyIN+LUhnzA886nL+hNLrlKYJfXtUw9KVRrlg=="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:f71ef04f-d392-41da-8154-e7915d4495b6"}},"directories":{},"maintainers":[{"name":"marcioapm","email":"marcioapm@gmail.com"},{"name":"joalves","email":"jonas@absmartly.com"},{"name":"caltheabtester","email":"cal@absmartly.com"},{"name":"marcioabs","email":"marcio@absmartly.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/dom-tracker_0.5.0_1776274387866_0.5337507938291544"},"_hasShrinkwrap":false}},"time":{"created":"2026-04-14T15:29:05.181Z","modified":"2026-04-15T17:33:08.354Z","0.1.0":"2026-04-14T15:29:05.430Z","0.3.0":"2026-04-14T23:36:04.432Z","0.4.0":"2026-04-15T00:01:19.931Z","0.5.0":"2026-04-15T17:33:08.019Z"},"bugs":{"url":"https://github.com/absmartly/dom-tracker/issues"},"author":{"name":"ABsmartly"},"license":"MIT","homepage":"https://github.com/absmartly/dom-tracker#readme","keywords":["analytics","tracking","dom","data-attributes","events"],"repository":{"type":"git","url":"git+https://github.com/absmartly/dom-tracker.git"},"description":"Standalone DOM analytics tracking library using data-attributes and selector rules","maintainers":[{"name":"marcioapm","email":"marcioapm@gmail.com"},{"name":"joalves","email":"jonas@absmartly.com"},{"name":"caltheabtester","email":"cal@absmartly.com"},{"name":"marcioabs","email":"marcio@absmartly.com"}],"readme":"# @absmartly/dom-tracker\n\n[![CI](https://github.com/absmartly/dom-tracker/actions/workflows/ci.yml/badge.svg)](https://github.com/absmartly/dom-tracker/actions/workflows/ci.yml)\n[![npm](https://img.shields.io/npm/v/@absmartly/dom-tracker)](https://www.npmjs.com/package/@absmartly/dom-tracker)\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)\n\nLightweight, framework-agnostic DOM analytics tracking library. Capture user interactions through **data attributes** and **CSS selector rules** — no framework integration required.\n\n## Features\n\n- **Data-attribute tracking** — add `data-abs-track` to any element to capture clicks\n- **CSS selector rules** — define rules that match elements and fire events on interaction\n- **Built-in trackers** — page views, scroll depth, time on page, form tracking, session tracking\n- **SPA support** — automatic route change detection via History API patching\n- **DOM mutation monitoring** — tracks dynamically added/removed elements\n- **Preset system** — pre-built configurations (e.g., HubSpot forms)\n- **Tree-shakeable** — import only the trackers you need\n- **Zero dependencies** — no runtime dependencies\n- **TypeScript** — fully typed with exported type definitions\n- **Multiple formats** — CommonJS, ES Modules, and UMD browser bundle\n\n## Installation\n\n```bash\n# npm\nnpm install @absmartly/dom-tracker\n\n# yarn\nyarn add @absmartly/dom-tracker\n\n# bun\nbun add @absmartly/dom-tracker\n```\n\nOr use the UMD bundle directly in a `<script>` tag:\n\n```html\n<script src=\"https://unpkg.com/@absmartly/dom-tracker/dist/dom-tracker.min.js\"></script>\n<script>\n  const tracker = new ABsmartlyDOMTracker.DOMTracker({ /* ... */ });\n</script>\n```\n\n## Quick Start\n\n```typescript\nimport { DOMTracker } from \"@absmartly/dom-tracker\";\n\nconst tracker = new DOMTracker({\n  onEvent: (event, props) => {\n    console.log(event, props);\n    // Send to your analytics provider\n  },\n  spa: true,\n});\n```\n\n```html\n<button data-abs-track=\"signup_click\" data-abs-plan=\"pro\">\n  Sign Up\n</button>\n<!-- Clicking emits: \"signup_click\" { plan: \"pro\", page_name: \"pricing\" } -->\n```\n\n## Configuration\n\nThe `DOMTracker` constructor accepts a `DOMTrackerConfig` object:\n\n```typescript\ninterface DOMTrackerConfig {\n  // Required — called for every tracked event\n  onEvent: EventHandler | EventHandler[];\n\n  // Optional — called when a tracker sets attributes (e.g., session data)\n  onAttribute?: AttributeHandler | AttributeHandler[];\n\n  // Additional trackers to register\n  trackers?: Tracker[];\n\n  // CSS selector tracking rules\n  rules?: TrackingRule[];\n\n  // Pre-built presets (rules + trackers)\n  presets?: Preset[];\n\n  // Enable SPA route change detection (default: false)\n  spa?: boolean;\n\n  // Auto-register default trackers: page-views, forms, session (default: true)\n  defaults?: boolean;\n\n  // Log debug info to console (default: false)\n  debug?: boolean;\n\n  // Custom page name derivation (default: last URL path segment)\n  pageName?: (url: URL) => string;\n}\n```\n\n**Handler types:**\n\n```typescript\ntype EventHandler = (event: string, props: Record<string, unknown>) => void;\ntype AttributeHandler = (attrs: Record<string, unknown>) => void;\n```\n\nMultiple handlers are supported — pass an array and each will be called independently:\n\n```typescript\nconst tracker = new DOMTracker({\n  onEvent: [\n    (event, props) => analytics.track(event, props),\n    (event, props) => console.log(event, props),\n  ],\n});\n```\n\n## Data Attribute Tracking\n\nAdd `data-abs-track` to any HTML element to automatically capture click events. Additional `data-abs-*` attributes are included as event properties.\n\n```html\n<button\n  data-abs-track=\"add_to_cart\"\n  data-abs-product-id=\"123\"\n  data-abs-price=\"29.99\"\n  data-abs-is-sale=\"true\"\n>\n  Add to Cart\n</button>\n```\n\nThis emits:\n\n```json\n{\n  \"event\": \"add_to_cart\",\n  \"props\": {\n    \"product_id\": \"123\",\n    \"price\": 29.99,\n    \"is_sale\": true,\n    \"page_name\": \"product-detail\"\n  }\n}\n```\n\n**Attribute parsing rules:**\n- `data-abs-track` — the event name (required)\n- `data-abs-*` — additional properties (kebab-case converted to snake_case)\n- `\"true\"` / `\"false\"` — coerced to booleans\n- Numeric strings — coerced to numbers\n- `page_name` — automatically injected\n\n**Debouncing:** Duplicate events on the same element are suppressed for 500ms.\n\n## CSS Selector Rules\n\nDefine rules that match elements by CSS selector and fire events on interaction:\n\n```typescript\nconst tracker = new DOMTracker({\n  onEvent: (event, props) => console.log(event, props),\n  rules: [\n    {\n      selector: \".cta-button\",\n      event: \"cta_clicked\",\n      props: { section: \"hero\" },\n    },\n    {\n      selector: \".search-input\",\n      event: \"search_focused\",\n      on: \"focus\",\n    },\n  ],\n});\n```\n\n```typescript\ninterface TrackingRule {\n  selector: string;                  // CSS selector to match\n  event: string;                     // Event name to emit\n  on?: string;                       // DOM event type (default: \"click\")\n  props?: Record<string, unknown>;   // Static properties to include\n}\n```\n\nRules use event delegation on the `window`, so they automatically work for dynamically added elements. Elements with `data-abs-track` are skipped to prevent double-tracking. Click events are debounced at 500ms per rule per element.\n\nRules can also be added after initialization:\n\n```typescript\ntracker.addRule({ selector: \".new-feature\", event: \"feature_click\" });\n```\n\n## Built-in Trackers\n\nBy default, `DOMTracker` auto-registers the **page-views**, **form-tracker**, and **session** trackers. Set `defaults: false` to disable this.\n\n### Page Views\n\nEmits a `page_view` event on initialization and on every SPA route change.\n\n**Event:** `page_view`\n\n| Property | Description |\n|---|---|\n| `page_name` | Derived page name |\n| `page_path` | URL pathname |\n| `page_url` | Full URL |\n| `referrer` | Document referrer |\n\n```typescript\nimport { pageViews } from \"@absmartly/dom-tracker\";\n\nconst tracker = new DOMTracker({\n  onEvent: handler,\n  defaults: false,\n  trackers: [pageViews()],\n});\n```\n\n### Scroll Depth\n\nTracks how far users scroll down the page.\n\n**Event:** `scroll_depth`\n\n| Property | Description |\n|---|---|\n| `threshold` | Scroll percentage reached |\n| `page_name` | Current page name |\n\n**Config:**\n\n```typescript\nimport { scrollDepth } from \"@absmartly/dom-tracker\";\n\nscrollDepth({\n  thresholds: [25, 50, 75, 100], // default\n});\n```\n\nEach threshold fires once per page. Thresholds reset on route change. Scroll events are throttled at 200ms.\n\n### Time on Page\n\nMeasures how long users spend on each page.\n\n**Events:**\n\n| Event | Properties | Description |\n|---|---|---|\n| `time_on_page` | `seconds`, `page_name` | Fires at each threshold |\n| `tab_hidden` | `page_name`, `time_on_page` | Tab became hidden (optional) |\n| `tab_visible` | `page_name`, `hidden_duration` | Tab became visible (optional) |\n\n**Config:**\n\n```typescript\nimport { timeOnPage } from \"@absmartly/dom-tracker\";\n\ntimeOnPage({\n  thresholds: [10, 30, 60, 180], // seconds (default)\n  visibility: {\n    trackEvents: false, // emit tab_hidden/tab_visible events (default: false)\n  },\n});\n```\n\nThe timer pauses when the tab is hidden and resumes when visible. Thresholds reset on route change.\n\n### Form Tracking\n\nTracks form interactions: start, submission, and abandonment.\n\n**Events:**\n\n| Event | Properties | Description |\n|---|---|---|\n| `form_started` | `form_id`, `form_action`, `page_name` | First field focused |\n| `form_submitted` | `form_id`, `form_action`, `page_name` | Form submitted |\n| `form_abandoned` | `form_id`, `fields_completed`, `last_field`, `page_name` | Inactivity timeout or route change |\n\n**Config:**\n\n```typescript\nimport { formTracker } from \"@absmartly/dom-tracker\";\n\nformTracker({\n  abandonment: {\n    timeout: 30000, // ms before firing form_abandoned (optional)\n  },\n});\n```\n\n**Form ID derivation** (first match wins): `data-abs-form-id` attribute → `form.id` → `form.name` → auto-generated ID.\n\nAbandonment fires when a user starts filling a form but doesn't submit within the timeout, or when they navigate away (SPA route change).\n\n### Session Tracking\n\nGenerates visitor and session IDs, detects traffic source, device type, and extracts UTM parameters.\n\n**Event:** `session_start`\n\n| Property | Description |\n|---|---|\n| `session_id` | Unique session identifier |\n| `landing_page` | URL pathname of first page |\n| `referrer` | Document referrer |\n\n**Attributes set** (via `onAttribute`):\n\n| Attribute | Description |\n|---|---|\n| `returning_visitor` | `true` if visitor cookie existed |\n| `traffic_source` | `\"direct\"`, `\"organic\"`, `\"social\"`, `\"referral\"`, or `\"paid\"` |\n| `device` | `\"desktop\"`, `\"mobile\"`, or `\"tablet\"` |\n| `utm_source` | UTM source parameter |\n| `utm_medium` | UTM medium parameter |\n| `utm_campaign` | UTM campaign parameter |\n| `utm_term` | UTM term parameter |\n| `utm_content` | UTM content parameter |\n\n**Config:**\n\n```typescript\nimport { sessionTracker } from \"@absmartly/dom-tracker\";\n\nsessionTracker({\n  cookieDomain: \".example.com\", // optional\n});\n```\n\n**Cookies:**\n- `_abs_visitor` — 365-day cookie for visitor ID (falls back to localStorage)\n- `_abs_session` — 1-day cookie for session ID (falls back to sessionStorage)\n\n**Traffic source detection:**\n- `\"paid\"` — utm_source parameter present\n- `\"organic\"` — referrer from Google, Bing, Yahoo, DuckDuckGo, or Baidu\n- `\"social\"` — referrer from Facebook, Instagram, Twitter, LinkedIn, TikTok, or Pinterest\n- `\"referral\"` — any other referrer\n- `\"direct\"` — no referrer\n\n### Rage Clicks\n\nDetects rapid repeated clicks on the same element, which typically indicates user frustration.\n\n**Event:** `rage_click`\n\n| Property | Description |\n|---|---|\n| `element_tag` | HTML tag name of the clicked element |\n| `element_text` | Visible text content (truncated to 100 chars) |\n| `click_count` | Number of clicks that triggered the event |\n| `page_name` | Current page name |\n\n**Config:**\n\n```typescript\nimport { rageClicks } from \"@absmartly/dom-tracker/trackers/rage-clicks\";\n\nrageClicks({\n  threshold: 3,    // clicks required to trigger (default)\n  window: 1000,    // time window in ms (default)\n});\n```\n\nClick counters reset on route change.\n\n### Dead Clicks\n\nDetects clicks on non-interactive elements, indicating broken or confusing UI.\n\n**Event:** `dead_click`\n\n| Property | Description |\n|---|---|\n| `element_tag` | HTML tag name of the clicked element |\n| `element_text` | Visible text content (truncated to 100 chars) |\n| `page_name` | Current page name |\n\n```typescript\nimport { deadClicks } from \"@absmartly/dom-tracker/trackers/dead-clicks\";\n\ndeadClicks();\n```\n\nAn element is considered interactive if it or any ancestor is: a native interactive tag (`a`, `button`, `input`, `select`, `textarea`, `label`, `summary`, `details`), has an ARIA role (`button`, `link`, `tab`, `menuitem`, etc.), has an `onclick` attribute, has `data-abs-track`, or has `contenteditable`. Events are debounced to one per element per second, with a 500ms delay before the interactivity check runs.\n\n### Element Visibility\n\nTracks when elements enter the viewport, useful for impression tracking.\n\n**Event:** `element_visible` (or a custom event name per element/rule)\n\n| Property | Description |\n|---|---|\n| `event_name` | Value of `data-abs-visible` or the rule's `event` field |\n| `page_name` | Current page name |\n| `...` | Any additional `data-abs-*` props on the element |\n\n**Two ways to mark elements:**\n\n1. **Data attribute** — add `data-abs-visible=\"impression_name\"` to any element, with optional `data-abs-*` props:\n\n```html\n<div\n  data-abs-visible=\"hero_banner_seen\"\n  data-abs-variant=\"dark\"\n>\n  <!-- ... -->\n</div>\n```\n\n2. **Config rules** — match elements by CSS selector:\n\n```typescript\nimport { elementVisibility } from \"@absmartly/dom-tracker/trackers/element-visibility\";\n\nelementVisibility({\n  threshold: 0.5, // fraction of element that must be visible (default)\n  rules: [\n    { selector: \".pricing-card\", event: \"pricing_card_seen\" },\n    { selector: \"#hero-banner\", event: \"hero_banner_seen\" },\n  ],\n});\n```\n\nEach element fires once per page. Seen elements reset on route change.\n\n### Outbound Link Clicks\n\nTracks clicks on links that navigate to an external hostname.\n\n**Event:** `outbound_click`\n\n| Property | Description |\n|---|---|\n| `url` | Full destination URL |\n| `hostname` | Destination hostname |\n| `link_text` | Anchor text (truncated to 100 chars) |\n| `page_name` | Current page name |\n\n```typescript\nimport { outboundLinks } from \"@absmartly/dom-tracker/trackers/outbound-links\";\n\noutboundLinks();\n```\n\n`mailto:` and `tel:` links are ignored. Detection uses event delegation, so clicks on child elements inside an anchor are captured correctly.\n\n### Error Tracking\n\nCaptures unhandled JavaScript errors and promise rejections.\n\n**Event:** `js_error`\n\n| Property | Description |\n|---|---|\n| `message` | Error message |\n| `filename` | Source file URL |\n| `lineno` | Line number |\n| `colno` | Column number |\n| `stack` | Stack trace (truncated to 1000 chars) |\n| `page_name` | Current page name |\n\n**Config:**\n\n```typescript\nimport { errorTracker } from \"@absmartly/dom-tracker/trackers/error-tracking\";\n\nerrorTracker({\n  maxErrors: 10,      // max errors captured per page (default)\n  dedupeWindow: 5000, // ms to suppress identical errors (default)\n});\n```\n\nListens to `window.onerror` and `unhandledrejection`. Identical errors (same message + filename + lineno) are deduplicated within `dedupeWindow`. The per-page error count resets on route change.\n\n## SPA Support\n\nEnable `spa: true` to automatically detect route changes in single-page applications:\n\n```typescript\nconst tracker = new DOMTracker({\n  onEvent: handler,\n  spa: true,\n});\n```\n\nThis:\n- Patches `history.pushState()` and `history.replaceState()`\n- Listens for `popstate` and `hashchange` events\n- Starts a `MutationObserver` on `document.body`\n- Notifies all trackers of route changes (resetting scroll depth, time on page, etc.)\n- Lets trackers subscribe to element additions/removals\n\n## Presets\n\nPresets bundle tracking rules and a tracker into a reusable configuration.\n\n### Using a Preset\n\n```typescript\nimport { DOMTracker } from \"@absmartly/dom-tracker\";\nimport { hubspotForms } from \"@absmartly/dom-tracker/presets/hubspot\";\n\nconst tracker = new DOMTracker({\n  onEvent: handler,\n  spa: true,\n  presets: [\n    hubspotForms({ abandonment: { timeout: 30000 } }),\n  ],\n});\n```\n\n### HubSpot Forms Preset\n\nTracks HubSpot embedded forms (`form.hs-form`) with automatic detection of dynamically injected forms.\n\n**Rules:** Tracks `.hs-input` focus events as `form_field_focused`.\n\n**Events:**\n\n| Event | Properties |\n|---|---|\n| `form_started` | `form_type: \"hubspot\"`, `page_name` |\n| `form_submitted` | `form_type: \"hubspot\"`, `page_name` |\n| `form_abandoned` | `form_type: \"hubspot\"`, `page_name` |\n\n### Creating a Custom Preset\n\n```typescript\nimport { definePreset } from \"@absmartly/dom-tracker\";\n\nconst myPreset = definePreset({\n  rules: [\n    { selector: \".pricing-card\", event: \"pricing_viewed\", on: \"mouseenter\" },\n  ],\n  tracker: {\n    name: \"my-custom-tracker\",\n    init(ctx) { /* ... */ },\n    destroy() { /* ... */ },\n  },\n});\n```\n\n## Custom Trackers\n\nImplement the `Tracker` interface to create your own:\n\n```typescript\nimport { Tracker, TrackerContext } from \"@absmartly/dom-tracker\";\n\nconst myTracker: Tracker = {\n  name: \"my-tracker\",\n\n  init(ctx: TrackerContext) {\n    ctx.emit(\"tracker_loaded\", { page_name: ctx.getPageName() });\n  },\n\n  onRouteChange(url: string, prevUrl: string) {\n    // Called on SPA navigation\n  },\n\n  onDOMMutation(mutations: MutationRecord[]) {\n    // Called on DOM changes (SPA mode only)\n  },\n\n  destroy() {\n    // Clean up listeners, timers, etc.\n  },\n};\n```\n\n### TrackerContext API\n\nThe `ctx` object passed to `init()` provides:\n\n| Method | Description |\n|---|---|\n| `emit(event, props)` | Emit a tracking event |\n| `setAttributes(attrs)` | Set session/visitor attributes |\n| `getPageName()` | Get current page name |\n| `getConfig()` | Access DOMTrackerConfig |\n| `querySelectorAll(selector)` | Query DOM elements |\n| `onElementAdded(selector, cb)` | Subscribe to element additions (returns unsubscribe fn) |\n| `onElementRemoved(selector, cb)` | Subscribe to element removals (returns unsubscribe fn) |\n\nRegister custom trackers at construction or dynamically:\n\n```typescript\n// At construction\nconst dom = new DOMTracker({\n  onEvent: handler,\n  trackers: [myTracker],\n});\n\n// Or later\ndom.addTracker(myTracker);\ndom.removeTracker(\"my-tracker\");\n```\n\n## API Reference\n\n### `DOMTracker`\n\n```typescript\nconst tracker = new DOMTracker(config: DOMTrackerConfig);\n\ntracker.addTracker(tracker: Tracker): void;\ntracker.removeTracker(name: string): void;\ntracker.addRule(rule: TrackingRule): void;\ntracker.destroy(): void;\n```\n\n- **`addTracker`** — Register a new tracker. Throws if a tracker with the same name exists.\n- **`removeTracker`** — Unregister and destroy a tracker by name. No-op if not found.\n- **`addRule`** — Add a CSS selector tracking rule at runtime.\n- **`destroy`** — Remove all event listeners, destroy all trackers, clean up. Idempotent.\n\n### Sub-path Imports\n\nIndividual trackers and presets can be imported directly for tree-shaking:\n\n```typescript\nimport { pageViews } from \"@absmartly/dom-tracker/trackers/page-views\";\nimport { scrollDepth } from \"@absmartly/dom-tracker/trackers/scroll\";\nimport { timeOnPage } from \"@absmartly/dom-tracker/trackers/time\";\nimport { formTracker } from \"@absmartly/dom-tracker/trackers/forms\";\nimport { sessionTracker } from \"@absmartly/dom-tracker/trackers/session\";\nimport { rageClicks } from \"@absmartly/dom-tracker/trackers/rage-clicks\";\nimport { deadClicks } from \"@absmartly/dom-tracker/trackers/dead-clicks\";\nimport { elementVisibility } from \"@absmartly/dom-tracker/trackers/element-visibility\";\nimport { outboundLinks } from \"@absmartly/dom-tracker/trackers/outbound-links\";\nimport { errorTracker } from \"@absmartly/dom-tracker/trackers/error-tracking\";\nimport { definePreset } from \"@absmartly/dom-tracker/presets\";\nimport { hubspotForms } from \"@absmartly/dom-tracker/presets/hubspot\";\n```\n\n## Browser Bundle\n\nThe UMD bundle exposes global variables for use without a bundler:\n\n```html\n<!-- Default bundle -->\n<script src=\"https://unpkg.com/@absmartly/dom-tracker/dist/dom-tracker.min.js\"></script>\n<script>\n  const tracker = new ABsmartlyDOMTracker.DOMTracker({\n    onEvent: function (event, props) {\n      console.log(event, props);\n    },\n    spa: true,\n  });\n</script>\n\n<!-- Full bundle (includes all trackers and presets) -->\n<script src=\"https://unpkg.com/@absmartly/dom-tracker/dist/dom-tracker.full.min.js\"></script>\n<script>\n  // ABsmartlyDOMTrackerFull includes everything\n  const tracker = new ABsmartlyDOMTrackerFull.DOMTracker({ /* ... */ });\n</script>\n```\n\n## License\n\n[MIT](LICENSE)\n","readmeFilename":"README.md"}