react-threat-map 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/DECISIONS.md CHANGED
@@ -247,7 +247,8 @@ Every runtime dependency, and why it earns its place:
247
247
  Both are declared `external`, so consumers dedupe them against their own copies of d3
248
248
  rather than shipping a second one.
249
249
 
250
- `react` is a **peer** dependency (>=18), never bundled.
250
+ `react` is a **peer** dependency, never bundled. The supported range and the reasoning
251
+ behind its floor are in [§7](#7-react-version-support-1614-and-up).
251
252
 
252
253
  Deliberately **not** dependencies: no state manager, no styling library, no CSS-in-JS, no
253
254
  animation library, no `d3-selection`/`d3-zoom`/`d3-scale`. The library ships zero CSS —
@@ -329,3 +330,128 @@ lines — silently erase every self-directed threat on the map.
329
330
  Consumers upgrading from 0.1.x see new animated loops wherever their feed contains
330
331
  same-region attacks that previously rendered as static dots. This is why the change went
331
332
  out as a minor bump rather than a patch.
333
+
334
+ ---
335
+
336
+ ## 7. React version support: 16.14 and up
337
+
338
+ **Chosen:** `peerDependencies: { react: ">=16.14.0" }`, covering React 16.14, 17, 18, and 19.
339
+
340
+ ### Why the floor is 16.14.0 and not 16.8.0
341
+
342
+ 16.8.0 is the obvious guess, since that is where hooks landed and hooks are all this library
343
+ uses. It is wrong. The compiled output uses the **automatic JSX runtime**, so it imports
344
+ `react/jsx-runtime`, and that entry point first ships in **16.14.0**.
345
+
346
+ The failure mode is what makes this worth recording: on 16.8–16.13 the package *installs
347
+ cleanly* and then fails at the consumer's bundler on an unresolved module. A range of
348
+ `>=16.8.0` would be a promise that npm accepts and webpack rejects, which is the worst
349
+ place for the error to surface. If the JSX transform is ever changed back to `classic`, the
350
+ floor can drop to 16.8.0 — those two facts move together.
351
+
352
+ ### Why not the original `>=18.0.0`
353
+
354
+ There was no recorded reason for it, and it was wrong in both directions: it excluded React
355
+ 16.14/17 consumers the library works fine on, and it *included* React 19, which was broken
356
+ (below). Nothing here needs more than the floor — the library uses only `useState`,
357
+ `useEffect`, `useRef`, `useMemo`, and `useCallback`.
358
+
359
+ Widened in 0.2.1, as a patch: broadening a peer range cannot break an existing consumer.
360
+
361
+ ### Why the public types import from `react` by name
362
+
363
+ Supporting a wide range is why `CSSProperties`, `MouseEvent`, `ReactElement`, and
364
+ `RefObject` are imported **by name** rather than reached through the `React` UMD global or
365
+ the global `JSX` namespace.
366
+
367
+ A bare `JSX.Element` in an emitted `.d.ts` resolves against whatever `@types/react` the
368
+ consumer happens to have, which is not a thing this library controls. `@types/react@19`
369
+ removed the global `JSX` namespace, so the 0.2.0 type surface failed for React 19 consumers
370
+ compiling with `skipLibCheck: false`:
371
+
372
+ ```
373
+ dist/index.d.ts(40,76): error TS2503: Cannot find namespace 'JSX'.
374
+ ```
375
+
376
+ Those four names are exported identically by `@types/react` 16 through 19. The rule is
377
+ enforced by `tests/react-compat.test.ts`, which rejects `React.*` and `JSX.*` in `src/`.
378
+
379
+ ### Tradeoff accepted
380
+
381
+ **The test suite does not run against the floor.** `@testing-library/react@16` requires
382
+ React 18+, so every test here executes on 18. Covering 16.14 properly means a second
383
+ devDependency tree and a CI matrix, which was judged not worth it for a library using five
384
+ hooks. A green suite is therefore *not* evidence that the floor works.
385
+
386
+ What closes most of the gap is static: `tests/react-compat.test.ts` asserts that every name
387
+ imported from `react` appears on an allowlist checked against 16.14 by hand, so reaching
388
+ for `useSyncExternalStore`, `useId`, `useTransition`, `useDeferredValue`, or `use` fails the
389
+ build rather than shipping. It cannot catch behavioural differences — only API existence.
390
+
391
+ The one behavioural difference worth knowing: React 16/17 do not batch `setState` outside
392
+ event handlers, so the `ResizeObserver`, `matchMedia`, and geo-load callbacks each trigger
393
+ their own render there. Every one of those call sites issues a single `setState`, so the
394
+ practical cost is nil — but a future hook that sets two pieces of state in one async
395
+ callback would render twice on the floor and once on 18+.
396
+
397
+ ---
398
+
399
+ ## 8. Package entry points: per-condition types, plus a `typesVersions` fallback
400
+
401
+ **Chosen:** `exports` declares types *inside* each of the `import` and `require`
402
+ conditions, and `typesVersions` maps the `geo` subpath for resolvers that ignore `exports`
403
+ entirely.
404
+
405
+ This is not decoration. 0.2.1 shipped with two independent faults here, both invisible to
406
+ every other check in the repo: the suite imports from `src/`, and the smoke test exercises
407
+ one resolution mode. A package can be entirely correct at runtime and still fail to
408
+ typecheck for a large share of its consumers.
409
+
410
+ ### `moduleResolution: "node"` ignores `exports`, so the subpath had no types
411
+
412
+ Under node10 resolution — still the default for any project that has not opted in, which
413
+ is disproportionately the React 16/17 projects the floor in §7 deliberately invites —
414
+ `react-threat-map/geo` resolved to nothing:
415
+
416
+ ```
417
+ error TS2307: Cannot find module 'react-threat-map/geo' or its corresponding type
418
+ declarations. There are types at '.../dist/geo.d.ts', but this result could not be
419
+ resolved under your current 'moduleResolution' setting.
420
+ ```
421
+
422
+ `typesVersions` is the only mechanism node10 honours for subpath types, so it is the fix.
423
+
424
+ The workaround a consumer reaches for first is a trap worth naming, because it *looks*
425
+ like it works: `react-threat-map/dist/geo` typechecks fine, then fails at runtime with
426
+ `ERR_PACKAGE_PATH_NOT_EXPORTED`, since the deep path is not in `exports`. Webpack 5
427
+ enforces the same restriction. Neither specifier satisfied both the compiler and the
428
+ bundler — which is why this needed fixing in the library rather than working around
429
+ downstream.
430
+
431
+ ### One `types` per subpath made the package masquerade as ESM
432
+
433
+ The map previously declared a single `"types": "./dist/index.d.ts"` covering both
434
+ conditions. Because this package is `"type": "module"`, that `.d.ts` is read as an ES
435
+ module — so a **CommonJS** consumer on `node16`/`nodenext` was told the types were ESM
436
+ while the runtime correctly handed it `dist/index.cjs`:
437
+
438
+ ```
439
+ error TS1479: The current file is a CommonJS module whose imports will produce 'require'
440
+ calls; however, the referenced file is an ECMAScript module...
441
+ ```
442
+
443
+ tsup was *already emitting* `dist/index.d.cts` and `dist/geo.d.cts`. The exports map simply
444
+ never pointed at them, so they shipped in every tarball as dead weight. Declaring types
445
+ per-condition is what makes the dual build actually dual.
446
+
447
+ ### Tradeoff accepted
448
+
449
+ `typesVersions` is a legacy mechanism superseded by `exports`, and it duplicates the `geo`
450
+ mapping in a second place. Adding a subpath therefore means editing both, and forgetting
451
+ one breaks only node10 consumers — the group least likely to be on the maintainer's
452
+ machine. It is kept because node10 offers no alternative, and dropping it would mean
453
+ telling React 16/17 consumers to change their `tsconfig` to install a library that
454
+ otherwise supports them.
455
+
456
+ `npm run check:exports` (arethetypeswrong) runs in CI and covers every entry point against
457
+ every resolution mode. It is what would have caught both faults before 0.2.1 went out.
package/README.md CHANGED
@@ -49,12 +49,17 @@ import { ThreatMap } from 'react-threat-map';
49
49
  npm install react-threat-map
50
50
  ```
51
51
 
52
- React 18+ is a peer dependency:
52
+ React is a peer dependency:
53
53
 
54
54
  ```json
55
- { "peerDependencies": { "react": ">=18.0.0" } }
55
+ { "peerDependencies": { "react": ">=16.14.0" } }
56
56
  ```
57
57
 
58
+ 16.14.0 is the floor because that is the first release to ship `react/jsx-runtime`,
59
+ which the compiled output imports. The library uses only `useState`, `useEffect`,
60
+ `useRef`, `useMemo`, and `useCallback`, so nothing above that floor is required —
61
+ React 16.14, 17, 18, and 19 are all supported.
62
+
58
63
  ---
59
64
 
60
65
  ## Quick start
package/dist/geo.d.cts CHANGED
@@ -1,5 +1,6 @@
1
- import { G as GeoData } from './regions-neHSPgtu.cjs';
2
- export { R as RegionEntry, a as RegionIndex, U as UNKNOWN_REGION, g as getRegionById, l as listRegions, b as lookupRegionCode } from './regions-neHSPgtu.cjs';
1
+ import { G as GeoData } from './regions-Bsys6EFr.cjs';
2
+ export { R as RegionEntry, a as RegionIndex, U as UNKNOWN_REGION, g as getRegionById, l as listRegions, b as lookupRegionCode } from './regions-Bsys6EFr.cjs';
3
+ import 'react';
3
4
 
4
5
  /**
5
6
  * Boundary geometry loading — the `react-threat-map/geo` entry point.
package/dist/geo.d.ts CHANGED
@@ -1,5 +1,6 @@
1
- import { G as GeoData } from './regions-neHSPgtu.js';
2
- export { R as RegionEntry, a as RegionIndex, U as UNKNOWN_REGION, g as getRegionById, l as listRegions, b as lookupRegionCode } from './regions-neHSPgtu.js';
1
+ import { G as GeoData } from './regions-Bsys6EFr.js';
2
+ export { R as RegionEntry, a as RegionIndex, U as UNKNOWN_REGION, g as getRegionById, l as listRegions, b as lookupRegionCode } from './regions-Bsys6EFr.js';
3
+ import 'react';
3
4
 
4
5
  /**
5
6
  * Boundary geometry loading — the `react-threat-map/geo` entry point.
package/dist/index.d.cts CHANGED
@@ -1,5 +1,6 @@
1
- import { T as ThreatMapProps, A as AnimationConfig, L as LineStyleConfig, c as RegionsConfig, d as ThreatMapTheme, e as AggregationConfig, a as RegionIndex, f as Attack, h as Threat, I as IntensityScale, S as Severity, i as ResolvedRegion, j as AggregationGranularity, k as LatLng, m as AttackLocation } from './regions-neHSPgtu.cjs';
2
- export { n as AggregationGroupBy, o as AggregationKeyFn, p as AggregationSeverityFn, C as Color, E as EasingName, G as GeoData, q as GeoFeature, r as GeoFeatureCollection, s as GeoGeometry, t as GeoProjectionLike, P as ProjectionName, u as ProjectionSpec, v as RegionCode, R as RegionEntry, w as RegionKind, x as RegionRenderContext, y as RegionRenderer, z as ThreatMapError, B as ThreatRenderContext, D as ThreatRenderer, U as UNKNOWN_REGION, g as getRegionById, l as listRegions, b as lookupRegionCode } from './regions-neHSPgtu.cjs';
1
+ import { ReactElement } from 'react';
2
+ import { T as ThreatMapProps, A as AnimationConfig, L as LineStyleConfig, c as RegionsConfig, d as ThreatMapTheme, e as AggregationConfig, a as RegionIndex, f as Attack, h as Threat, I as IntensityScale, S as Severity, i as ResolvedRegion, j as AggregationGranularity, k as LatLng, m as AttackLocation } from './regions-Bsys6EFr.cjs';
3
+ export { n as AggregationGroupBy, o as AggregationKeyFn, p as AggregationSeverityFn, C as Color, E as EasingName, G as GeoData, q as GeoFeature, r as GeoFeatureCollection, s as GeoGeometry, t as GeoProjectionLike, P as ProjectionName, u as ProjectionSpec, v as RegionCode, R as RegionEntry, w as RegionKind, x as RegionRenderContext, y as RegionRenderer, z as ThreatMapError, B as ThreatRenderContext, D as ThreatRenderer, U as UNKNOWN_REGION, g as getRegionById, l as listRegions, b as lookupRegionCode } from './regions-Bsys6EFr.cjs';
3
4
 
4
5
  /**
5
6
  * The `<ThreatMap>` component.
@@ -37,7 +38,7 @@ export { n as AggregationGroupBy, o as AggregationKeyFn, p as AggregationSeverit
37
38
  * />
38
39
  * ```
39
40
  */
40
- declare function ThreatMap<TMeta = unknown>(props: ThreatMapProps<TMeta>): JSX.Element;
41
+ declare function ThreatMap<TMeta = unknown>(props: ThreatMapProps<TMeta>): ReactElement;
41
42
 
42
43
  /**
43
44
  * Default theme and configuration objects.
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
- import { T as ThreatMapProps, A as AnimationConfig, L as LineStyleConfig, c as RegionsConfig, d as ThreatMapTheme, e as AggregationConfig, a as RegionIndex, f as Attack, h as Threat, I as IntensityScale, S as Severity, i as ResolvedRegion, j as AggregationGranularity, k as LatLng, m as AttackLocation } from './regions-neHSPgtu.js';
2
- export { n as AggregationGroupBy, o as AggregationKeyFn, p as AggregationSeverityFn, C as Color, E as EasingName, G as GeoData, q as GeoFeature, r as GeoFeatureCollection, s as GeoGeometry, t as GeoProjectionLike, P as ProjectionName, u as ProjectionSpec, v as RegionCode, R as RegionEntry, w as RegionKind, x as RegionRenderContext, y as RegionRenderer, z as ThreatMapError, B as ThreatRenderContext, D as ThreatRenderer, U as UNKNOWN_REGION, g as getRegionById, l as listRegions, b as lookupRegionCode } from './regions-neHSPgtu.js';
1
+ import { ReactElement } from 'react';
2
+ import { T as ThreatMapProps, A as AnimationConfig, L as LineStyleConfig, c as RegionsConfig, d as ThreatMapTheme, e as AggregationConfig, a as RegionIndex, f as Attack, h as Threat, I as IntensityScale, S as Severity, i as ResolvedRegion, j as AggregationGranularity, k as LatLng, m as AttackLocation } from './regions-Bsys6EFr.js';
3
+ export { n as AggregationGroupBy, o as AggregationKeyFn, p as AggregationSeverityFn, C as Color, E as EasingName, G as GeoData, q as GeoFeature, r as GeoFeatureCollection, s as GeoGeometry, t as GeoProjectionLike, P as ProjectionName, u as ProjectionSpec, v as RegionCode, R as RegionEntry, w as RegionKind, x as RegionRenderContext, y as RegionRenderer, z as ThreatMapError, B as ThreatRenderContext, D as ThreatRenderer, U as UNKNOWN_REGION, g as getRegionById, l as listRegions, b as lookupRegionCode } from './regions-Bsys6EFr.js';
3
4
 
4
5
  /**
5
6
  * The `<ThreatMap>` component.
@@ -37,7 +38,7 @@ export { n as AggregationGroupBy, o as AggregationKeyFn, p as AggregationSeverit
37
38
  * />
38
39
  * ```
39
40
  */
40
- declare function ThreatMap<TMeta = unknown>(props: ThreatMapProps<TMeta>): JSX.Element;
41
+ declare function ThreatMap<TMeta = unknown>(props: ThreatMapProps<TMeta>): ReactElement;
41
42
 
42
43
  /**
43
44
  * Default theme and configuration objects.
@@ -1,3 +1,5 @@
1
+ import { CSSProperties } from 'react';
2
+
1
3
  /**
2
4
  * Public data types for `react-threat-map`.
3
5
  *
@@ -6,6 +8,7 @@
6
8
  *
7
9
  * @packageDocumentation
8
10
  */
11
+
9
12
  /**
10
13
  * A raw geographic coordinate in degrees (WGS84).
11
14
  *
@@ -651,7 +654,7 @@ interface ThreatMapProps<TMeta = unknown> {
651
654
  /** Applied to the wrapper element. */
652
655
  readonly className?: string;
653
656
  /** Applied to the wrapper element. The canvases fill it. */
654
- readonly style?: React.CSSProperties;
657
+ readonly style?: CSSProperties;
655
658
  /** Accessible label for the map. Default `"Cyberattack threat map"`. */
656
659
  readonly ariaLabel?: string;
657
660
  }
