awesome-heatmap 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 ADDED
@@ -0,0 +1,23 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
4
+ AwesomeHeatmap Contributors
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
23
+
package/README.md ADDED
@@ -0,0 +1,358 @@
1
+ # awesome-heatmap
2
+
3
+ A zero-dependency **heatmap grid** React component. Feed it any data shape — arrays of objects, REST API URLs, GeoJSON, CSV-style nested arrays — and it auto-extracts a numeric matrix, renders colour-coded cells, and gives you a full toolbar with colour scale picker, row sorting, cell size slider, theme switcher, and a floating tooltip. All styles are injected at runtime, so no CSS import is needed.
4
+
5
+ ---
6
+
7
+ ## Features
8
+
9
+ - **Universal data ingestion** — arrays of objects, URL strings (JSON / REST API), GeoJSON FeatureCollections, CSV-style `[[headers], [row], ...]`, envelope-wrapped responses (`{ data: [...] }`, `{ results: [...] }`, etc.)
10
+ - **Auto matrix extraction** — detects numeric columns (≥ 75% parseable values), picks the first non-numeric column as the row label, flattens nested objects up to 3 levels deep
11
+ - **8 colour scales** — Plasma, Inferno, Viridis, Magma, Thermal, Ice, Red→Blue, Spectral — all using perceptually uniform control-point interpolation
12
+ - **3 built-in themes** — `dark`, `light`, `ember` — all colours driven by CSS custom properties
13
+ - **Toolbar controls** — colour scale dropdown with preview dots, row sort (original / A→Z / Z→A / sum↑ / sum↓), cell size slider (16–80px), show/hide cell values toggle, theme switcher
14
+ - **Column header mini-bars** — each column header shows a colour-coded bar for its average value relative to the global range
15
+ - **Sticky row labels + column headers** — both stay in view while scrolling a large matrix
16
+ - **Floating tooltip** — shows row label, column label, formatted value, and a colour-matched progress bar
17
+ - **Summary footer** — global Σ, x̄, max, min, and cell count
18
+ - **IndexedDB preference persistence** — theme, scale, sort, cell size, and show-values are saved per `storageKey` and restored on next load
19
+ - **Self-contained styles** — CSS Module replaced with a runtime-injected `<style>` tag; no stylesheet import required
20
+ - **ESM + CJS dual build** — works in Vite, Next.js, CRA, and any modern bundler
21
+ - **Zero runtime dependencies** beyond React
22
+
23
+ ---
24
+
25
+ ## Installation
26
+
27
+ ```bash
28
+ npm install awesome-heatmap
29
+ # or
30
+ yarn add awesome-heatmap
31
+ # or
32
+ pnpm add awesome-heatmap
33
+ ```
34
+
35
+ > **Peer dependencies** — React ≥ 18 and ReactDOM ≥ 18 must already be installed.
36
+
37
+ ---
38
+
39
+ ## Quick Start
40
+
41
+ No CSS import needed — styles are injected automatically.
42
+
43
+ ```jsx
44
+ import { AwesomeHeatmap } from 'awesome-heatmap';
45
+
46
+ var data = [
47
+ { city: 'New York', jan: 2, feb: 4, mar: 10, apr: 17, may: 23, jun: 28 },
48
+ { city: 'London', jan: 5, feb: 6, mar: 9, apr: 13, may: 17, jun: 20 },
49
+ { city: 'Tokyo', jan: 6, feb: 7, mar: 11, apr: 17, may: 22, jun: 26 },
50
+ { city: 'Sydney', jan: 26, feb: 26, mar: 23, apr: 18, may: 14, jun: 11 },
51
+ { city: 'Dubai', jan: 19, feb: 21, mar: 25, apr: 33, may: 38, jun: 40 },
52
+ ];
53
+
54
+ export default function App() {
55
+ return (
56
+ <div style={{ width: '100%', height: '500px' }}>
57
+ <AwesomeHeatmap
58
+ data={data}
59
+ title="Monthly Temperatures (°C)"
60
+ />
61
+ </div>
62
+ );
63
+ }
64
+ ```
65
+
66
+ ---
67
+
68
+ ## Props
69
+
70
+ | Prop | Type | Default | Description |
71
+ |---|---|---|---|
72
+ | `data` | `Array \| string` | `[]` | Data to visualise. Pass an array of objects, or a URL string pointing to a JSON endpoint. See [Data formats](#-data-formats) below. |
73
+ | `title` | `string` | `'Heatmap'` | Title shown in the toolbar. |
74
+ | `theme` | `'dark' \| 'light' \| 'ember'` | `'dark'` | Initial colour theme. User can switch at runtime via the toolbar. |
75
+ | `colorScale` | `string` | `'plasma'` | Initial colour scale key. Must be one of the `COLOR_SCALES` keys. |
76
+ | `storageKey` | `string` | `'awesome-heatmap'` | IndexedDB key used to persist and restore user preferences. Pass a unique value per instance. Set to `''` to disable persistence. |
77
+ | `height` | `string \| number` | `'100%'` | CSS height of the component. Numbers are treated as px. |
78
+ | `onThemeChange` | `(theme: string) => void` | — | Called whenever the user switches theme, with the new theme name. |
79
+
80
+ ---
81
+
82
+ ## Named Exports
83
+
84
+ ### `COLOR_SCALES`
85
+
86
+ An object mapping scale keys to display labels. Use this to build your own scale picker or to pass a valid value to the `colorScale` prop.
87
+
88
+ ```js
89
+ import { COLOR_SCALES } from 'awesome-heatmap';
90
+
91
+ console.log(COLOR_SCALES);
92
+ // {
93
+ // plasma: "Plasma",
94
+ // inferno: "Inferno",
95
+ // viridis: "Viridis",
96
+ // magma: "Magma",
97
+ // thermal: "Thermal",
98
+ // ice: "Ice",
99
+ // rdbu: "Red → Blue",
100
+ // spectral: "Spectral",
101
+ // }
102
+ ```
103
+
104
+ ### `PAGE_BG`
105
+
106
+ An object mapping theme names to their recommended outer page background colours. Useful for styling the page wrapper to match the heatmap.
107
+
108
+ ```js
109
+ import { PAGE_BG } from 'awesome-heatmap';
110
+
111
+ console.log(PAGE_BG);
112
+ // { dark: "#04060c", light: "#e0e5f0", ember: "#070402" }
113
+ ```
114
+
115
+ ---
116
+
117
+ ## Data Formats
118
+
119
+ AwesomeHeatmap accepts data in many shapes and normalises them automatically.
120
+
121
+ ### Array of objects (recommended)
122
+
123
+ ```js
124
+ var data = [
125
+ { product: 'Shoes', mon: 120, tue: 95, wed: 143 },
126
+ { product: 'Bags', mon: 88, tue: 112, wed: 76 },
127
+ { product: 'Hats', mon: 54, tue: 67, wed: 91 },
128
+ ];
129
+ ```
130
+
131
+ The first non-numeric column (`product`) becomes the row label. All numeric columns become the heatmap columns.
132
+
133
+ ### URL string — JSON endpoint
134
+
135
+ ```jsx
136
+ <AwesomeHeatmap data="https://api.example.com/metrics" title="Server Metrics" />
137
+ ```
138
+
139
+ The component fetches the URL, then unwraps common envelope keys (`data`, `results`, `items`, `rows`, etc.) automatically.
140
+
141
+ ### Envelope-wrapped response
142
+
143
+ ```json
144
+ { "results": [ { "region": "APAC", "q1": 4.2, "q2": 5.1, "q3": 3.8 } ] }
145
+ ```
146
+
147
+ ### CSV-style nested arrays (first row = headers)
148
+
149
+ ```js
150
+ var data = [
151
+ ['city', 'q1', 'q2', 'q3', 'q4'],
152
+ ['Berlin', 8.2, 14.1, 19.3, 9.5 ],
153
+ ['Paris', 9.1, 15.0, 20.2, 10.1],
154
+ ['Madrid', 11.5, 17.3, 24.8, 13.2],
155
+ ];
156
+ ```
157
+
158
+ ### GeoJSON FeatureCollection
159
+
160
+ ```js
161
+ var geojson = {
162
+ type: 'FeatureCollection',
163
+ features: [
164
+ { type: 'Feature', geometry: { type: 'Point', coordinates: [0,0] }, properties: { name: 'A', value: 42 } },
165
+ ],
166
+ };
167
+ ```
168
+
169
+ Properties are extracted automatically; the geometry is ignored.
170
+
171
+ ---
172
+
173
+ ## Examples
174
+
175
+ ### Example 1 — Dark theme with Viridis scale and persistence disabled
176
+
177
+ ```jsx
178
+ import { AwesomeHeatmap } from 'awesome-heatmap';
179
+
180
+ var salesData = [
181
+ { rep: 'Alice', jan: 42000, feb: 38000, mar: 51000, apr: 47000, may: 55000, jun: 61000 },
182
+ { rep: 'Bob', jan: 31000, feb: 44000, mar: 39000, apr: 52000, may: 48000, jun: 43000 },
183
+ { rep: 'Carol', jan: 58000, feb: 62000, mar: 55000, apr: 67000, may: 71000, jun: 69000 },
184
+ { rep: 'David', jan: 27000, feb: 31000, mar: 35000, apr: 29000, may: 38000, jun: 42000 },
185
+ { rep: 'Eva', jan: 49000, feb: 53000, mar: 61000, apr: 58000, may: 64000, jun: 72000 },
186
+ { rep: 'Frank', jan: 36000, feb: 41000, mar: 38000, apr: 44000, may: 46000, jun: 50000 },
187
+ ];
188
+
189
+ export default function SalesDashboard() {
190
+ return (
191
+ <div style={{ width: '100%', height: '480px', borderRadius: 16, overflow: 'hidden' }}>
192
+ <AwesomeHeatmap
193
+ data={salesData}
194
+ title="Sales by Rep — H1"
195
+ theme="dark"
196
+ colorScale="viridis"
197
+ storageKey=""
198
+ />
199
+ </div>
200
+ );
201
+ }
202
+ ```
203
+
204
+ ---
205
+
206
+ ### Example 2 — Light theme, live data from a REST API, `onThemeChange` callback
207
+
208
+ ```jsx
209
+ import { useState } from 'react';
210
+ import { AwesomeHeatmap, PAGE_BG } from 'awesome-heatmap';
211
+
212
+ export default function LiveMetrics() {
213
+ var [theme, setTheme] = useState('light');
214
+
215
+ return (
216
+ <div style={{ background: PAGE_BG[theme], minHeight: '100vh', padding: 32, transition: 'background 0.4s' }}>
217
+ <div style={{ width: '100%', height: '600px', borderRadius: 20, overflow: 'hidden', boxShadow: '0 4px 24px rgba(0,0,0,0.12)' }}>
218
+ <AwesomeHeatmap
219
+ data="https://jsonplaceholder.typicode.com/users"
220
+ title="User Metrics (live)"
221
+ theme={theme}
222
+ colorScale="thermal"
223
+ storageKey="live-metrics-heatmap"
224
+ onThemeChange={function(t) { setTheme(t); }}
225
+ />
226
+ </div>
227
+ </div>
228
+ );
229
+ }
230
+ ```
231
+
232
+ ---
233
+
234
+ ### Example 3 — Ember theme, CSV-style array, controlled height, multiple instances
235
+
236
+ ```jsx
237
+ import { AwesomeHeatmap, COLOR_SCALES } from 'awesome-heatmap';
238
+
239
+ var weeklyTraffic = [
240
+ ['page', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'],
241
+ ['Home', 9420, 8830, 9210, 9640, 11200, 8100, 7300],
242
+ ['Products', 4210, 4650, 4120, 4980, 5340, 3800, 3200],
243
+ ['Blog', 2100, 2340, 2890, 2670, 2450, 3100, 2900],
244
+ ['Pricing', 1840, 2010, 1950, 2200, 2680, 1400, 1100],
245
+ ['Contact', 820, 910, 880, 970, 1050, 640, 580 ],
246
+ ['Docs', 3400, 3800, 4200, 4100, 3900, 2200, 1900],
247
+ ];
248
+
249
+ var teamPerformance = [
250
+ ['team', 'sprints_completed', 'bugs_closed', 'prs_merged', 'reviews_done', 'deploys'],
251
+ ['Frontend', 8, 24, 31, 42, 12],
252
+ ['Backend', 6, 38, 22, 28, 9],
253
+ ['Mobile', 7, 19, 17, 24, 6],
254
+ ['DevOps', 5, 11, 8, 15, 28],
255
+ ['Data', 4, 14, 12, 18, 4],
256
+ ];
257
+
258
+ export default function MultiHeatmap() {
259
+ return (
260
+ <div style={{ background: '#070402', minHeight: '100vh', padding: 32, display: 'flex', flexDirection: 'column', gap: 24 }}>
261
+
262
+ <div style={{ height: 420, borderRadius: 16, overflow: 'hidden' }}>
263
+ <AwesomeHeatmap
264
+ data={weeklyTraffic}
265
+ title="Weekly Page Traffic"
266
+ theme="ember"
267
+ colorScale="plasma"
268
+ storageKey="traffic-heatmap"
269
+ />
270
+ </div>
271
+
272
+ <div style={{ height: 360, borderRadius: 16, overflow: 'hidden' }}>
273
+ <AwesomeHeatmap
274
+ data={teamPerformance}
275
+ title="Team Performance — Current Sprint"
276
+ theme="ember"
277
+ colorScale="inferno"
278
+ storageKey="team-heatmap"
279
+ />
280
+ </div>
281
+
282
+ {/* Display available colour scales */}
283
+ <div style={{ color: '#a0836a', fontFamily: 'monospace', fontSize: 12 }}>
284
+ Available scales: {Object.keys(COLOR_SCALES).join(', ')}
285
+ </div>
286
+ </div>
287
+ );
288
+ }
289
+ ```
290
+
291
+ ---
292
+
293
+ No separate CSS file is produced. All styles are bundled as a JavaScript string and injected into `<head>` when the component first mounts.
294
+
295
+
296
+ ## Toolbar Controls
297
+
298
+ | Control | Description |
299
+ |---|---|
300
+ | **Cell size slider** | Resize all cells from 16px to 80px. Values are shown inside cells when size ≥ 28px and the eye toggle is on. |
301
+ | **Eye toggle** | Show or hide numeric values inside each cell. |
302
+ | **Colour scale dropdown** | Switch between the 8 available scales. Each option shows a preview strip. |
303
+ | **Sort dropdown** | Sort rows by: original order, label A→Z / Z→A, or row sum ascending / descending. |
304
+ | **Theme dots** | Switch between Dark (blue), Light (indigo), and Ember (orange) themes. |
305
+
306
+ ---
307
+
308
+ ## CSS Custom Properties
309
+
310
+ All visual tokens are CSS custom properties set on the root element. Override them per-instance via a wrapper class:
311
+
312
+ ```css
313
+ .my-heatmap-wrapper {
314
+ --hm-bg: #0a0a0a;
315
+ --hm-surface: #141414;
316
+ --hm-surface2: #1c1c1c;
317
+ --hm-surface3: #242424;
318
+ --hm-border: rgba(255,255,255,0.06);
319
+ --hm-border2: rgba(255,255,255,0.12);
320
+ --hm-text1: #f0f0f0;
321
+ --hm-text2: #888;
322
+ --hm-text3: #444;
323
+ --hm-accent: #7c3aed;
324
+ --hm-accentbg: rgba(124,58,237,0.12);
325
+ --hm-shadow: 0 8px 40px rgba(0,0,0,0.7);
326
+ }
327
+ ```
328
+
329
+ Then wrap the component:
330
+
331
+ ```jsx
332
+ <div className="my-heatmap-wrapper" style={{ height: 500 }}>
333
+ <AwesomeHeatmap data={data} />
334
+ </div>
335
+ ```
336
+
337
+ ---
338
+
339
+ ## Preference Persistence
340
+
341
+ User preferences (theme, colour scale, sort order, cell size, show-values toggle) are automatically saved to IndexedDB under the `storageKey` and restored on the next mount. To use multiple independent instances on the same page, pass a unique `storageKey` to each:
342
+
343
+ ```jsx
344
+ <AwesomeHeatmap data={salesData} storageKey="sales-heatmap" />
345
+ <AwesomeHeatmap data={trafficData} storageKey="traffic-heatmap" />
346
+ ```
347
+
348
+ To disable persistence entirely, pass an empty string:
349
+
350
+ ```jsx
351
+ <AwesomeHeatmap data={data} storageKey="" />
352
+ ```
353
+
354
+ ---
355
+
356
+ ## License
357
+
358
+ MIT
@@ -0,0 +1,215 @@
1
+ "use strict";const t=require("react/jsx-runtime"),c=require("react"),e="ahm",B=(...s)=>s.filter(Boolean).join(" "),ue=`
2
+ @import url("https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&family=Outfit:wght@400;500;600;700&display=swap");
3
+
4
+ .${e}-root {
5
+ display: flex; flex-direction: column; overflow: hidden;
6
+ background: var(--hm-bg); color: var(--hm-text1);
7
+ font-family: "Outfit", sans-serif;
8
+ border-radius: 16px; box-shadow: var(--hm-shadow);
9
+ position: relative; container-type: inline-size;
10
+ }
11
+ .${e}-toolbar {
12
+ display: flex; align-items: center; justify-content: space-between;
13
+ padding: 0 16px; height: 52px; flex-shrink: 0;
14
+ border-bottom: 1px solid var(--hm-border);
15
+ background: var(--hm-surface); gap: 12px;
16
+ }
17
+ .${e}-toolbarL { display: flex; align-items: center; gap: 10px; overflow: hidden; }
18
+ .${e}-toolbarR { display: flex; align-items: center; gap: 6px; flex-shrink: 0; }
19
+ .${e}-logoMark { display: flex; align-items: center; flex-shrink: 0; }
20
+ .${e}-title {
21
+ font-family: "Outfit", sans-serif; font-size: 15px; font-weight: 600;
22
+ color: var(--hm-text1); white-space: nowrap; overflow: hidden;
23
+ text-overflow: ellipsis; letter-spacing: -.01em;
24
+ }
25
+ .${e}-dimBadge {
26
+ font-family: "IBM Plex Mono", monospace; font-size: 11px; font-weight: 500;
27
+ color: var(--hm-text3); background: var(--hm-surface2);
28
+ border: 1px solid var(--hm-border); border-radius: 6px;
29
+ padding: 2px 8px; white-space: nowrap; letter-spacing: .03em; flex-shrink: 0;
30
+ }
31
+ .${e}-sizeControl { display: flex; align-items: center; gap: 5px; color: var(--hm-text3); }
32
+ .${e}-sizeIcon { flex-shrink: 0; }
33
+ .${e}-sizeSlider { width: 72px; accent-color: var(--hm-accent); cursor: pointer; height: 3px; }
34
+ .${e}-iconBtn {
35
+ display: flex; align-items: center; justify-content: center;
36
+ width: 30px; height: 30px; border-radius: 8px;
37
+ border: 1px solid var(--hm-border); background: var(--hm-surface2);
38
+ color: var(--hm-text2); cursor: pointer; transition: all .14s; flex-shrink: 0;
39
+ }
40
+ .${e}-iconBtn:hover { border-color: var(--hm-border2); color: var(--hm-text1); }
41
+ .${e}-iconBtnOn { background: var(--hm-accentbg) !important; border-color: var(--hm-accent) !important; color: var(--hm-accent) !important; }
42
+ .${e}-dropdownWrap { position: relative; }
43
+ .${e}-dropBtn {
44
+ display: flex; align-items: center; gap: 6px; padding: 5px 10px;
45
+ border-radius: 8px; border: 1px solid var(--hm-border);
46
+ background: var(--hm-surface2); color: var(--hm-text1);
47
+ font-family: "Outfit", sans-serif; font-size: 12px; font-weight: 500;
48
+ cursor: pointer; white-space: nowrap; transition: all .14s; height: 30px;
49
+ }
50
+ .${e}-dropBtn:hover { border-color: var(--hm-border2); }
51
+ .${e}-dropBtnOn { border-color: var(--hm-accent) !important; background: var(--hm-accentbg) !important; }
52
+ .${e}-dropBtnLabel { font-size: 11.5px; color: var(--hm-text2); max-width: 90px; overflow: hidden; text-overflow: ellipsis; }
53
+ .${e}-dropArrow { color: var(--hm-text3); flex-shrink: 0; }
54
+ .${e}-dropdown {
55
+ position: absolute; right: 0; top: calc(100% + 6px); z-index: 500;
56
+ background: var(--hm-surface); border: 1px solid var(--hm-border2);
57
+ border-radius: 10px; box-shadow: var(--hm-shadow);
58
+ padding: 4px; min-width: 170px; display: flex; flex-direction: column; gap: 1px;
59
+ }
60
+ .${e}-dropItem {
61
+ display: flex; align-items: center; gap: 8px; padding: 7px 10px;
62
+ border-radius: 7px; border: none; background: none;
63
+ color: var(--hm-text1); font-family: "Outfit", sans-serif;
64
+ font-size: 12.5px; font-weight: 500; cursor: pointer;
65
+ text-align: left; transition: background .10s; white-space: nowrap;
66
+ }
67
+ .${e}-dropItem:hover { background: var(--hm-surface2); }
68
+ .${e}-dropItemOn { background: var(--hm-accentbg) !important; color: var(--hm-accent) !important; }
69
+ .${e}-scalePreview { display: flex; gap: 2px; align-items: center; flex-shrink: 0; }
70
+ .${e}-scalePreviewDot { width: 10px; height: 10px; border-radius: 2px; display: inline-block; flex-shrink: 0; }
71
+ .${e}-themeToggle { display: flex; gap: 5px; margin-left: 4px; }
72
+ .${e}-themeBtn {
73
+ width: 14px; height: 14px; border-radius: 50%;
74
+ border: 2px solid transparent; background: var(--dot);
75
+ cursor: pointer; transition: all .15s; flex-shrink: 0; padding: 0;
76
+ }
77
+ .${e}-themeBtn:hover { transform: scale(1.25); }
78
+ .${e}-themeBtnOn { border-color: var(--hm-text1) !important; transform: scale(1.2); box-shadow: 0 0 0 1px var(--hm-bg); }
79
+ .${e}-body { flex: 1; overflow: hidden; display: flex; flex-direction: column; position: relative; }
80
+ .${e}-stateWrap {
81
+ flex: 1; display: flex; flex-direction: column;
82
+ align-items: center; justify-content: center;
83
+ gap: 12px; color: var(--hm-text2); font-size: 14px; padding: 40px 20px;
84
+ }
85
+ .${e}-stateHint { font-size: 12.5px; color: var(--hm-text3); text-align: center; max-width: 360px; line-height: 1.6; }
86
+ .${e}-stateHint code {
87
+ font-family: "IBM Plex Mono", monospace; font-size: 11px;
88
+ background: var(--hm-surface2); padding: 1px 5px;
89
+ border-radius: 4px; color: var(--hm-accent);
90
+ }
91
+ .${e}-spinner {
92
+ width: 28px; height: 28px;
93
+ border: 2.5px solid var(--hm-border2);
94
+ border-top-color: var(--hm-accent);
95
+ border-radius: 50%;
96
+ animation: ${e}-spin .65s linear infinite;
97
+ }
98
+ @keyframes ${e}-spin { to { transform: rotate(360deg); } }
99
+ .${e}-errBanner {
100
+ margin: 16px; padding: 12px 16px;
101
+ background: rgba(239,68,68,.08); border: 1px solid rgba(239,68,68,.25);
102
+ border-radius: 10px; color: #f87171; font-size: 13px; line-height: 1.5;
103
+ }
104
+ .${e}-heatmapWrap { flex: 1; overflow: hidden; display: flex; flex-direction: column; }
105
+ .${e}-colHeaderRow {
106
+ display: flex; align-items: flex-end; padding-bottom: 2px;
107
+ border-bottom: 1px solid var(--hm-border); flex-shrink: 0;
108
+ background: var(--hm-bg); position: sticky; top: 0; z-index: 20; overflow: hidden;
109
+ }
110
+ .${e}-cornerCell { width: 140px; min-width: 140px; flex-shrink: 0; }
111
+ .${e}-colHeader {
112
+ flex-shrink: 0; display: flex; flex-direction: column;
113
+ align-items: center; justify-content: flex-end;
114
+ padding-bottom: 4px; height: 72px; position: relative;
115
+ transition: background .12s; border-radius: 4px 4px 0 0;
116
+ }
117
+ .${e}-colHeaderOn { background: var(--hm-accentbg) !important; }
118
+ .${e}-colHeaderText {
119
+ font-family: "IBM Plex Mono", monospace; font-size: 10px; font-weight: 500;
120
+ color: var(--hm-text2); writing-mode: vertical-rl; transform: rotate(180deg);
121
+ white-space: nowrap; overflow: hidden; max-height: 56px;
122
+ text-overflow: ellipsis; letter-spacing: .02em; line-height: 1;
123
+ padding-bottom: 2px; transition: color .12s;
124
+ }
125
+ .${e}-colHeaderOn .${e}-colHeaderText { color: var(--hm-text1) !important; }
126
+ .${e}-colMiniBar {
127
+ width: 100%; height: 3px; background: var(--hm-surface3);
128
+ margin-top: 3px; border-radius: 2px; overflow: hidden;
129
+ display: flex; align-items: flex-end;
130
+ }
131
+ .${e}-colMiniBarFill { width: 100%; border-radius: 2px; transition: height .3s ease; }
132
+ .${e}-grid {
133
+ flex: 1; overflow: auto;
134
+ scrollbar-width: thin; scrollbar-color: rgba(255,255,255,.08) transparent;
135
+ }
136
+ .${e}-grid::-webkit-scrollbar { width: 5px; height: 5px; }
137
+ .${e}-grid::-webkit-scrollbar-thumb { background: rgba(255,255,255,.08); border-radius: 4px; }
138
+ .${e}-row { display: flex; align-items: stretch; }
139
+ .${e}-row:hover .${e}-rowLabel { color: var(--hm-text1); background: var(--hm-accentbg); }
140
+ .${e}-rowLabel {
141
+ width: 140px; min-width: 140px; flex-shrink: 0;
142
+ font-family: "IBM Plex Mono", monospace; font-size: 10.5px; font-weight: 400;
143
+ color: var(--hm-text2); padding: 0 10px; display: flex; align-items: center;
144
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
145
+ border-right: 1px solid var(--hm-border); background: var(--hm-bg);
146
+ position: sticky; left: 0; z-index: 10; transition: all .12s; letter-spacing: .01em;
147
+ }
148
+ .${e}-rowLabelOn { color: var(--hm-text1) !important; background: var(--hm-accentbg) !important; }
149
+ .${e}-cell {
150
+ display: flex; align-items: center; justify-content: center;
151
+ flex-shrink: 0; cursor: crosshair;
152
+ transition: outline .08s, filter .08s;
153
+ outline: 2px solid transparent; outline-offset: -1px;
154
+ position: relative; background: var(--hm-surface3);
155
+ }
156
+ .${e}-cell:hover { outline-color: rgba(255,255,255,.6); z-index: 5; filter: brightness(1.15); }
157
+ .${e}-cellVal {
158
+ font-family: "IBM Plex Mono", monospace; font-size: 9px; font-weight: 600;
159
+ letter-spacing: -.01em; pointer-events: none;
160
+ text-shadow: 0 1px 2px rgba(0,0,0,.3); line-height: 1;
161
+ max-width: 100%; overflow: hidden; text-overflow: ellipsis;
162
+ white-space: nowrap; padding: 0 2px;
163
+ }
164
+ .${e}-cellNull { font-family: "IBM Plex Mono", monospace; font-size: 11px; color: var(--hm-text3); opacity: .4; }
165
+ .${e}-footer {
166
+ display: flex; align-items: center; justify-content: space-between;
167
+ padding: 8px 12px; border-top: 1px solid var(--hm-border);
168
+ background: var(--hm-surface); flex-shrink: 0; gap: 16px; flex-wrap: wrap;
169
+ }
170
+ .${e}-legend { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
171
+ .${e}-legendMin, .${e}-legendMax {
172
+ font-family: "IBM Plex Mono", monospace; font-size: 10px;
173
+ color: var(--hm-text3); white-space: nowrap; min-width: 32px;
174
+ }
175
+ .${e}-legendMax { text-align: right; }
176
+ .${e}-legendBar { border-radius: 4px; display: block; height: 12px; width: 140px; flex-shrink: 0; }
177
+ .${e}-summaryStats { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
178
+ .${e}-statItem {
179
+ font-family: "IBM Plex Mono", monospace; font-size: 11px;
180
+ color: var(--hm-text2); display: flex; align-items: center; gap: 4px; white-space: nowrap;
181
+ }
182
+ .${e}-statItem span:first-child { color: var(--hm-text3); font-size: 10px; }
183
+ .${e}-statNulls { font-family: "IBM Plex Mono", monospace; font-size: 10px; color: var(--hm-text3); white-space: nowrap; }
184
+ .${e}-tooltip {
185
+ position: fixed; z-index: 9999; pointer-events: none;
186
+ background: var(--hm-surface); border: 1px solid var(--hm-border2);
187
+ border-radius: 10px; box-shadow: var(--hm-shadow);
188
+ padding: 0; overflow: hidden; min-width: 180px; max-width: 260px;
189
+ animation: ${e}-ttIn .1s ease;
190
+ }
191
+ @keyframes ${e}-ttIn {
192
+ from { opacity: 0; transform: scale(.96) translateY(3px); }
193
+ to { opacity: 1; transform: scale(1) translateY(0); }
194
+ }
195
+ .${e}-ttBody { padding: 12px 14px; display: flex; flex-direction: column; gap: 5px; }
196
+ .${e}-ttRow { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; }
197
+ .${e}-ttKey {
198
+ font-family: "Outfit", sans-serif; font-size: 10px; font-weight: 600;
199
+ text-transform: uppercase; letter-spacing: .07em; color: var(--hm-text3); flex-shrink: 0;
200
+ }
201
+ .${e}-ttVal {
202
+ font-family: "IBM Plex Mono", monospace; font-size: 12.5px; font-weight: 500;
203
+ color: var(--hm-text1); text-align: right; overflow: hidden;
204
+ text-overflow: ellipsis; white-space: nowrap; max-width: 140px;
205
+ }
206
+ .${e}-ttDivider { height: 1px; background: var(--hm-border); margin: 2px 0; }
207
+ .${e}-ttBar { height: 4px; background: var(--hm-surface3); border-radius: 3px; overflow: hidden; margin-top: 3px; }
208
+ .${e}-ttBarFill { height: 100%; border-radius: 3px; transition: width .15s ease; }
209
+ /* Theme variants */
210
+ .${e}-themeLight .${e}-grid::-webkit-scrollbar-thumb { background: rgba(0,0,0,.12); }
211
+ .${e}-themeLight .${e}-cell:hover { outline-color: rgba(0,0,0,.5); }
212
+ .${e}-themeEmber .${e}-grid::-webkit-scrollbar-thumb { background: rgba(255,100,20,.12); }
213
+ .${e}-themeEmber .${e}-cell:hover { outline-color: rgba(255,160,60,.6); }
214
+ `;function ge(){const s="__ahm_styles__";if(typeof document>"u"||document.getElementById(s))return;const o=document.createElement("style");o.id=s,o.textContent=ue,document.head.appendChild(o)}const be=s=>String(s??"").replace(/([a-z])([A-Z])/g,"$1 $2").replace(/[_\-]+/g," ").replace(/\b\w/g,o=>o.toUpperCase()).trim()||String(s),we=s=>String(s??"").trim().replace(/[_\s\-]+(.)?/g,(o,a)=>a?a.toUpperCase():"").replace(/^(.)/,o=>o.toLowerCase()),ne=(s,o="",a=0)=>a>3||typeof s!="object"||s===null?{[o]:s}:Object.entries(s).reduce((n,[d,l])=>{const x=o?`${o}_${d}`:d;return typeof l=="object"&&l!==null&&!Array.isArray(l)&&a<3?{...n,...ne(l,x,a+1)}:{...n,[x]:l}},{}),$e=s=>{const o=ne(s);return Object.fromEntries(Object.entries(o).map(([a,n])=>[we(a),n]))},se=["data","items","results","rows","records","list","content","payload","body","response","output","dataset","datasets","values","entries"],ve=s=>{if(!s)return[];if(Array.isArray(s)){if(!s.length)return[];const o=s[0];if(typeof o!="object"||o===null)return s.map((a,n)=>({index:n,value:a}));if(Array.isArray(o)){const a=o.map((n,d)=>n!=null?String(n):`col${d}`);return s.slice(1).map(n=>{const d={};return a.forEach((l,x)=>{d[l]=n[x]??null}),d})}return s}if(s.type==="FeatureCollection"&&Array.isArray(s.features))return s.features.filter(o=>(o==null?void 0:o.properties)&&typeof o.properties=="object").map(o=>{var a;return{...o.properties,_geomType:(a=o.geometry)==null?void 0:a.type}});for(const o of se){if(Array.isArray(s[o]))return s[o];if(s[o]&&typeof s[o]=="object"){for(const a of se)if(Array.isArray(s[o][a]))return s[o][a]}}return typeof s=="object"?[s]:[]},ye=s=>{if(s==null||s==="")return!1;const o=parseFloat(String(s).replace(/[,$%\s€£¥]/g,""));return!isNaN(o)&&isFinite(o)},je=s=>{if(s==null||s==="")return null;const o=parseFloat(String(s).replace(/[,$%\s€£¥]/g,""));return isNaN(o)?null:o},ke=s=>{if(!s.length)return null;const o=s.slice(0,200),a=[...new Set(o.flatMap(Object.keys))],n=a.filter(w=>{const f=o.map(k=>k[w]).filter(k=>k!=null&&k!=="");return f.length?f.filter(ye).length/f.length>.75:!1}),d=a.find(w=>!n.includes(w))??null;if(!n.length)return null;const l=s,x=n,v=l.map((w,f)=>d?String(w[d]??`Row ${f+1}`):`Row ${f+1}`),j=x.map(be),b=l.map(w=>x.map(f=>je(w[f])));let g=1/0,$=-1/0;return b.forEach(w=>w.forEach(f=>{f!==null&&(f<g&&(g=f),f>$&&($=f))})),isFinite(g)||(g=0,$=1),g===$&&(g=g-1,$=$+1),{rows:l,cols:x,rowLabels:v,colLabels:j,values:b,min:g,max:$,labelKey:d}},_={plasma:"Plasma",inferno:"Inferno",viridis:"Viridis",magma:"Magma",thermal:"Thermal",ice:"Ice",rdbu:"Red → Blue",spectral:"Spectral"},Ne=(s,o)=>{const a=s.length-1,n=Math.min(Math.floor(o*a),a-1),d=o*a-n,[l,x,v]=s[n],[j,b,g]=s[n+1];return[Math.round(l+(j-l)*d),Math.round(x+(b-x)*d),Math.round(v+(g-v)*d)]},oe={plasma:[[13,8,135],[84,2,163],[139,10,165],[185,50,137],[219,92,104],[244,136,73],[254,188,43],[240,249,33]],inferno:[[0,0,4],[40,11,84],[101,21,110],[159,42,99],[212,72,66],[245,125,21],[250,193,39],[252,255,164]],viridis:[[68,1,84],[72,40,120],[62,83,160],[49,120,183],[38,153,180],[31,183,152],[122,209,81],[253,231,37]],magma:[[0,0,4],[28,16,68],[79,18,123],[129,37,129],[181,54,122],[229,80,100],[251,135,97],[252,253,191]],thermal:[[4,35,51],[23,84,107],[60,146,153],[99,197,174],[162,220,193],[218,241,211],[246,210,169],[249,142,82],[214,41,21]],ice:[[4,0,46],[32,20,115],[66,59,170],[103,107,211],[142,156,232],[181,205,247],[215,238,255],[252,255,255]],rdbu:[[178,24,43],[214,96,77],[244,165,130],[253,219,199],[209,229,240],[146,197,222],[67,147,195],[33,102,172]],spectral:[[158,1,66],[213,62,79],[244,109,67],[253,174,97],[254,224,139],[230,245,152],[171,221,164],[102,194,165],[50,136,189],[94,79,162]]},E=(s,o)=>Ne(oe[s]??oe.plasma,Math.max(0,Math.min(1,o))),H=([s,o,a])=>`rgb(${s},${o},${a})`,S={dark:{"--hm-bg":"#080c14","--hm-surface":"#0e1521","--hm-surface2":"#141d2e","--hm-surface3":"#1a243b","--hm-border":"rgba(255,255,255,.07)","--hm-border2":"rgba(255,255,255,.13)","--hm-text1":"#e4eaf8","--hm-text2":"#6b7a9e","--hm-text3":"#3a445e","--hm-accent":"#4f7cff","--hm-accentbg":"rgba(79,124,255,.12)","--hm-shadow":"0 8px 40px rgba(0,0,0,.6)","--hm-page-bg":"#04060c"},light:{"--hm-bg":"#f0f2f7","--hm-surface":"#ffffff","--hm-surface2":"#e8ecf5","--hm-surface3":"#dde2ee","--hm-border":"rgba(0,0,0,.08)","--hm-border2":"rgba(0,0,0,.14)","--hm-text1":"#0d1324","--hm-text2":"#4a5370","--hm-text3":"#99a3bc","--hm-accent":"#2f5cf0","--hm-accentbg":"rgba(47,92,240,.09)","--hm-shadow":"0 4px 24px rgba(0,0,0,.10)","--hm-page-bg":"#e0e5f0"},ember:{"--hm-bg":"#0e0804","--hm-surface":"#170e06","--hm-surface2":"#1f1408","--hm-surface3":"#271a0a","--hm-border":"rgba(255,160,60,.08)","--hm-border2":"rgba(255,160,60,.16)","--hm-text1":"#faebd7","--hm-text2":"#a0836a","--hm-text3":"#5a3d2a","--hm-accent":"#ff7a2f","--hm-accentbg":"rgba(255,122,47,.13)","--hm-shadow":"0 8px 40px rgba(80,20,0,.7)","--hm-page-bg":"#070402"}};S.dark["--hm-page-bg"],S.light["--hm-page-bg"],S.ember["--hm-page-bg"];const ie="awesome-heatmap",z="prefs",Me=s=>new Promise(o=>{try{const a=indexedDB.open(ie,1);a.onupgradeneeded=n=>n.target.result.createObjectStore(z),a.onsuccess=n=>{try{const l=n.target.result.transaction(z,"readonly").objectStore(z).get(s);l.onsuccess=()=>o(l.result??null),l.onerror=()=>o(null)}catch{o(null)}},a.onerror=()=>o(null)}catch{o(null)}}),Se=(s,o)=>new Promise(a=>{try{const n=indexedDB.open(ie,1);n.onupgradeneeded=d=>d.target.result.createObjectStore(z),n.onsuccess=d=>{try{const l=d.target.result.transaction(z,"readwrite");l.objectStore(z).put(o,s),l.oncomplete=()=>a(!0),l.onerror=()=>a(!1)}catch{a(!1)}},n.onerror=()=>a(!1)}catch{a(!1)}}),C=s=>s==null?"—":Math.abs(s)>=1e6?(s/1e6).toFixed(2)+"M":Math.abs(s)>=1e3?(s/1e3).toFixed(1)+"K":Number.isInteger(s)?String(s):s.toFixed(2),ae=[{id:"none",label:"Original order"},{id:"rowAsc",label:"Row A → Z"},{id:"rowDesc",label:"Row Z → A"},{id:"sumAsc",label:"Row sum ↑"},{id:"sumDesc",label:"Row sum ↓"}];function Be({content:s,x:o,y:a,visible:n,theme:d}){const l=c.useRef(null),[x,v]=c.useState({top:0,left:0});return c.useEffect(()=>{if(!n||!l.current)return;const j=l.current,b=window.innerWidth,g=window.innerHeight,$=j.offsetWidth||200,w=j.offsetHeight||80,f=14;let k=o+f,y=a-w/2;k+$>b-8&&(k=o-$-f),y<8&&(y=8),y+w>g-8&&(y=g-w-8),v({top:y,left:k})},[o,a,n,s]),n?t.jsx("div",{ref:l,className:`${e}-tooltip`,style:{...S[d],top:x.top,left:x.left},children:s}):null}function Le({scale:s,min:o,max:a,theme:n}){const d=S[n]??S.dark,l=c.useRef(null);return c.useEffect(()=>{const x=l.current;if(!x)return;const v=x.getContext("2d"),j=x.width;for(let b=0;b<j;b++){const[g,$,w]=E(s,b/(j-1));v.fillStyle=`rgb(${g},${$},${w})`,v.fillRect(b,0,1,x.height)}},[s]),t.jsxs("div",{className:`${e}-legend`,style:d,children:[t.jsx("span",{className:`${e}-legendMin`,children:C(o)}),t.jsx("canvas",{ref:l,width:200,height:12,className:`${e}-legendBar`}),t.jsx("span",{className:`${e}-legendMax`,children:C(a)})]})}const Ce=c.memo(function({value:o,t:a,scaleName:n,showVal:d,cellSize:l,onEnter:x,onLeave:v}){if(o===null)return t.jsx("div",{className:`${e}-cell`,style:{width:l,height:l},onMouseEnter:x,onMouseLeave:v,children:t.jsx("span",{className:`${e}-cellNull`,children:"·"})});const[j,b,g]=E(n,a),w=.299*j+.587*b+.114*g>140?"rgba(0,0,0,.85)":"rgba(255,255,255,.9)";return t.jsx("div",{className:`${e}-cell`,style:{width:l,height:l,background:`rgb(${j},${b},${g})`,color:w},onMouseEnter:x,onMouseLeave:v,children:d&&t.jsx("span",{className:`${e}-cellVal`,children:C(o)})})});function Oe({data:s=[],title:o="Heatmap",theme:a="dark",colorScale:n="plasma",storageKey:d="awesome-heatmap",height:l="100%",onThemeChange:x=null}){var te;ge();const[v,j]=c.useState([]),[b,g]=c.useState(!1),[$,w]=c.useState("");c.useEffect(()=>{let r=!1;return(async()=>{g(!0),w("");try{let h;if(typeof s=="string"){const m=await fetch(s);if(!m.ok)throw new Error(`HTTP ${m.status} — ${s}`);h=await m.json()}else h=s;const u=ve(h).map($e);r||j(u)}catch(h){r||w(h.message)}finally{r||g(!1)}})(),()=>{r=!0}},[s]);const[f,k]=c.useState(a),[y,V]=c.useState(n),[M,q]=c.useState("none"),[R,K]=c.useState(!1),[L,U]=c.useState(40),[Ee,Z]=c.useState(null),[W,F]=c.useState({visible:!1,content:null,x:0,y:0}),[X,P]=c.useState(!1),[G,D]=c.useState(!1),[le,J]=c.useState(null),[ce,Q]=c.useState(null),T=c.useRef(!1),I=S[f]??S.dark;c.useEffect(()=>{d&&Me(d).then(r=>{if(!r){T.current=!0;return}r.theme&&S[r.theme]&&k(r.theme),r.scale&&_[r.scale]&&V(r.scale),r.sortMode&&q(r.sortMode),typeof r.showVals=="boolean"&&K(r.showVals),r.cellSize&&r.cellSize>=16&&U(r.cellSize),T.current=!0})},[d]),c.useEffect(()=>{!d||!T.current||Se(d,{theme:f,scale:y,sortMode:M,showVals:R,cellSize:L})},[d,f,y,M,R,L]),c.useEffect(()=>{S[a]&&(k(a),x==null||x(a))},[a,x]),c.useEffect(()=>{_[n]&&V(n)},[n]);const i=c.useMemo(()=>ke(v),[v]),Y=c.useMemo(()=>{if(!i)return[];const r=i.rows.length,p=Array.from({length:r},(u,m)=>m);if(M==="none")return p;if(M==="rowAsc")return[...p].sort((u,m)=>String(i.rowLabels[u]).localeCompare(String(i.rowLabels[m])));if(M==="rowDesc")return[...p].sort((u,m)=>String(i.rowLabels[m]).localeCompare(String(i.rowLabels[u])));const h=u=>i.values[u].reduce((m,A)=>m+(A??0),0);return M==="sumAsc"?[...p].sort((u,m)=>h(u)-h(m)):M==="sumDesc"?[...p].sort((u,m)=>h(m)-h(u)):p},[i,M]),ee=c.useMemo(()=>i?i.cols.map((r,p)=>{const h=i.values.map(m=>m[p]).filter(m=>m!==null);if(!h.length)return{min:0,max:0,avg:0,sum:0};const u=h.reduce((m,A)=>m+A,0);return{min:Math.min(...h),max:Math.max(...h),avg:u/h.length,sum:u}}):[],[i]),O=c.useCallback(r=>!i||r===null?0:(r-i.min)/(i.max-i.min),[i]),de=r=>{k(r),x==null||x(r)},he=c.useCallback((r,p,h)=>{var re;if(!i)return;const u=Y[r],m=(re=i.values[u])==null?void 0:re[p],A=i.rowLabels[u],fe=i.colLabels[p];Q(r),J(p),Z({row:r,col:p}),F({visible:!0,x:h.clientX,y:h.clientY,content:t.jsxs("div",{className:`${e}-ttBody`,style:I,children:[t.jsxs("div",{className:`${e}-ttRow`,children:[t.jsx("span",{className:`${e}-ttKey`,children:"Row"}),t.jsx("span",{className:`${e}-ttVal`,children:A})]}),t.jsxs("div",{className:`${e}-ttRow`,children:[t.jsx("span",{className:`${e}-ttKey`,children:"Column"}),t.jsx("span",{className:`${e}-ttVal`,children:fe})]}),t.jsx("div",{className:`${e}-ttDivider`}),t.jsxs("div",{className:`${e}-ttRow`,children:[t.jsx("span",{className:`${e}-ttKey`,children:"Value"}),t.jsx("span",{className:`${e}-ttVal`,style:{color:m!==null?H(E(y,O(m))):void 0},children:m!==null?C(m):"—"})]}),m!==null&&t.jsx("div",{className:`${e}-ttBar`,children:t.jsx("div",{className:`${e}-ttBarFill`,style:{width:`${O(m)*100}%`,background:H(E(y,O(m)))}})})]})})},[i,Y,y,O,I]),pe=c.useCallback(()=>{Z(null),Q(null),J(null),F(r=>({...r,visible:!1}))},[]),me=c.useCallback(r=>{F(p=>p.visible?{...p,x:r.clientX,y:r.clientY}:p)},[]),N=c.useMemo(()=>{if(!i)return null;const r=i.values.flat().filter(h=>h!==null);if(!r.length)return null;const p=r.reduce((h,u)=>h+u,0);return{count:r.length,total:i.rows.length*i.cols.length,min:i.min,max:i.max,avg:p/r.length,sum:p,rows:i.rows.length,cols:i.cols.length}},[i]);c.useEffect(()=>{const r=()=>{P(!1),D(!1)};return document.addEventListener("mousedown",r),()=>document.removeEventListener("mousedown",r)},[]);const xe=f==="light"?`${e}-themeLight`:f==="ember"?`${e}-themeEmber`:`${e}-themeDark`;return t.jsxs("div",{className:B(`${e}-root`,xe),style:{...I,height:typeof l=="number"?`${l}px`:l},onMouseMove:me,children:[t.jsxs("div",{className:`${e}-toolbar`,children:[t.jsxs("div",{className:`${e}-toolbarL`,children:[t.jsx("div",{className:`${e}-logoMark`,"aria-hidden":"true",children:t.jsxs("svg",{width:"22",height:"22",viewBox:"0 0 22 22",fill:"none",children:[t.jsx("rect",{x:"1",y:"1",width:"4",height:"4",rx:"1",fill:"var(--hm-accent)",opacity:".3"}),t.jsx("rect",{x:"6",y:"1",width:"4",height:"4",rx:"1",fill:"var(--hm-accent)",opacity:".5"}),t.jsx("rect",{x:"11",y:"1",width:"4",height:"4",rx:"1",fill:"var(--hm-accent)",opacity:".9"}),t.jsx("rect",{x:"16",y:"1",width:"4",height:"4",rx:"1",fill:"var(--hm-accent)",opacity:".5"}),t.jsx("rect",{x:"1",y:"6",width:"4",height:"4",rx:"1",fill:"var(--hm-accent)",opacity:".5"}),t.jsx("rect",{x:"6",y:"6",width:"4",height:"4",rx:"1",fill:"var(--hm-accent)",opacity:".9"}),t.jsx("rect",{x:"11",y:"6",width:"4",height:"4",rx:"1",fill:"var(--hm-accent)",opacity:".5"}),t.jsx("rect",{x:"16",y:"6",width:"4",height:"4",rx:"1",fill:"var(--hm-accent)",opacity:".3"}),t.jsx("rect",{x:"1",y:"11",width:"4",height:"4",rx:"1",fill:"var(--hm-accent)",opacity:".9"}),t.jsx("rect",{x:"6",y:"11",width:"4",height:"4",rx:"1",fill:"var(--hm-accent)",opacity:".5"}),t.jsx("rect",{x:"11",y:"11",width:"4",height:"4",rx:"1",fill:"var(--hm-accent)",opacity:".3"}),t.jsx("rect",{x:"16",y:"11",width:"4",height:"4",rx:"1",fill:"var(--hm-accent)",opacity:".5"}),t.jsx("rect",{x:"1",y:"16",width:"4",height:"4",rx:"1",fill:"var(--hm-accent)",opacity:".5"}),t.jsx("rect",{x:"6",y:"16",width:"4",height:"4",rx:"1",fill:"var(--hm-accent)",opacity:".3"}),t.jsx("rect",{x:"11",y:"16",width:"4",height:"4",rx:"1",fill:"var(--hm-accent)",opacity:".5"}),t.jsx("rect",{x:"16",y:"16",width:"4",height:"4",rx:"1",fill:"var(--hm-accent)",opacity:".9"})]})}),t.jsx("h1",{className:`${e}-title`,children:o}),N&&t.jsxs("span",{className:`${e}-dimBadge`,children:[N.rows.toLocaleString()," × ",N.cols.toLocaleString()]})]}),t.jsxs("div",{className:`${e}-toolbarR`,children:[t.jsxs("div",{className:`${e}-sizeControl`,children:[t.jsxs("svg",{viewBox:"0 0 14 14",fill:"none",width:"12",height:"12",className:`${e}-sizeIcon`,children:[t.jsx("rect",{x:"1",y:"1",width:"5",height:"5",rx:"1",stroke:"currentColor",strokeWidth:"1.3"}),t.jsx("rect",{x:"8",y:"1",width:"5",height:"5",rx:"1",stroke:"currentColor",strokeWidth:"1.3"}),t.jsx("rect",{x:"1",y:"8",width:"5",height:"5",rx:"1",stroke:"currentColor",strokeWidth:"1.3"}),t.jsx("rect",{x:"8",y:"8",width:"5",height:"5",rx:"1",stroke:"currentColor",strokeWidth:"1.3"})]}),t.jsx("input",{type:"range",min:16,max:80,step:4,value:L,onChange:r=>U(Number(r.target.value)),className:`${e}-sizeSlider`,title:`Cell size: ${L}px`})]}),t.jsx("button",{className:B(`${e}-iconBtn`,R&&`${e}-iconBtnOn`),onClick:()=>K(r=>!r),title:R?"Hide cell values":"Show cell values",children:t.jsxs("svg",{viewBox:"0 0 16 16",fill:"none",width:"14",height:"14",children:[t.jsx("path",{d:"M1 8C1 8 3.5 3 8 3s7 5 7 5-2.5 5-7 5S1 8 1 8z",stroke:"currentColor",strokeWidth:"1.4"}),t.jsx("circle",{cx:"8",cy:"8",r:"2",stroke:"currentColor",strokeWidth:"1.4"})]})}),t.jsxs("div",{className:`${e}-dropdownWrap`,onMouseDown:r=>r.stopPropagation(),children:[t.jsxs("button",{className:B(`${e}-dropBtn`,X&&`${e}-dropBtnOn`),onClick:()=>{P(r=>!r),D(!1)},title:"Color scale",children:[t.jsx("span",{className:`${e}-scalePreview`,children:[0,.25,.5,.75,1].map(r=>t.jsx("span",{style:{background:H(E(y,r))},className:`${e}-scalePreviewDot`},r))}),t.jsx("span",{className:`${e}-dropBtnLabel`,children:_[y]}),t.jsx("svg",{viewBox:"0 0 10 6",fill:"none",width:"9",className:`${e}-dropArrow`,children:t.jsx("path",{d:"M1 1l4 4 4-4",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round"})})]}),X&&t.jsx("div",{className:`${e}-dropdown`,style:I,children:Object.entries(_).map(([r,p])=>t.jsxs("button",{className:B(`${e}-dropItem`,y===r&&`${e}-dropItemOn`),onClick:()=>{V(r),P(!1)},children:[t.jsx("span",{className:`${e}-scalePreview`,children:[0,.2,.4,.6,.8,1].map(h=>t.jsx("span",{style:{background:H(E(r,h))},className:`${e}-scalePreviewDot`},h))}),t.jsx("span",{children:p})]},r))})]}),t.jsxs("div",{className:`${e}-dropdownWrap`,onMouseDown:r=>r.stopPropagation(),children:[t.jsxs("button",{className:B(`${e}-dropBtn`,G&&`${e}-dropBtnOn`),onClick:()=>{D(r=>!r),P(!1)},title:"Sort rows",children:[t.jsx("svg",{viewBox:"0 0 14 14",fill:"none",width:"13",height:"13",children:t.jsx("path",{d:"M2 3h10M4 7h6M6 11h2",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round"})}),t.jsx("span",{className:`${e}-dropBtnLabel`,children:((te=ae.find(r=>r.id===M))==null?void 0:te.label)??"Sort"}),t.jsx("svg",{viewBox:"0 0 10 6",fill:"none",width:"9",className:`${e}-dropArrow`,children:t.jsx("path",{d:"M1 1l4 4 4-4",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round"})})]}),G&&t.jsx("div",{className:`${e}-dropdown`,style:I,children:ae.map(r=>t.jsx("button",{className:B(`${e}-dropItem`,M===r.id&&`${e}-dropItemOn`),onClick:()=>{q(r.id),D(!1)},children:r.label},r.id))})]}),t.jsx("div",{className:`${e}-themeToggle`,children:[{id:"dark",color:"#4f7cff",label:"Dark"},{id:"light",color:"#2f5cf0",label:"Light"},{id:"ember",color:"#ff7a2f",label:"Ember"}].map(r=>t.jsx("button",{className:B(`${e}-themeBtn`,f===r.id&&`${e}-themeBtnOn`),style:{"--dot":r.color},onClick:()=>de(r.id),title:r.label},r.id))})]})]}),t.jsxs("div",{className:`${e}-body`,children:[b&&t.jsxs("div",{className:`${e}-stateWrap`,children:[t.jsx("div",{className:`${e}-spinner`}),t.jsx("span",{children:"Loading data…"})]}),!b&&$&&t.jsxs("div",{className:`${e}-errBanner`,children:[t.jsx("strong",{children:"Failed to load:"})," ",$]}),!b&&!$&&!i&&v.length>0&&t.jsxs("div",{className:`${e}-stateWrap`,children:[t.jsxs("svg",{viewBox:"0 0 48 48",fill:"none",width:"48",height:"48",opacity:".3",children:[t.jsx("rect",{x:"4",y:"4",width:"40",height:"40",rx:"4",stroke:"currentColor",strokeWidth:"2"}),t.jsx("path",{d:"M12 24h24M24 12v24",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round"})]}),t.jsx("p",{children:"No numeric columns found in this dataset."}),t.jsx("p",{className:`${e}-stateHint`,children:"AwesomeHeatmap needs at least one numeric column to render."})]}),!b&&!$&&v.length===0&&t.jsxs("div",{className:`${e}-stateWrap`,children:[t.jsxs("svg",{viewBox:"0 0 48 48",fill:"none",width:"48",height:"48",opacity:".3",children:[t.jsx("rect",{x:"4",y:"8",width:"40",height:"32",rx:"4",stroke:"currentColor",strokeWidth:"2"}),t.jsx("line",{x1:"4",y1:"16",x2:"44",y2:"16",stroke:"currentColor",strokeWidth:"2"}),t.jsx("line",{x1:"16",y1:"8",x2:"16",y2:"40",stroke:"currentColor",strokeWidth:"2",opacity:".5"})]}),t.jsx("p",{children:"No data loaded"}),t.jsxs("p",{className:`${e}-stateHint`,children:["Pass an array, a JSON file path, or a REST API URL to the ",t.jsx("code",{children:"data"})," prop."]})]}),!b&&i&&t.jsxs("div",{className:`${e}-heatmapWrap`,children:[t.jsxs("div",{className:`${e}-colHeaderRow`,style:{paddingLeft:0},children:[t.jsx("div",{className:`${e}-cornerCell`}),i.colLabels.map((r,p)=>{var h,u;return t.jsxs("div",{className:B(`${e}-colHeader`,le===p&&`${e}-colHeaderOn`),style:{width:L,minWidth:L},title:r,children:[t.jsx("span",{className:`${e}-colHeaderText`,children:r}),t.jsx("div",{className:`${e}-colMiniBar`,children:t.jsx("div",{className:`${e}-colMiniBarFill`,style:{height:`${Math.round(O(((h=ee[p])==null?void 0:h.avg)??0)*100)}%`,background:H(E(y,O(((u=ee[p])==null?void 0:u.avg)??0)))}})})]},p)})]}),t.jsx("div",{className:`${e}-grid`,children:Y.map((r,p)=>t.jsxs("div",{className:`${e}-row`,children:[t.jsx("div",{className:B(`${e}-rowLabel`,ce===p&&`${e}-rowLabelOn`),title:i.rowLabels[r],children:i.rowLabels[r]}),i.values[r].map((h,u)=>t.jsx(Ce,{value:h,t:O(h),scaleName:y,showVal:R&&L>=28,cellSize:L,onEnter:m=>he(p,u,m),onLeave:pe},u))]},r))}),t.jsxs("div",{className:`${e}-footer`,children:[t.jsx(Le,{scale:y,min:i.min,max:i.max,theme:f}),N&&t.jsxs("div",{className:`${e}-summaryStats`,children:[t.jsxs("span",{className:`${e}-statItem`,children:[t.jsx("span",{children:"Σ"})," ",C(N.sum)]}),t.jsxs("span",{className:`${e}-statItem`,children:[t.jsx("span",{children:"x̄"})," ",C(N.avg)]}),t.jsxs("span",{className:`${e}-statItem`,children:[t.jsx("span",{children:"↑"})," ",C(N.max)]}),t.jsxs("span",{className:`${e}-statItem`,children:[t.jsx("span",{children:"↓"})," ",C(N.min)]}),t.jsx("span",{className:`${e}-statNulls`,children:N.total-N.count>0?`${(N.total-N.count).toLocaleString()} null cells`:`${N.count.toLocaleString()} cells`})]})]})]})]}),t.jsx(Be,{content:W.content,x:W.x,y:W.y,visible:W.visible,theme:f})]})}module.exports=Oe;
215
+ //# sourceMappingURL=index.js.map