@trackunit/react-map 0.2.203 → 0.2.205

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/index.cjs.js CHANGED
@@ -5887,6 +5887,57 @@ const useFitToContent = (api, handles, options) => {
5887
5887
  return react.useMemo(() => ({ hasFitted, isWaiting, fitNow }), [hasFitted, isWaiting, fitNow]);
5888
5888
  };
5889
5889
 
5890
+ /**
5891
+ * Single dev-warning channel for `useHeatmap` (invalid data).
5892
+ */
5893
+ const warnHeatmap = (message, ...details) => {
5894
+ // eslint-disable-next-line no-console -- Intentional: the one heatmap warning channel; invalid input is dropped and must stay visible
5895
+ console.warn(`[useHeatmap] ${message}`, ...details);
5896
+ };
5897
+
5898
+ /**
5899
+ * Converts consumer items into weighted GeoJSON `Point` features.
5900
+ *
5901
+ * Null positions and zero weights are dropped silently. Invalid positions and
5902
+ * negative or non-finite weights are dropped with one grouped warning per pass.
5903
+ */
5904
+ const normalizeHeatmapData = ({ data, getPosition, getWeight, }) => {
5905
+ const features = [];
5906
+ const positions = [];
5907
+ let invalidPositionCount = 0;
5908
+ let invalidWeightCount = 0;
5909
+ for (const item of data) {
5910
+ const position = getPosition(item);
5911
+ if (position === null)
5912
+ continue;
5913
+ const parsedPosition = geoJsonUtils.geoJsonPositionSchema.safeParse(position);
5914
+ if (!parsedPosition.success) {
5915
+ invalidPositionCount++;
5916
+ continue;
5917
+ }
5918
+ const weight = getWeight?.(item) ?? 1;
5919
+ if (weight === 0)
5920
+ continue;
5921
+ if (!Number.isFinite(weight) || weight < 0) {
5922
+ invalidWeightCount++;
5923
+ continue;
5924
+ }
5925
+ positions.push(parsedPosition.data);
5926
+ features.push({
5927
+ type: "Feature",
5928
+ geometry: { type: "Point", coordinates: parsedPosition.data },
5929
+ properties: { weight },
5930
+ });
5931
+ }
5932
+ if (invalidPositionCount > 0 || invalidWeightCount > 0) {
5933
+ warnHeatmap(`Filtered ${invalidPositionCount} item(s) with invalid position and ${invalidWeightCount} item(s) with invalid weight. Expected [longitude, latitude] with longitude between -180 and 180, latitude between -90 and 90, and a finite weight >= 0.`);
5934
+ }
5935
+ return {
5936
+ features: { type: "FeatureCollection", features },
5937
+ positions,
5938
+ };
5939
+ };
5940
+
5890
5941
  const EMPTY_CONTROLS$2 = {};
