heatspot 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +98 -0
- package/dist/constants/constants.d.ts +44 -0
- package/dist/constants/constants.js +56 -0
- package/dist/contracts/heatmap-contracts.d.ts +22 -0
- package/dist/contracts/heatmap-contracts.js +1 -0
- package/dist/contracts/rendering-contracts.d.ts +6 -0
- package/dist/contracts/rendering-contracts.js +1 -0
- package/dist/core/heatmap-tracker.d.ts +29 -0
- package/dist/core/heatmap-tracker.js +111 -0
- package/dist/heatmap-element.d.ts +47 -0
- package/dist/heatmap-element.js +176 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.js +66 -0
- package/dist/rendering/renderer.d.ts +26 -0
- package/dist/rendering/renderer.js +105 -0
- package/package.json +62 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 heatspot contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# heatspot
|
|
2
|
+
|
|
3
|
+
`heatspot` is an ESM TypeScript library for capturing pointer heat data and rendering an embeddable heatmap web component.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- ESM package output with TypeScript declarations
|
|
8
|
+
- Pointer heat tracking utility API
|
|
9
|
+
- Reusable `<heat-map>` component with slot-based content
|
|
10
|
+
- Built-in toggle icon (top-left) to show heat overlay
|
|
11
|
+
- Configurable `toolbar` attribute: `simple` (default) or `hidden`
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install heatspot
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
### 1. Import the library
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import "heatspot";
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The `heat-map` custom element is registered on import.
|
|
28
|
+
|
|
29
|
+
### 2. Render the component
|
|
30
|
+
|
|
31
|
+
```html
|
|
32
|
+
<heat-map toolbar="simple">
|
|
33
|
+
<section>
|
|
34
|
+
<h2>Example Panel</h2>
|
|
35
|
+
<p>Move the mouse over this area, then click the icon in the top-left.</p>
|
|
36
|
+
</section>
|
|
37
|
+
</heat-map>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`toolbar` options:
|
|
41
|
+
|
|
42
|
+
- `simple` - show the heatmap toggle icon
|
|
43
|
+
- `hidden` - hide the heatmap toggle icon
|
|
44
|
+
|
|
45
|
+
Example with hidden toolbar:
|
|
46
|
+
|
|
47
|
+
```html
|
|
48
|
+
<heat-map toolbar="hidden">
|
|
49
|
+
<section>
|
|
50
|
+
<h2>Passive Tracking Panel</h2>
|
|
51
|
+
<p>The heatmap toggle icon is not rendered in this mode.</p>
|
|
52
|
+
</section>
|
|
53
|
+
</heat-map>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Example
|
|
57
|
+
|
|
58
|
+

