Skip to content

Releases: BogdanTaranenko/react-threat-map

v0.2.4 — cross-browser fixes

Choose a tag to compare

@BogdanTaranenko BogdanTaranenko released this 04 Sep 13:42

Two bugs that made the map fail outright on older browsers, plus a documented support floor. No API changes — this is a drop-in patch.

Fixed

Crash on mount, Safari/iOS ≤13. useReducedMotion and usePixelRatio called addEventListener on the object returned by matchMedia. Safari only made MediaQueryList an EventTarget in 14, so below that this threw TypeError: query.addEventListener is not a function during mount and took the consumer's React tree down with it. Both hooks now feature-detect and fall back to addListener/removeListener.

Invisible map on browsers without CSS aspect-ratio (Safari/iOS <15, Chrome <88, Firefox <89). A <ThreatMap> given no height derived one solely from aspect-ratio. Where that property is unimplemented the declaration is dropped, and since both canvases are absolutely positioned nothing else contributed height — the wrapper collapsed to zero and nothing rendered. It now falls back to a width-derived min-height.

inset: 0 shorthand on both canvases replaced with top/left longhand, since inset is itself only Safari 14.1+.

Added

A Browser support section in the README: the floor (Chrome/Edge 88, Firefox 89, Safari 15), why aspect-ratio sets it, and what degrades quietly on Safari <16. The package previously declared no browser support anywhere.

Notes

On browsers below the floor the map now renders at a derived height rather than not at all — a visible behaviour change, though strictly a fix.

Verified with a new regression suite covering a Safari 13-shaped MediaQueryList and a browser without aspect-ratio, and by measuring the animation frame-by-frame in real Microsoft Edge 152 and Chrome 152.

v0.2.3

Choose a tag to compare

@BogdanTaranenko BogdanTaranenko released this 21 Jul 17:26

Full Changelog: v0.2.2...v0.2.3

v0.2.2

Choose a tag to compare

@BogdanTaranenko BogdanTaranenko released this 21 Jul 13:31

Packaging fixes. No API changes, no runtime behaviour changes — this release only affects how TypeScript resolves the package's types.

If you are on moduleResolution: "bundler" or "node16" from ESM, 0.2.1 already worked for you and nothing here changes.

react-threat-map/geo now has types under moduleResolution: "node"

node10 resolution ignores the exports map, and there was no typesVersions fallback, so the subpath resolved to nothing:

error TS2307: Cannot find module 'react-threat-map/geo' or its corresponding
type declarations. There are types at '.../dist/geo.d.ts', but this result
could not be resolved under your current 'moduleResolution' setting.

This lands hardest on the consumers 0.2.1 set out to support: projects still on node10 resolution skew old, which is the same population still on React 16/17.

If you worked around this with react-threat-map/dist/geo — that path typechecks but fails at runtime with ERR_PACKAGE_PATH_NOT_EXPORTED, and webpack 5 enforces the same restriction. Switch back to react-threat-map/geo.

CommonJS consumers no longer receive ESM type declarations

Both entry points declared a single "types" covering the import and require conditions. Because this package is "type": "module", that .d.ts is read as an ES module — so CommonJS consumers on node16/nodenext were told the types were ESM while the runtime correctly handed them the .cjs build:

error TS1479: The current file is a CommonJS module whose imports will produce
'require' calls; however, the referenced file is an ECMAScript module...

The correct CJS declarations (index.d.cts, geo.d.cts) were already being built and shipped — the exports map just never pointed at them. Types are now declared per condition.

Verified

Entry node10 node16 (CJS) node16 (ESM) bundler
react-threat-map ✅ ✅ ✅ ✅
react-threat-map/geo ✅ ✅ ✅ ✅

npm run check:exports (arethetypeswrong) now runs in CI against a real npm pack, covering every entry point against every resolution mode. Nothing else in the suite could see this class of fault: the tests import from src/, so a package that is correct at runtime can still fail to typecheck for a large share of its consumers.