5891
5942
  /**
5892
5943
  * Weighted point heatmap layer handle (provider-agnostic).
@@ -5898,31 +5949,15 @@ const EMPTY_CONTROLS$2 = {};
5898
5949
  const useHeatmap = (options) => {
5899
5950
  const { id, name, data, getPosition, getWeight, style, controls, loading, fitParticipation = "all" } = options;
5900
5951
  const layerReady = useLayerReady(loading);
5901
- const normalized = react.useMemo(() => {
5902
- const features = [];
5903
- const positions = [];
5904
- for (const item of data) {
5905
- const position = getPosition(item);
5906
- if (position === null)
5907
- continue;
5908
- const parsedPosition = geoJsonUtils.geoJsonPositionSchema.safeParse(position);
5909
- if (!parsedPosition.success)
5910
- continue;
5911
- const weight = getWeight?.(item) ?? 1;
5912
- if (!Number.isFinite(weight) || weight <= 0)
5913
- continue;
5914
- positions.push(parsedPosition.data);
5915
- features.push({
5916
- type: "Feature",
5917
- geometry: { type: "Point", coordinates: parsedPosition.data },
5918
- properties: { weight },
5919
- });
5920
- }
5921
- return {
5922
- features: { type: "FeatureCollection", features },
5923
- positions,
5924
- };
5925
- }, [data, getPosition, getWeight]);
5952
+ // Keyed on `data` (adjust-state-during-render + isEqual) so inline accessors
5953
+ // don't re-normalize the whole dataset on every render. Accessor changes
5954
+ // apply on the next `data` change.
5955
+ const [prevData, setPrevData] = react.useState(data);
5956
+ const [normalized, setNormalized] = react.useState(() => normalizeHeatmapData({ data, getPosition, getWeight }));
5957
+ if (data !== prevData && !esToolkit.isEqual(data, prevData)) {
5958
+ setPrevData(data);
5959
+ setNormalized(normalizeHeatmapData({ data, getPosition, getWeight }));
5960
+ }
5926
5961
  const resolvedStyle = react.useMemo(() => ({
5927
5962
  radiusPixels: style?.radiusPixels ?? 30,
5928
5963
  intensity: style?.intensity ?? 1,
package/index.esm.js CHANGED
@@ -5886,6 +5886,57 @@ const useFitToContent = (api, handles, options) => {
5886
5886
  return useMemo(() => ({ hasFitted, isWaiting, fitNow }), [hasFitted, isWaiting, fitNow]);
5887
5887
  };
5888
5888
 
5889
+ /**
5890
+ * Single dev-warning channel for `useHeatmap` (invalid data).
5891
+ */
5892
+ const warnHeatmap = (message, ...details) => {
5893
+ // eslint-disable-next-line no-console -- Intentional: the one heatmap warning channel; invalid input is dropped and must stay visible
5894
+ console.warn(`[useHeatmap] ${message}`, ...details);
5895
+ };
5896
+
5897
+ /**
5898
+ * Converts consumer items into weighted GeoJSON `Point` features.
5899
+ *
5900
+ * Null positions and zero weights are dropped silently. Invalid positions and
5901
+ * negative or non-finite weights are dropped with one grouped warning per pass.
5902
+ */
5903
+ const normalizeHeatmapData = ({ data, getPosition, getWeight, }) => {
5904
+ const features = [];
5905
+ const positions = [];
5906
+ let invalidPositionCount = 0;
5907
+ let invalidWeightCount = 0;
5908
+ for (const item of data) {
5909
+ const position = getPosition(item);
5910
+ if (position === null)
5911
+ continue;
5912
+ const parsedPosition = geoJsonPositionSchema.safeParse(position);
5913
+ if (!parsedPosition.success) {
5914
+ invalidPositionCount++;
5915
+ continue;
5916
+ }
5917
+ const weight = getWeight?.(item) ?? 1;
5918
+ if (weight === 0)
5919
+ continue;
5920
+ if (!Number.isFinite(weight) || weight < 0) {
5921
+ invalidWeightCount++;
5922
+ continue;
5923
+ }
5924
+ positions.push(parsedPosition.data);
5925
+ features.push({
5926
+ type: "Feature",
5927
+ geometry: { type: "Point", coordinates: parsedPosition.data },
5928
+ properties: { weight },
5929
+ });
5930
+ }
5931
+ if (invalidPositionCount > 0 || invalidWeightCount > 0) {
5932
+ warnHeatmap(`Filtered ${invalidPositionCount} item(s) with invalid position and ${invalidWeightCount} item(s) with invalid weight. Expected [longitude, latitude] with longitude between -180 and 180, latitude between -90 and 90, and a finite weight >= 0.`);
5933
+ }
5934
+ return {
5935
+ features: { type: "FeatureCollection", features },
5936
+ positions,
5937
+ };
5938
+ };
5939
+
5889
5940
  const EMPTY_CONTROLS$2 = {};