|
|
59
|
+
|
|
60
|
+
### 3. Optional tracking API
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
import {
|
|
64
|
+
configureMouseHeatmap,
|
|
65
|
+
getMouseHeatmapData,
|
|
66
|
+
resetMouseHeatmap,
|
|
67
|
+
startMouseTracking,
|
|
68
|
+
stopMouseTracking
|
|
69
|
+
} from "heatspot";
|
|
70
|
+
|
|
71
|
+
configureMouseHeatmap({ mergeRadius: 24, maxHotspots: 450 });
|
|
72
|
+
startMouseTracking();
|
|
73
|
+
|
|
74
|
+
const snapshot = getMouseHeatmapData();
|
|
75
|
+
console.log(snapshot.hotspots);
|
|
76
|
+
|
|
77
|
+
stopMouseTracking();
|
|
78
|
+
resetMouseHeatmap();
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Scripts
|
|
82
|
+
|
|
83
|
+
- `npm run build` - compile library to `dist/`
|
|
84
|
+
- `npm run test` - run Vitest tests
|
|
85
|
+
- `npm run test:coverage` - run tests with coverage reports in `coverage/`
|
|
86
|
+
- `npm run build:verify` - run tests and harness build
|
|
87
|
+
- `npm run pack:check` - show package contents using `npm pack --dry-run`
|
|
88
|
+
|
|
89
|
+
## Development
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
npm install
|
|
93
|
+
npm start
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## License
|
|
97
|
+
|
|
98
|
+
MIT
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type HeatmapConfig } from "../contracts/heatmap-contracts.js";
|
|
2
|
+
import { type HeatColor } from "../contracts/rendering-contracts.js";
|
|
3
|
+
/**
|
|
4
|
+
* Default clustering configuration used by the global tracker API.
|
|
5
|
+
*/
|
|
6
|
+
export declare const DEFAULT_HEATMAP_CONFIG: HeatmapConfig;
|
|
7
|
+
/** Minimum allowed hotspot merge radius in pixels. */
|
|
8
|
+
export declare const MIN_MERGE_RADIUS = 4;
|
|
9
|
+
/** Minimum allowed hotspot storage size for a tracker instance. */
|
|
10
|
+
export declare const MIN_MAX_HOTSPOTS = 20;
|
|
11
|
+
/**
|
|
12
|
+
* Tracker configuration used by the `heat-map` web component instance.
|
|
13
|
+
*/
|
|
14
|
+
export declare const ELEMENT_TRACKER_CONFIG: HeatmapConfig;
|
|
15
|
+
/**
|
|
16
|
+
* Ordered heat palette from coolest to hottest.
|
|
17
|
+
*/
|
|
18
|
+
export declare const DEFAULT_HEAT_COLORS: readonly HeatColor[];
|
|
19
|
+
/**
|
|
20
|
+
* Discrete contour levels used to pick the dominant output heat color.
|
|
21
|
+
*/
|
|
22
|
+
export declare const DOMINANT_HEAT_LEVELS: readonly [0.2, 0.35, 0.5, 0.65, 0.8, 1];
|
|
23
|
+
/** Exponent used to bias relative hotspot strength scaling. */
|
|
24
|
+
export declare const RELATIVE_STRENGTH_EXPONENT = 0.85;
|
|
25
|
+
/** Max normalized distance multiplier for hotspot influence cutoff. */
|
|
26
|
+
export declare const FIELD_DISTANCE_MULTIPLIER = 2.2;
|
|
27
|
+
/** Falloff divisor controlling Gaussian-style strength decay around hotspots. */
|
|
28
|
+
export declare const FIELD_FALLOFF_DIVISOR = 0.8;
|
|
29
|
+
/** Strength below this value is treated as low-heat rendering. */
|
|
30
|
+
export declare const LOW_HEAT_THRESHOLD = 0.05;
|
|
31
|
+
/** Alpha used for low-heat visible floor color. */
|
|
32
|
+
export declare const LOW_HEAT_ALPHA = 0.16;
|
|
33
|
+
/** Base alpha for dominant contour color levels. */
|
|
34
|
+
export declare const HEAT_ALPHA_BASE = 0.22;
|
|
35
|
+
/** Additional alpha multiplier per dominant contour level. */
|
|
36
|
+
export declare const HEAT_ALPHA_MULTIPLIER = 0.45;
|
|
37
|
+
/** Alpha used for maximum heat color output. */
|
|
38
|
+
export declare const MAX_HEAT_ALPHA = 0.68;
|
|
39
|
+
/** Sampling step in pixels for rasterized heat field rendering. */
|
|
40
|
+
export declare const DEFAULT_SAMPLE_STEP = 4;
|
|
41
|
+
/** Minimum contour radius in pixels for visible heat blobs. */
|
|
42
|
+
export declare const MIN_CONTOUR_RADIUS = 14;
|
|
43
|
+
/** Height/width ratio used to derive contour radius from viewport size. */
|
|
44
|
+
export declare const CONTOUR_RADIUS_RATIO = 0.07;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default clustering configuration used by the global tracker API.
|
|
3
|
+
*/
|
|
4
|
+
export const DEFAULT_HEATMAP_CONFIG = {
|
|
5
|
+
mergeRadius: 28,
|
|
6
|
+
maxHotspots: 400
|
|
7
|
+
};
|
|
8
|
+
/** Minimum allowed hotspot merge radius in pixels. */
|
|
9
|
+
export const MIN_MERGE_RADIUS = 4;
|
|
10
|
+
/** Minimum allowed hotspot storage size for a tracker instance. */
|
|
11
|
+
export const MIN_MAX_HOTSPOTS = 20;
|
|
12
|
+
/**
|
|
13
|
+
* Tracker configuration used by the `heat-map` web component instance.
|
|
14
|
+
*/
|
|
15
|
+
export const ELEMENT_TRACKER_CONFIG = {
|
|
16
|
+
mergeRadius: 24,
|
|
17
|
+
maxHotspots: 450
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Ordered heat palette from coolest to hottest.
|
|
21
|
+
*/
|
|
22
|
+
export const DEFAULT_HEAT_COLORS = [
|
|
23
|
+
[20, 34, 120],
|
|
24
|
+
[0, 145, 255],
|
|
25
|
+
[0, 200, 125],
|
|
26
|
+
[255, 225, 70],
|
|
27
|
+
[255, 140, 30],
|
|
28
|
+
[225, 35, 30],
|
|
29
|
+
[255, 255, 255]
|
|
30
|
+
];
|
|
31
|
+
/**
|
|
32
|
+
* Discrete contour levels used to pick the dominant output heat color.
|
|
33
|
+
*/
|
|
34
|
+
export const DOMINANT_HEAT_LEVELS = [0.2, 0.35, 0.5, 0.65, 0.8, 1];
|
|
35
|
+
/** Exponent used to bias relative hotspot strength scaling. */
|
|
36
|
+
export const RELATIVE_STRENGTH_EXPONENT = 0.85;
|
|
37
|
+
/** Max normalized distance multiplier for hotspot influence cutoff. */
|
|
38
|
+
export const FIELD_DISTANCE_MULTIPLIER = 2.2;
|
|
39
|
+
/** Falloff divisor controlling Gaussian-style strength decay around hotspots. */
|
|
40
|
+
export const FIELD_FALLOFF_DIVISOR = 0.8;
|
|
41
|
+
/** Strength below this value is treated as low-heat rendering. */
|
|
42
|
+
export const LOW_HEAT_THRESHOLD = 0.05;
|
|
43
|
+
/** Alpha used for low-heat visible floor color. */
|
|
44
|
+
export const LOW_HEAT_ALPHA = 0.16;
|
|
45
|
+
/** Base alpha for dominant contour color levels. */
|
|
46
|
+
export const HEAT_ALPHA_BASE = 0.22;
|
|
47
|
+
/** Additional alpha multiplier per dominant contour level. */
|
|
48
|
+
export const HEAT_ALPHA_MULTIPLIER = 0.45;
|
|
49
|
+
/** Alpha used for maximum heat color output. */
|
|
50
|
+
export const MAX_HEAT_ALPHA = 0.68;
|
|
51
|
+
/** Sampling step in pixels for rasterized heat field rendering. */
|
|
52
|
+
export const DEFAULT_SAMPLE_STEP = 4;
|
|
53
|
+
/** Minimum contour radius in pixels for visible heat blobs. */
|
|
54
|
+
export const MIN_CONTOUR_RADIUS = 14;
|
|
55
|
+
/** Height/width ratio used to derive contour radius from viewport size. */
|
|
56
|
+
export const CONTOUR_RADIUS_RATIO = 0.07;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface ViewportSize {
|
|
2
|
+
width: number;
|
|
3
|
+
height: number;
|
|
4
|
+
}
|
|
5
|
+
export interface HeatmapConfig {
|
|
6
|
+
mergeRadius: number;
|
|
7
|
+
maxHotspots: number;
|
|
8
|
+
}
|
|
9
|
+
export type HeatmapToolbarMode = "simple" | "hidden";
|
|
10
|
+
export interface HeatmapHotspot {
|
|
11
|
+
id: string;
|
|
12
|
+
x: number;
|
|
13
|
+
y: number;
|
|
14
|
+
count: number;
|
|
15
|
+
intensity: number;
|
|
16
|
+
}
|
|
17
|
+
export interface HeatmapSnapshot {
|
|
18
|
+
totalSamples: number;
|
|
19
|
+
trackedSince: number | null;
|
|
20
|
+
viewport: ViewportSize;
|
|
21
|
+
hotspots: HeatmapHotspot[];
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type HeatmapConfig, type HeatmapSnapshot, type ViewportSize } from "../contracts/heatmap-contracts.js";
|
|
2
|
+
/**
|
|
3
|
+
* Stores and aggregates pointer samples into clustered hotspots.
|
|
4
|
+
*/
|
|
5
|
+
export declare class HeatmapTracker {
|
|
6
|
+
private config;
|
|
7
|
+
private sampleCount;
|
|
8
|
+
private trackedSince;
|
|
9
|
+
private latestViewport;
|
|
10
|
+
private readonly trackedHotspots;
|
|
11
|
+
private nextHotspotId;
|
|
12
|
+
constructor(initialConfig?: Partial<HeatmapConfig>);
|
|
13
|
+
/**
|
|
14
|
+
* Updates clustering configuration.
|
|
15
|
+
*/
|
|
16
|
+
configure(nextConfig: Partial<HeatmapConfig>): void;
|
|
17
|
+
/**
|
|
18
|
+
* Records a pointer position and merges it into the nearest hotspot.
|
|
19
|
+
*/
|
|
20
|
+
recordPosition(x: number, y: number, viewport: ViewportSize): void;
|
|
21
|
+
/**
|
|
22
|
+
* Clears all tracked samples and hotspots.
|
|
23
|
+
*/
|
|
24
|
+
reset(): void;
|
|
25
|
+
/**
|
|
26
|
+
* Returns a snapshot suitable for rendering or analytics.
|
|
27
|
+
*/
|
|
28
|
+
getSnapshot(): HeatmapSnapshot;
|
|
29
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { DEFAULT_HEATMAP_CONFIG, MIN_MAX_HOTSPOTS, MIN_MERGE_RADIUS } from "../constants/constants.js";
|
|
2
|
+
function clamp(value, min, max) {
|
|
3
|
+
return Math.min(max, Math.max(min, value));
|
|
4
|
+
}
|
|
5
|
+
function getDistanceSquared(aX, aY, bX, bY) {
|
|
6
|
+
const dX = aX - bX;
|
|
7
|
+
const dY = aY - bY;
|
|
8
|
+
return dX * dX + dY * dY;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Stores and aggregates pointer samples into clustered hotspots.
|
|
12
|
+
*/
|
|
13
|
+
export class HeatmapTracker {
|
|
14
|
+
config;
|
|
15
|
+
sampleCount = 0;
|
|
16
|
+
trackedSince = null;
|
|
17
|
+
latestViewport = { width: 0, height: 0 };
|
|
18
|
+
trackedHotspots = [];
|
|
19
|
+
nextHotspotId = 0;
|
|
20
|
+
constructor(initialConfig = {}) {
|
|
21
|
+
this.config = { ...DEFAULT_HEATMAP_CONFIG };
|
|
22
|
+
this.configure(initialConfig);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Updates clustering configuration.
|
|
26
|
+
*/
|
|
27
|
+
configure(nextConfig) {
|
|
28
|
+
const mergeRadius = nextConfig.mergeRadius ?? this.config.mergeRadius;
|
|
29
|
+
const maxHotspots = nextConfig.maxHotspots ?? this.config.maxHotspots;
|
|
30
|
+
this.config = {
|
|
31
|
+
mergeRadius: Math.max(MIN_MERGE_RADIUS, mergeRadius),
|
|
32
|
+
maxHotspots: Math.max(MIN_MAX_HOTSPOTS, Math.floor(maxHotspots))
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Records a pointer position and merges it into the nearest hotspot.
|
|
37
|
+
*/
|
|
38
|
+
recordPosition(x, y, viewport) {
|
|
39
|
+
if (viewport.width <= 0 || viewport.height <= 0) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const clampedX = clamp(x, 0, viewport.width);
|
|
43
|
+
const clampedY = clamp(y, 0, viewport.height);
|
|
44
|
+
const mergeRadiusSquared = this.config.mergeRadius * this.config.mergeRadius;
|
|
45
|
+
let closest = null;
|
|
46
|
+
let closestDistance = Number.POSITIVE_INFINITY;
|
|
47
|
+
for (const hotspot of this.trackedHotspots) {
|
|
48
|
+
const distance = getDistanceSquared(clampedX, clampedY, hotspot.x, hotspot.y);
|
|
49
|
+
if (distance <= mergeRadiusSquared && distance < closestDistance) {
|
|
50
|
+
closest = hotspot;
|
|
51
|
+
closestDistance = distance;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (closest) {
|
|
55
|
+
const nextCount = closest.count + 1;
|
|
56
|
+
closest.x = (closest.x * closest.count + clampedX) / nextCount;
|
|
57
|
+
closest.y = (closest.y * closest.count + clampedY) / nextCount;
|
|
58
|
+
closest.count = nextCount;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
this.trackedHotspots.push({
|
|
62
|
+
id: `hs-${this.nextHotspotId++}`,
|
|
63
|
+
x: clampedX,
|
|
64
|
+
y: clampedY,
|
|
65
|
+
count: 1,
|
|
66
|
+
intensity: 0
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
if (this.trackedHotspots.length > this.config.maxHotspots) {
|
|
70
|
+
this.trackedHotspots.sort((a, b) => a.count - b.count);
|
|
71
|
+
this.trackedHotspots.shift();
|
|
72
|
+
}
|
|
73
|
+
this.sampleCount += 1;
|
|
74
|
+
this.latestViewport = viewport;
|
|
75
|
+
if (this.trackedSince === null) {
|
|
76
|
+
this.trackedSince = Date.now();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Clears all tracked samples and hotspots.
|
|
81
|
+
*/
|
|
82
|
+
reset() {
|
|
83
|
+
this.sampleCount = 0;
|
|
84
|
+
this.trackedSince = null;
|
|
85
|
+
this.latestViewport = { width: 0, height: 0 };
|
|
86
|
+
this.trackedHotspots.length = 0;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Returns a snapshot suitable for rendering or analytics.
|
|
90
|
+
*/
|
|
91
|
+
getSnapshot() {
|
|
92
|
+
let maxCount = 0;
|
|
93
|
+
for (const hotspot of this.trackedHotspots) {
|
|
94
|
+
if (hotspot.count > maxCount) {
|
|
95
|
+
maxCount = hotspot.count;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const hotspots = this.trackedHotspots
|
|
99
|
+
.map((hotspot) => ({
|
|
100
|
+
...hotspot,
|
|
101
|
+
intensity: maxCount === 0 ? 0 : hotspot.count / maxCount
|
|
102
|
+
}))
|
|
103
|
+
.sort((a, b) => b.count - a.count);
|
|
104
|
+
return {
|
|
105
|
+
totalSamples: this.sampleCount,
|
|
106
|
+
trackedSince: this.trackedSince,
|
|
107
|
+
viewport: { ...this.latestViewport },
|
|
108
|
+
hotspots
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { LitElement } from "lit";
|
|
2
|
+
export declare class HeatMapElement extends LitElement {
|
|
3
|
+
static properties: {
|
|
4
|
+
heatmapVisible: {
|
|
5
|
+
state: boolean;
|
|
6
|
+
};
|
|
7
|
+
toolbar: {
|
|
8
|
+
type: StringConstructor;
|
|
9
|
+
reflect: boolean;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
private heatmapVisible;
|
|
13
|
+
private toolbar;
|
|
14
|
+
private drawLoopId;
|
|
15
|
+
private readonly tracker;
|
|
16
|
+
static styles: import("lit").CSSResult;
|
|
17
|
+
constructor();
|
|
18
|
+
/**
|
|
19
|
+
* Stops frame rendering when element leaves the document.
|
|
20
|
+
*/
|
|
21
|
+
disconnectedCallback(): void;
|
|
22
|
+
/**
|
|
23
|
+
* Starts or stops rendering based on the overlay visibility.
|
|
24
|
+
*/
|
|
25
|
+
updated(): void;
|
|
26
|
+
/**
|
|
27
|
+
* Toggles heatmap overlay visibility.
|
|
28
|
+
*/
|
|
29
|
+
private toggleHeatmap;
|
|
30
|
+
/**
|
|
31
|
+
* Tracks pointer motion while overlay is hidden.
|
|
32
|
+
*/
|
|
33
|
+
private onPointerMove;
|
|
34
|
+
/**
|
|
35
|
+
* Creates a requestAnimationFrame loop for continuous heatmap drawing.
|
|
36
|
+
*/
|
|
37
|
+
private startDrawLoop;
|
|
38
|
+
/**
|
|
39
|
+
* Stops the active requestAnimationFrame drawing loop.
|
|
40
|
+
*/
|
|
41
|
+
private stopDrawLoop;
|
|
42
|
+
/**
|
|
43
|
+
* Renders the latest tracked hotspot data onto the overlay canvas.
|
|
44
|
+
*/
|
|
45
|
+
private drawHeatmap;
|
|
46
|
+
render(): import("lit-html").TemplateResult<1>;
|
|
47
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { LitElement, css, html } from "lit";
|
|
2
|
+
import { HeatmapTracker } from "./core/heatmap-tracker.js";
|
|
3
|
+
import { ELEMENT_TRACKER_CONFIG } from "./constants/constants.js";
|
|
4
|
+
import { renderHeatmapOverlay } from "./rendering/renderer.js";
|
|
5
|
+
export class HeatMapElement extends LitElement {
|
|
6
|
+
static properties = {
|
|
7
|
+
heatmapVisible: { state: true },
|
|
8
|
+
toolbar: { type: String, reflect: true }
|
|
9
|
+
};
|
|
10
|
+
drawLoopId = null;
|
|
11
|
+
tracker = new HeatmapTracker(ELEMENT_TRACKER_CONFIG);
|
|
12
|
+
static styles = css `
|
|
13
|
+
:host {
|
|
14
|
+
position: relative;
|
|
15
|
+
display: block;
|
|
16
|
+
min-height: 220px;
|
|
17
|
+
border-radius: 12px;
|
|
18
|
+
overflow: hidden;
|
|
19
|
+
isolation: isolate;
|
|
20
|
+
background: #f4f7ff;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.surface {
|
|
24
|
+
position: relative;
|
|
25
|
+
min-height: inherit;
|
|
26
|
+
width: 100%;
|
|
27
|
+
height: 100%;
|
|
28
|
+
z-index: 1;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.toggle {
|
|
32
|
+
position: absolute;
|
|
33
|
+
top: 0.55rem;
|
|
34
|
+
left: 0.55rem;
|
|
35
|
+
width: 1.9rem;
|
|
36
|
+
height: 1.9rem;
|
|
37
|
+
border-radius: 999px;
|
|
38
|
+
border: 1px solid rgba(255, 255, 255, 0.5);
|
|
39
|
+
background: rgba(15, 23, 42, 0.82);
|
|
40
|
+
color: #ffffff;
|
|
41
|
+
padding: 0;
|
|
42
|
+
line-height: 1;
|
|
43
|
+
font-size: 0.92rem;
|
|
44
|
+
font-weight: 700;
|
|
45
|
+
cursor: pointer;
|
|
46
|
+
z-index: 10001;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.toggle:hover {
|
|
50
|
+
background: rgba(2, 6, 23, 0.95);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.overlay {
|
|
54
|
+
position: absolute;
|
|
55
|
+
inset: 0;
|
|
56
|
+
width: 100%;
|
|
57
|
+
height: 100%;
|
|
58
|
+
pointer-events: none;
|
|
59
|
+
z-index: 10000;
|
|
60
|
+
}
|
|
61
|
+
`;
|
|
62
|
+
constructor() {
|
|
63
|
+
super();
|
|
64
|
+
this.heatmapVisible = false;
|
|
65
|
+
this.toolbar = "simple";
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Stops frame rendering when element leaves the document.
|
|
69
|
+
*/
|
|
70
|
+
disconnectedCallback() {
|
|
71
|
+
this.stopDrawLoop();
|
|
72
|
+
super.disconnectedCallback();
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Starts or stops rendering based on the overlay visibility.
|
|
76
|
+
*/
|
|
77
|
+
updated() {
|
|
78
|
+
if (this.toolbar !== "simple" && this.toolbar !== "hidden") {
|
|
79
|
+
this.toolbar = "simple";
|
|
80
|
+
}
|
|
81
|
+
if (this.heatmapVisible) {
|
|
82
|
+
this.startDrawLoop();
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
this.stopDrawLoop();
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Toggles heatmap overlay visibility.
|
|
89
|
+
*/
|
|
90
|
+
toggleHeatmap() {
|
|
91
|
+
this.heatmapVisible = !this.heatmapVisible;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Tracks pointer motion while overlay is hidden.
|
|
95
|
+
*/
|
|
96
|
+
onPointerMove(event) {
|
|
97
|
+
if (this.heatmapVisible) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const surface = this.renderRoot.querySelector(".surface");
|
|
101
|
+
if (!surface) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const rect = surface.getBoundingClientRect();
|
|
105
|
+
if (rect.width <= 0 || rect.height <= 0) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const x = Math.min(rect.width, Math.max(0, event.clientX - rect.left));
|
|
109
|
+
const y = Math.min(rect.height, Math.max(0, event.clientY - rect.top));
|
|
110
|
+
this.tracker.recordPosition(x, y, { width: rect.width, height: rect.height });
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Creates a requestAnimationFrame loop for continuous heatmap drawing.
|
|
114
|
+
*/
|
|
115
|
+
startDrawLoop() {
|
|
116
|
+
if (this.drawLoopId !== null) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const drawFrame = () => {
|
|
120
|
+
this.drawHeatmap();
|
|
121
|
+
this.drawLoopId = window.requestAnimationFrame(drawFrame);
|
|
122
|
+
};
|
|
123
|
+
this.drawLoopId = window.requestAnimationFrame(drawFrame);
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Stops the active requestAnimationFrame drawing loop.
|
|
127
|
+
*/
|
|
128
|
+
stopDrawLoop() {
|
|
129
|
+
if (this.drawLoopId === null) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
window.cancelAnimationFrame(this.drawLoopId);
|
|
133
|
+
this.drawLoopId = null;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Renders the latest tracked hotspot data onto the overlay canvas.
|
|
137
|
+
*/
|
|
138
|
+
drawHeatmap() {
|
|
139
|
+
const canvas = this.renderRoot.querySelector("#heatmap");
|
|
140
|
+
const surface = this.renderRoot.querySelector(".surface");
|
|
141
|
+
if (!canvas || !surface) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const width = Math.round(surface.clientWidth);
|
|
145
|
+
const height = Math.round(surface.clientHeight);
|
|
146
|
+
if (width <= 0 || height <= 0) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (canvas.width !== width || canvas.height !== height) {
|
|
150
|
+
canvas.width = width;
|
|
151
|
+
canvas.height = height;
|
|
152
|
+
}
|
|
153
|
+
const context = canvas.getContext("2d");
|
|
154
|
+
if (!context) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const snapshot = this.tracker.getSnapshot();
|
|
158
|
+
renderHeatmapOverlay(context, width, height, snapshot.hotspots);
|
|
159
|
+
}
|
|
160
|
+
render() {
|
|
161
|
+
return html `
|
|
162
|
+
<div class="surface" @pointermove=${this.onPointerMove}>
|
|
163
|
+
<slot></slot>
|
|
164
|
+
</div>
|
|
165
|
+
${this.toolbar === "hidden"
|
|
166
|
+
? null
|
|
167
|
+
: html `<button class="toggle" @click=${this.toggleHeatmap} aria-label="Toggle heatmap">
|
|
168
|
+
${this.heatmapVisible ? "x" : "*"}
|
|
169
|
+
</button>`}
|
|
170
|
+
${this.heatmapVisible ? html `<canvas id="heatmap" class="overlay"></canvas>` : null}
|
|
171
|
+
`;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
if (typeof customElements !== "undefined" && !customElements.get("heat-map")) {
|
|
175
|
+
customElements.define("heat-map", HeatMapElement);
|
|
176
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { HeatmapTracker } from "./core/heatmap-tracker.js";
|
|
2
|
+
import { DEFAULT_HEATMAP_CONFIG } from "./constants/constants.js";
|
|
3
|
+
import { HeatMapElement } from "./heatmap-element.js";
|
|
4
|
+
import { type HeatmapConfig, type HeatmapSnapshot, type ViewportSize } from "./contracts/heatmap-contracts.js";
|
|
5
|
+
export declare const hello = "hello";
|
|
6
|
+
export { HeatMapElement };
|
|
7
|
+
export { DEFAULT_HEATMAP_CONFIG, HeatmapTracker };
|
|
8
|
+
export type { HeatmapConfig, HeatmapHotspot, HeatmapToolbarMode, HeatmapSnapshot, ViewportSize } from "./contracts/heatmap-contracts.js";
|
|
9
|
+
/**
|
|
10
|
+
* Updates tracker settings for globally tracked mouse heatmap data.
|
|
11
|
+
*/
|
|
12
|
+
export declare function configureMouseHeatmap(nextConfig: Partial<HeatmapConfig>): void;
|
|
13
|
+
/**
|
|
14
|
+
* Records a single mouse position sample into the global tracker.
|
|
15
|
+
*/
|
|
16
|
+
export declare function recordMousePosition(x: number, y: number, viewport?: ViewportSize): void;
|
|
17
|
+
/**
|
|
18
|
+
* Starts browser pointer tracking for the global tracker.
|
|
19
|
+
*/
|
|
20
|
+
export declare function startMouseTracking(): void;
|
|
21
|
+
/**
|
|
22
|
+
* Stops browser pointer tracking for the global tracker.
|
|
23
|
+
*/
|
|
24
|
+
export declare function stopMouseTracking(): void;
|
|
25
|
+
/**
|
|
26
|
+
* Clears all currently tracked global heatmap data.
|
|
27
|
+
*/
|
|
28
|
+
export declare function resetMouseHeatmap(): void;
|
|
29
|
+
/**
|
|
30
|
+
* Returns the latest global heatmap snapshot.
|
|
31
|
+
*/
|
|
32
|
+
export declare function getMouseHeatmapData(): HeatmapSnapshot;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { HeatmapTracker } from "./core/heatmap-tracker.js";
|
|
2
|
+
import { DEFAULT_HEATMAP_CONFIG } from "./constants/constants.js";
|
|
3
|
+
import { HeatMapElement } from "./heatmap-element.js";
|
|
4
|
+
export const hello = "hello";
|
|
5
|
+
export { HeatMapElement };
|
|
6
|
+
export { DEFAULT_HEATMAP_CONFIG, HeatmapTracker };
|
|
7
|
+
const globalTracker = new HeatmapTracker(DEFAULT_HEATMAP_CONFIG);
|
|
8
|
+
let moveListener = null;
|
|
9
|
+
function getViewportFromWindow() {
|
|
10
|
+
if (typeof window === "undefined") {
|
|
11
|
+
return { width: 0, height: 0 };
|
|
12
|
+
}
|
|
13
|
+
return {
|
|
14
|
+
width: Math.max(0, window.innerWidth),
|
|
15
|
+
height: Math.max(0, window.innerHeight)
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Updates tracker settings for globally tracked mouse heatmap data.
|
|
20
|
+
*/
|
|
21
|
+
export function configureMouseHeatmap(nextConfig) {
|
|
22
|
+
globalTracker.configure(nextConfig);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Records a single mouse position sample into the global tracker.
|
|
26
|
+
*/
|
|
27
|
+
export function recordMousePosition(x, y, viewport = getViewportFromWindow()) {
|
|
28
|
+
globalTracker.recordPosition(x, y, viewport);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Starts browser pointer tracking for the global tracker.
|
|
32
|
+
*/
|
|
33
|
+
export function startMouseTracking() {
|
|
34
|
+
if (moveListener || typeof window === "undefined") {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
moveListener = (event) => {
|
|
38
|
+
recordMousePosition(event.clientX, event.clientY, {
|
|
39
|
+
width: window.innerWidth,
|
|
40
|
+
height: window.innerHeight
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
window.addEventListener("pointermove", moveListener, { passive: true });
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Stops browser pointer tracking for the global tracker.
|
|
47
|
+
*/
|
|
48
|
+
export function stopMouseTracking() {
|
|
49
|
+
if (!moveListener || typeof window === "undefined") {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
window.removeEventListener("pointermove", moveListener);
|
|
53
|
+
moveListener = null;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Clears all currently tracked global heatmap data.
|
|
57
|
+
*/
|
|
58
|
+
export function resetMouseHeatmap() {
|
|
59
|
+
globalTracker.reset();
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Returns the latest global heatmap snapshot.
|
|
63
|
+
*/
|
|
64
|
+
export function getMouseHeatmapData() {
|
|
65
|
+
return globalTracker.getSnapshot();
|
|
66
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type HeatmapHotspot } from "../contracts/heatmap-contracts.js";
|
|
2
|
+
import { type HeatColor, type HeatmapRenderOptions } from "../contracts/rendering-contracts.js";
|
|
3
|
+
/**
|
|
4
|
+
* Converts an absolute hotspot count into a relative strength ratio.
|
|
5
|
+
*/
|
|
6
|
+
export declare function getRelativeStrength(count: number, maxCount: number): number;
|
|
7
|
+
/**
|
|
8
|
+
* Interpolates the heat scale into a single RGB color.
|
|
9
|
+
*/
|
|
10
|
+
export declare function interpolateHeatColor(strength: number, colors?: readonly HeatColor[]): [number, number, number];
|
|
11
|
+
/**
|
|
12
|
+
* Calculates merged field strength at a specific coordinate.
|
|
13
|
+
*/
|
|
14
|
+
export declare function calculateFieldStrength(x: number, y: number, hotspots: Array<{
|
|
15
|
+
x: number;
|
|
16
|
+
y: number;
|
|
17
|
+
count: number;
|
|
18
|
+
}>, radius: number, maxHotspotCount: number): number;
|
|
19
|
+
/**
|
|
20
|
+
* Maps field strength into a discrete dominant color and alpha.
|
|
21
|
+
*/
|
|
22
|
+
export declare function getDominantHeatColor(strength: number, colors?: readonly HeatColor[]): [number, number, number, number];
|
|
23
|
+
/**
|
|
24
|
+
* Draws a contour-style heatmap overlay from hotspots onto a canvas context.
|
|
25
|
+
*/
|
|
26
|
+
export declare function renderHeatmapOverlay(context: CanvasRenderingContext2D, width: number, height: number, hotspots: HeatmapHotspot[], options?: HeatmapRenderOptions): void;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { CONTOUR_RADIUS_RATIO, DEFAULT_HEAT_COLORS, DEFAULT_SAMPLE_STEP, DOMINANT_HEAT_LEVELS, FIELD_DISTANCE_MULTIPLIER, FIELD_FALLOFF_DIVISOR, HEAT_ALPHA_BASE, HEAT_ALPHA_MULTIPLIER, LOW_HEAT_ALPHA, LOW_HEAT_THRESHOLD, MAX_HEAT_ALPHA, MIN_CONTOUR_RADIUS, RELATIVE_STRENGTH_EXPONENT } from "../constants/constants.js";
|
|
2
|
+
/**
|
|
3
|
+
* Converts an absolute hotspot count into a relative strength ratio.
|
|
4
|
+
*/
|
|
5
|
+
export function getRelativeStrength(count, maxCount) {
|
|
6
|
+
if (maxCount <= 0) {
|
|
7
|
+
return 0;
|
|
8
|
+
}
|
|
9
|
+
return Math.pow(count / maxCount, RELATIVE_STRENGTH_EXPONENT);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Interpolates the heat scale into a single RGB color.
|
|
13
|
+
*/
|
|
14
|
+
export function interpolateHeatColor(strength, colors = DEFAULT_HEAT_COLORS) {
|
|
15
|
+
const segments = colors.length - 1;
|
|
16
|
+
const scaled = Math.max(0, Math.min(1, strength)) * segments;
|
|
17
|
+
const lowerIndex = Math.floor(scaled);
|
|
18
|
+
const upperIndex = Math.min(segments, lowerIndex + 1);
|
|
19
|
+
const localT = scaled - lowerIndex;
|
|
20
|
+
const low = colors[lowerIndex];
|
|
21
|
+
const high = colors[upperIndex];
|
|
22
|
+
return [
|
|
23
|
+
Math.round(low[0] + (high[0] - low[0]) * localT),
|
|
24
|
+
Math.round(low[1] + (high[1] - low[1]) * localT),
|
|
25
|
+
Math.round(low[2] + (high[2] - low[2]) * localT)
|
|
26
|
+
];
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Calculates merged field strength at a specific coordinate.
|
|
30
|
+
*/
|
|
31
|
+
export function calculateFieldStrength(x, y, hotspots, radius, maxHotspotCount) {
|
|
32
|
+
const radiusSquared = radius * radius;
|
|
33
|
+
let strength = 0;
|
|
34
|
+
for (const spot of hotspots) {
|
|
35
|
+
const dx = x - spot.x;
|
|
36
|
+
const dy = y - spot.y;
|
|
37
|
+
const distanceSquared = dx * dx + dy * dy;
|
|
38
|
+
if (distanceSquared > radiusSquared * FIELD_DISTANCE_MULTIPLIER) {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
const localStrength = getRelativeStrength(spot.count, maxHotspotCount);
|
|
42
|
+
const falloff = Math.exp(-distanceSquared / (radiusSquared * FIELD_FALLOFF_DIVISOR));
|
|
43
|
+
strength += localStrength * falloff;
|
|
44
|
+
}
|
|
45
|
+
return Math.min(1, strength);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Maps field strength into a discrete dominant color and alpha.
|
|
49
|
+
*/
|
|
50
|
+
export function getDominantHeatColor(strength, colors = DEFAULT_HEAT_COLORS) {
|
|
51
|
+
if (strength <= 0) {
|
|
52
|
+
return [0, 0, 0, 0];
|
|
53
|
+
}
|
|
54
|
+
if (strength < LOW_HEAT_THRESHOLD) {
|
|
55
|
+
const [r, g, b] = interpolateHeatColor(0, colors);
|
|
56
|
+
return [r, g, b, LOW_HEAT_ALPHA];
|
|
57
|
+
}
|
|
58
|
+
for (const level of DOMINANT_HEAT_LEVELS) {
|
|
59
|
+
if (strength <= level) {
|
|
60
|
+
const [r, g, b] = interpolateHeatColor(level, colors);
|
|
61
|
+
return [r, g, b, HEAT_ALPHA_BASE + level * HEAT_ALPHA_MULTIPLIER];
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const [r, g, b] = interpolateHeatColor(1, colors);
|
|
65
|
+
return [r, g, b, MAX_HEAT_ALPHA];
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Draws a contour-style heatmap overlay from hotspots onto a canvas context.
|
|
69
|
+
*/
|
|
70
|
+
export function renderHeatmapOverlay(context, width, height, hotspots, options = {}) {
|
|
71
|
+
context.clearRect(0, 0, width, height);
|
|
72
|
+
if (hotspots.length === 0) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const sampleStep = options.sampleStep ?? DEFAULT_SAMPLE_STEP;
|
|
76
|
+
const maxContourRadius = options.maxContourRadius ?? Math.max(MIN_CONTOUR_RADIUS, Math.min(width, height) * CONTOUR_RADIUS_RATIO);
|
|
77
|
+
const colors = options.colors ?? DEFAULT_HEAT_COLORS;
|
|
78
|
+
const maxHotspotCount = hotspots.reduce((max, spot) => Math.max(max, spot.count), 1);
|
|
79
|
+
const sampleWidth = Math.ceil(width / sampleStep);
|
|
80
|
+
const sampleHeight = Math.ceil(height / sampleStep);
|
|
81
|
+
const image = context.createImageData(sampleWidth, sampleHeight);
|
|
82
|
+
for (let y = 0; y < sampleHeight; y += 1) {
|
|
83
|
+
for (let x = 0; x < sampleWidth; x += 1) {
|
|
84
|
+
const sampleX = x * sampleStep;
|
|
85
|
+
const sampleY = y * sampleStep;
|
|
86
|
+
const strength = calculateFieldStrength(sampleX, sampleY, hotspots, maxContourRadius, maxHotspotCount);
|
|
87
|
+
const [r, g, b, a] = getDominantHeatColor(strength, colors);
|
|
88
|
+
const index = (y * sampleWidth + x) * 4;
|
|
89
|
+
image.data[index] = r;
|
|
90
|
+
image.data[index + 1] = g;
|
|
91
|
+
image.data[index + 2] = b;
|
|
92
|
+
image.data[index + 3] = Math.round(a * 255);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const offscreen = document.createElement("canvas");
|
|
96
|
+
offscreen.width = sampleWidth;
|
|
97
|
+
offscreen.height = sampleHeight;
|
|
98
|
+
const offscreenContext = offscreen.getContext("2d");
|
|
99
|
+
if (!offscreenContext) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
offscreenContext.putImageData(image, 0, 0);
|
|
103
|
+
context.imageSmoothingEnabled = true;
|
|
104
|
+
context.drawImage(offscreen, 0, 0, sampleWidth, sampleHeight, 0, 0, width, height);
|
|
105
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "heatspot",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A tiny ESM TypeScript library.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"prebuild": "rimraf dist",
|
|
19
|
+
"build": "tsc -p tsconfig.build.json",
|
|
20
|
+
"build:verify": "npm run test:coverage && npm run build:harness",
|
|
21
|
+
"prepublishOnly": "npm run test && npm run build",
|
|
22
|
+
"pack:check": "npm pack --dry-run",
|
|
23
|
+
"start": "npm run build && vite --config harness/vite.config.ts",
|
|
24
|
+
"build:harness": "npm run build && vite build --config harness/vite.config.ts",
|
|
25
|
+
"lint": "eslint .",
|
|
26
|
+
"lint:fix": "eslint . --fix",
|
|
27
|
+
"test": "vitest run",
|
|
28
|
+
"test:coverage": "vitest run --coverage",
|
|
29
|
+
"test:check": "tsc -p tsconfig.json --noEmit",
|
|
30
|
+
"test:watch": "vitest"
|
|
31
|
+
},
|
|
32
|
+
"keywords": [
|
|
33
|
+
"heatmap",
|
|
34
|
+
"lit",
|
|
35
|
+
"web-components",
|
|
36
|
+
"typescript",
|
|
37
|
+
"esm"
|
|
38
|
+
],
|
|
39
|
+
"author": "",
|
|
40
|
+
"license": "MIT",
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=18"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"lit": "^3.3.2"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@eslint/js": "^9.21.0",
|
|
49
|
+
"@types/node": "^25.3.5",
|
|
50
|
+
"@typescript-eslint/eslint-plugin": "^8.26.1",
|
|
51
|
+
"@typescript-eslint/parser": "^8.26.1",
|
|
52
|
+
"eslint": "^9.21.0",
|
|
53
|
+
"eslint-plugin-import": "^2.31.0",
|
|
54
|
+
"globals": "^16.0.0",
|
|
55
|
+
"jsdom": "^26.0.0",
|
|
56
|
+
"rimraf": "^6.0.1",
|
|
57
|
+
"typescript": "^5.9.3",
|
|
58
|
+
"vite": "^7.3.1",
|
|
59
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
60
|
+
"vitest": "^4.0.18"
|
|
61
|
+
}
|
|
62
|
+
}
|