@@ -1,3 +1,5 @@
1
+ import { CSSProperties } from 'react';
2
+
1
3
  /**
2
4
  * Public data types for `react-threat-map`.
3
5
  *
@@ -6,6 +8,7 @@
6
8
  *
7
9
  * @packageDocumentation
8
10
  */
11
+
9
12
  /**
10
13
  * A raw geographic coordinate in degrees (WGS84).
11
14
  *
@@ -651,7 +654,7 @@ interface ThreatMapProps<TMeta = unknown> {
651
654
  /** Applied to the wrapper element. */
652
655
  readonly className?: string;
653
656
  /** Applied to the wrapper element. The canvases fill it. */
654
- readonly style?: React.CSSProperties;
657
+ readonly style?: CSSProperties;
655
658
  /** Accessible label for the map. Default `"Cyberattack threat map"`. */
656
659
  readonly ariaLabel?: string;
657
660
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-threat-map",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "A React component that renders animated cyberattack threats on a static world map, with intelligent per-region aggregation and first-class US state support.",
5
5
  "keywords": [
6
6
  "react",
@@ -29,16 +29,33 @@
29
29
  "main": "./dist/index.cjs",
30
30
  "module": "./dist/index.js",
31
31
  "types": "./dist/index.d.ts",
32
+ "typesVersions": {
33
+ "*": {
34
+ "geo": [
35
+ "./dist/geo.d.ts"
36
+ ]
37
+ }
38
+ },
32
39
  "exports": {
33
40
  ".": {
34
- "types": "./dist/index.d.ts",
35
- "import": "./dist/index.js",
36
- "require": "./dist/index.cjs"
41
+ "import": {
42
+ "types": "./dist/index.d.ts",
43
+ "default": "./dist/index.js"
44
+ },
45
+ "require": {
46
+ "types": "./dist/index.d.cts",
47
+ "default": "./dist/index.cjs"
48
+ }
37
49
  },
38
50
  "./geo": {
39
- "types": "./dist/geo.d.ts",
40
- "import": "./dist/geo.js",
41
- "require": "./dist/geo.cjs"
51
+ "import": {
52
+ "types": "./dist/geo.d.ts",
53
+ "default": "./dist/geo.js"
54
+ },
55
+ "require": {
56
+ "types": "./dist/geo.d.cts",
57
+ "default": "./dist/geo.cjs"
58
+ }
42
59
  },
43
60
  "./package.json": "./package.json"
44
61
  },
@@ -56,6 +73,7 @@
56
73
  "build": "npm run geo && tsup",
57
74
  "dev": "tsup --watch",
58
75
  "typecheck": "tsc --noEmit",
76
+ "check:exports": "attw --pack .",
59
77
  "test": "vitest run",
60
78
  "test:watch": "vitest",
61
79
  "test:coverage": "vitest run --coverage",
@@ -64,13 +82,14 @@
64
82
  "example": "npm --prefix examples/demo run dev"
65
83
  },
66
84
  "peerDependencies": {
67
- "react": ">=18.0.0"
85
+ "react": ">=16.14.0"
68
86
  },
69
87
  "dependencies": {
70
88
  "d3-geo": "^3.1.1",
71
89
  "topojson-client": "^3.1.0"
72
90
  },
73
91
  "devDependencies": {
92
+ "@arethetypeswrong/cli": "^0.18.5",
74
93
  "@testing-library/jest-dom": "^6.9.1",
75
94
  "@testing-library/react": "^16.1.0",
76
95
  "@types/d3-geo": "^3.1.0",