5890
5941
  /**
5891
5942
  * Weighted point heatmap layer handle (provider-agnostic).
@@ -5897,31 +5948,15 @@ const EMPTY_CONTROLS$2 = {};
5897
5948
  const useHeatmap = (options) => {
5898
5949
  const { id, name, data, getPosition, getWeight, style, controls, loading, fitParticipation = "all" } = options;
5899
5950
  const layerReady = useLayerReady(loading);
5900
- const normalized = useMemo(() => {
5901
- const features = [];
5902
- const positions = [];
5903
- for (const item of data) {
5904
- const position = getPosition(item);
5905
- if (position === null)
5906
- continue;
5907
- const parsedPosition = geoJsonPositionSchema.safeParse(position);
5908
- if (!parsedPosition.success)
5909
- continue;
5910
- const weight = getWeight?.(item) ?? 1;
5911
- if (!Number.isFinite(weight) || weight <= 0)
5912
- continue;
5913
- positions.push(parsedPosition.data);
5914
- features.push({
5915
- type: "Feature",
5916
- geometry: { type: "Point", coordinates: parsedPosition.data },
5917
- properties: { weight },
5918
- });
5919
- }
5920
- return {
5921
- features: { type: "FeatureCollection", features },
5922
- positions,
5923
- };
5924
- }, [data, getPosition, getWeight]);
5951
+ // Keyed on `data` (adjust-state-during-render + isEqual) so inline accessors
5952
+ // don't re-normalize the whole dataset on every render. Accessor changes
5953
+ // apply on the next `data` change.
5954
+ const [prevData, setPrevData] = useState(data);
5955
+ const [normalized, setNormalized] = useState(() => normalizeHeatmapData({ data, getPosition, getWeight }));
5956
+ if (data !== prevData && !isEqual(data, prevData)) {
5957
+ setPrevData(data);
5958
+ setNormalized(normalizeHeatmapData({ data, getPosition, getWeight }));
5959
+ }
5925
5960
  const resolvedStyle = useMemo(() => ({
5926
5961
  radiusPixels: style?.radiusPixels ?? 30,
5927
5962
  intensity: style?.intensity ?? 1,
package/package.json CHANGED
@@ -1,23 +1,23 @@
1
1
  {
2
2
  "name": "@trackunit/react-map",
3
- "version": "0.2.203",
3
+ "version": "0.2.205",
4
4
  "repository": "https://github.com/Trackunit/manager",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "engines": {
7
7
  "node": ">=24.x"
8
8
  },
9
9
  "dependencies": {
10
- "@trackunit/react-components": "3.8.0",
11
- "@trackunit/css-class-variance-utilities": "2.3.3",
12
- "@trackunit/react-form-components": "2.10.6",
13
- "@trackunit/react-core-hooks": "1.25.3",
14
- "@trackunit/geo-json-utils": "1.18.3",
15
- "@trackunit/i18n-library-translation": "2.8.3",
10
+ "@trackunit/react-components": "3.8.1",
11
+ "@trackunit/css-class-variance-utilities": "2.3.4",
12
+ "@trackunit/react-form-components": "2.10.7",
13
+ "@trackunit/react-core-hooks": "1.25.4",
14
+ "@trackunit/geo-json-utils": "1.18.4",
15
+ "@trackunit/i18n-library-translation": "2.8.4",
16
16
  "react-minimal-pie-chart": "^8.4.0",
17
- "@trackunit/react-map-adapter-shared": "0.0.154",
18
- "@trackunit/react-map-color-utils": "0.0.129",
19
- "@trackunit/shared-utils": "1.19.3",
20
- "@trackunit/ui-design-tokens": "1.18.3",
17
+ "@trackunit/react-map-adapter-shared": "0.0.155",
18
+ "@trackunit/react-map-color-utils": "0.0.130",
19
+ "@trackunit/shared-utils": "1.19.4",
20
+ "@trackunit/ui-design-tokens": "1.18.4",
21
21
  "@floating-ui/react": "^0.26.25",
22
22
  "es-toolkit": "^1.52.0",
23
23
  "tailwind-merge": "^2.0.0",
@@ -1,4 +1,4 @@
1
- import { type GeoJsonPosition } from "@trackunit/geo-json-utils";
1
+ import type { GeoJsonPosition } from "@trackunit/geo-json-utils";
2
2
  import type { CategoryKey, ControlConfig } from "../../controls/types";
3
3
  import type { FitParticipation, HeatmapLayerHandle } from "../types";
4
4
  export type HeatmapStyle = Readonly<{
@@ -0,0 +1,19 @@
1
+ import { type GeoJsonPosition } from "@trackunit/geo-json-utils";
2
+ import type { GeoJsonFeatureCollection } from "../../types";
3
+ export type NormalizedHeatmapData = Readonly<{
4
+ features: GeoJsonFeatureCollection;
5
+ positions: ReadonlyArray<GeoJsonPosition>;
6
+ }>;
7
+ type NormalizeHeatmapDataOptions<TItem> = Readonly<{
8
+ data: ReadonlyArray<TItem>;
9
+ getPosition: (item: TItem) => GeoJsonPosition | null;
10
+ getWeight: ((item: TItem) => number) | undefined;
11
+ }>;
12
+ /**
13
+ * Converts consumer items into weighted GeoJSON `Point` features.
14
+ *
15
+ * Null positions and zero weights are dropped silently. Invalid positions and
16
+ * negative or non-finite weights are dropped with one grouped warning per pass.
17
+ */
18
+ export declare const normalizeHeatmapData: <TItem>({ data, getPosition, getWeight, }: NormalizeHeatmapDataOptions<TItem>) => NormalizedHeatmapData;
19
+ export {};
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Single dev-warning channel for `useHeatmap` (invalid data).
3
+ */
4
+ export declare const warnHeatmap: (message: string, ...details: ReadonlyArray<unknown>) => void;
@@ -1 +0,0 @@
1
- {"version":3,"file":"entry.js","sourceRoot":"","sources":["../../../../../libs/react/map/migrations/entry.ts"],"names":[],"mappings":"","sourcesContent":["export {};\n"]}