v0.2.1

Choose a tag to compare

@BogdanTaranenko BogdanTaranenko released this 21 Jul 13:11

Compatibility release. No API changes — no code changes are needed to upgrade.

React 16.14 and 17 are now supported

peerDependencies was >=18.0.0 with no recorded rationale, which locked out consumers on React 16.14/17 that the library already worked on. It uses only useState, useEffect, useRef, useMemo, and useCallback — no React 18+ API.

The floor is 16.14.0, not 16.8.0 (hooks): the compiled output uses the automatic JSX runtime and so imports react/jsx-runtime, which first ships in 16.14.0. On 16.8–16.13 the package installs and then fails at bundle time on an unresolved module.

If you were installing with --legacy-peer-deps to work around the old range, you can drop that flag.

Fixed: broken types on React 19

The emitted .d.ts referenced JSX.Element and React.CSSProperties as globals with no import of react, so the published type surface resolved against whatever @types/react the consumer had. @types/react@19 removed the global JSX namespace, which broke React 19 consumers compiling with skipLibCheck: false:

dist/index.d.ts(40,76): error TS2503: Cannot find namespace 'JSX'.

Those types are now imported by name, which fixes React 19 and is what makes the wider floor viable.

Verified

Each version installed from a real tarball with clean peer resolution (no --legacy-peer-deps), typechecked at skipLibCheck: false, and put through a mount / re-render / unmount pass:

React Install Typecheck Runtime
16.14.0 ✅ ✅ ✅
17.0.2 ✅ ✅ ✅
18.3.1 ✅ ✅ ✅
19.2.7 ✅ ✅ ✅

Note that the test suite does not run against the floor — @testing-library/react@16 requires React 18+. tests/react-compat.test.ts guards the part that can be checked statically: every name imported from react must be on an allowlist verified against 16.14.

v0.2.0 — same-place threats render as a self-loop

Choose a tag to compare

@BogdanTaranenko BogdanTaranenko released this 21 Jul 10:07

Highlights

An attack whose origin and destination resolve to the same place now renders as a self-loop instead of an invisible point.

This covers a domestic incident in a country with no subdivisions (DE → DE), lateral movement inside one US state (US-CA → US-CA), and two hosts given identical city coordinates. All three produce a zero-length chord, which previously degenerated the arc: the renderer skips zero-length polylines, so those threats drew no track and no trail — just an origin dot with the head sitting on top of it. They could not be hovered either, since hit-testing measures distance to line segments and there were none.

buildArc now emits a circle tangent to the shared point: the origin marker sits on the point, the head travels the loop, and the impact ripple fires back on the same spot. It is ordinary geometry, so batching, fading, and hit-testing work with no special cases.

<ThreatMap attacks={[{ id: '1', from: 'DE', to: 'DE', severity: 'high' }]} />

Behaviour change

This is why the release is minor rather than patch. If your feed contains same-region attacks that previously rendered as static dots, they now render as animated loops. Nothing else about existing arcs changes.

Endpoints do not have to be exactly equal — anything projecting within half a pixel counts as the same place, since a sub-pixel line cannot be seen or hovered anyway. The tradeoff is that two genuinely distinct points can render as a loop when the viewport is zoomed far enough out.

The loop radius is clamped to 6–18 px so a same-place attack stays visible on a world map, and deliberately ignores curvature — curvature is the height of a lift applied to a chord, and a self-loop has no chord, so coupling them would make curvature: 0 erase these threats entirely.

Full rationale in DECISIONS.md §6.

Docs & demo

  • New README section covering same-place attacks.
  • New Domestic attacks demo panel showing all three behaviours against one feed, switching only the projection: Germany (Frankfurt → Frankfurt), United States (state-to-state via geoAlbersUsa), and World (both together).

Verification

Typecheck, 245 tests across node 18/20/22, coverage 94.45%, geo data in sync, demo build, and package smoke test — all green.

Full changelog: v0.1.2...v0.2.0