@trackunit/react-map 0.2.7 → 0.2.9-alpha-636ec415cd1.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/index.cjs.js
CHANGED
|
@@ -7702,7 +7702,9 @@ const buildContendedShapes = (group) => group.map(record => {
|
|
|
7702
7702
|
for (const other of group) {
|
|
7703
7703
|
if (other.id === record.id)
|
|
7704
7704
|
continue;
|
|
7705
|
-
|
|
7705
|
+
// Pure predicate: geometry was validated once at the computeFillTiling
|
|
7706
|
+
// boundary, so this O(n²) loop performs no safeParse per pair (SAGA-664).
|
|
7707
|
+
if (geoJsonUtils.isFullyContainedInGeometry(record.geometry, other.geometry)) {
|
|
7706
7708
|
containedIn.push(other.id);
|
|
7707
7709
|
}
|
|
7708
7710
|
}
|
|
@@ -7775,6 +7777,37 @@ const computePromotionGroupFills = (members, winnerId, restingPeerFills = new Ma
|
|
|
7775
7777
|
};
|
|
7776
7778
|
const computeFillTiling = (input) => {
|
|
7777
7779
|
const { features, viewportBounds, resolveStackOrder, selectedFeatureId, suppressedFeatureIds, layerGeodesic, featureStyles, } = input;
|
|
7780
|
+
// Validation boundary (SAGA-664): validate each input feature's polygonal
|
|
7781
|
+
// geometry once here — O(n) — dropping malformed geometry. The downstream O(n²)
|
|
7782
|
+
// overlap loop then runs the pure predicates on trusted geometry without a
|
|
7783
|
+
// safeParse per pair. Validate the input (not the densified output), since
|
|
7784
|
+
// densification is our own transform of already-valid geometry.
|
|
7785
|
+
const validFeatures = [];
|
|
7786
|
+
const droppedInvalidFeatures = [];
|
|
7787
|
+
for (const feature of features) {
|
|
7788
|
+
const geometry = polygonalGeometry(feature);
|
|
7789
|
+
if (geometry === null)
|
|
7790
|
+
continue;
|
|
7791
|
+
const parsed = geometry.type === "Polygon"
|
|
7792
|
+
? geoJsonUtils.geoJsonPolygonSchema.safeParse(geometry)
|
|
7793
|
+
: geoJsonUtils.geoJsonMultiPolygonSchema.safeParse(geometry);
|
|
7794
|
+
if (!parsed.success) {
|
|
7795
|
+
droppedInvalidFeatures.push({
|
|
7796
|
+
feature,
|
|
7797
|
+
validationError: parsed.error.format(),
|
|
7798
|
+
});
|
|
7799
|
+
continue;
|
|
7800
|
+
}
|
|
7801
|
+
validFeatures.push(feature);
|
|
7802
|
+
}
|
|
7803
|
+
if (droppedInvalidFeatures.length > 0) {
|
|
7804
|
+
const idList = droppedInvalidFeatures
|
|
7805
|
+
.map(entry => (entry.feature.id !== undefined ? String(entry.feature.id) : undefined))
|
|
7806
|
+
.filter((id) => id !== undefined)
|
|
7807
|
+
.join(", ") || "(no feature ids)";
|
|
7808
|
+
// eslint-disable-next-line no-console -- Intentional: warn devs when schema-invalid polygonal geometry is excluded from fill tiling
|
|
7809
|
+
console.warn(`[computeFillTiling] Excluded ${droppedInvalidFeatures.length} feature(s) with invalid polygonal geometry from fill tiling (full fill still renders; overlap clipping is skipped): ${idList}`);
|
|
7810
|
+
}
|
|
7778
7811
|
// Densify before any polygon/boundary math so clip geometry follows great-circle
|
|
7779
7812
|
// arcs on large polygons (mirrors the ADR-0024 precedent for edge labels). Always
|
|
7780
7813
|
// defer to densifyGeodesicFeatures' per-feature resolution (geodesic override >
|
|
@@ -7783,7 +7816,7 @@ const computeFillTiling = (input) => {
|
|
|
7783
7816
|
// original collection when nothing is densified.
|
|
7784
7817
|
const featureCollection = {
|
|
7785
7818
|
type: "FeatureCollection",
|
|
7786
|
-
features:
|
|
7819
|
+
features: validFeatures,
|
|
7787
7820
|
};
|
|
7788
7821
|
const densifiedFeatures = reactMapAdapterShared.densifyGeodesicFeatures(featureCollection, layerGeodesic ?? true, featureStyles).features;
|
|
7789
7822
|
const records = [];
|
|
@@ -7876,7 +7909,12 @@ const computeFillTiling = (input) => {
|
|
|
7876
7909
|
fillGeometries.set(id, EMPTY_FILL);
|
|
7877
7910
|
}
|
|
7878
7911
|
}
|
|
7879
|
-
return {
|
|
7912
|
+
return {
|
|
7913
|
+
fillGeometries,
|
|
7914
|
+
featureToGroupKey,
|
|
7915
|
+
featureZIndex,
|
|
7916
|
+
droppedInvalidFeatures,
|
|
7917
|
+
};
|
|
7880
7918
|
};
|
|
7881
7919
|
|
|
7882
7920
|
const DEFAULT_ZOOM = 0;
|
|
@@ -9950,6 +9988,44 @@ const useShapeDecorations = ({ onDecorationsChange, onStyleOverridesChange, api,
|
|
|
9950
9988
|
return react.useMemo(() => ({ subscribe, getSnapshot }), [subscribe, getSnapshot]);
|
|
9951
9989
|
};
|
|
9952
9990
|
|
|
9991
|
+
const resolveDroppedFeatureDedupeKey = (layerHandleId, entry) => {
|
|
9992
|
+
if (entry.feature.id !== undefined) {
|
|
9993
|
+
return `${layerHandleId}:${String(entry.feature.id)}`;
|
|
9994
|
+
}
|
|
9995
|
+
const geometry = entry.feature.geometry;
|
|
9996
|
+
if (geometry === null) {
|
|
9997
|
+
return `${layerHandleId}:anonymous:null-geometry`;
|
|
9998
|
+
}
|
|
9999
|
+
if (geometry.type === "Polygon") {
|
|
10000
|
+
return `${layerHandleId}:anonymous:Polygon:${geometry.coordinates.flat(2).length}`;
|
|
10001
|
+
}
|
|
10002
|
+
if (geometry.type === "MultiPolygon") {
|
|
10003
|
+
return `${layerHandleId}:anonymous:MultiPolygon:${geometry.coordinates.flat(3).length}`;
|
|
10004
|
+
}
|
|
10005
|
+
return `${layerHandleId}:anonymous:${geometry.type}`;
|
|
10006
|
+
};
|
|
10007
|
+
const captureOnce = (key, report, sessionReportedDedupeKeys, payload) => {
|
|
10008
|
+
if (sessionReportedDedupeKeys.has(key))
|
|
10009
|
+
return; // skip sentry warning if already reported this session
|
|
10010
|
+
sessionReportedDedupeKeys.add(key); // add key to ref set to prevent duplicate warnings in this session
|
|
10011
|
+
report(payload);
|
|
10012
|
+
};
|
|
10013
|
+
/**
|
|
10014
|
+
* Report to Sentry each dropped invalid feature via `report`, at most once per handle/feature per session.
|
|
10015
|
+
* Note: `computeFillTiling` emits `console.warn` separately.
|
|
10016
|
+
*/
|
|
10017
|
+
const captureDroppedInvalidFillTilingFeatures = (droppedInvalidFeatures, layerHandleId, sessionReportedDedupeKeys, report) => {
|
|
10018
|
+
for (const entry of droppedInvalidFeatures) {
|
|
10019
|
+
const key = resolveDroppedFeatureDedupeKey(layerHandleId, entry);
|
|
10020
|
+
const featureId = entry.feature.id !== undefined ? String(entry.feature.id) : undefined;
|
|
10021
|
+
captureOnce(key, report, sessionReportedDedupeKeys, {
|
|
10022
|
+
layerHandleId,
|
|
10023
|
+
featureId,
|
|
10024
|
+
validationError: entry.validationError,
|
|
10025
|
+
});
|
|
10026
|
+
}
|
|
10027
|
+
};
|
|
10028
|
+
|
|
9953
10029
|
// ============================================================================
|
|
9954
10030
|
// Default resolver
|
|
9955
10031
|
// ============================================================================
|
|
@@ -10142,6 +10218,9 @@ const pickFillPromotionCandidates = (fillHits, compareByArea) => {
|
|
|
10142
10218
|
* @internal
|
|
10143
10219
|
*/
|
|
10144
10220
|
const useShapeFillTiling = ({ api, handles, onFillGeometriesChange, onZIndexOverridesChange, onSettledHover, selectedFeatureId, selectedHandleId, suppressedFillIdsByHandle, }) => {
|
|
10221
|
+
const errorHandler = reactCoreHooks.useErrorHandlerOrNull();
|
|
10222
|
+
/** Handle/feature keys already reported to Sentry this session. */
|
|
10223
|
+
const sessionReportedDedupeKeysRef = react.useRef(new Set());
|
|
10145
10224
|
const tilingContentKey = react.useMemo(() => buildTilingContentKey(handles), [handles]);
|
|
10146
10225
|
const tilingHandles = react.useMemo(() => handles.filter((handle) => handle.layerType === "shapes" && handle.overlap?.mode === "tile"), [handles]);
|
|
10147
10226
|
const hasTilingHandles = tilingHandles.length > 0;
|
|
@@ -10375,7 +10454,7 @@ const useShapeFillTiling = ({ api, handles, onFillGeometriesChange, onZIndexOver
|
|
|
10375
10454
|
const nextRestingFills = new Map();
|
|
10376
10455
|
const nextRestingZIndices = new Map();
|
|
10377
10456
|
for (const handle of currentHandles) {
|
|
10378
|
-
const { fillGeometries, featureToGroupKey, featureZIndex } = computeFillTiling({
|
|
10457
|
+
const { fillGeometries, featureToGroupKey, featureZIndex, droppedInvalidFeatures } = computeFillTiling({
|
|
10379
10458
|
features: handle.features.features,
|
|
10380
10459
|
viewportBounds: GLOBAL_BOUNDS,
|
|
10381
10460
|
resolveStackOrder: handle.overlap?.resolveStackOrder ?? defaultShapeStackOrder,
|
|
@@ -10383,6 +10462,14 @@ const useShapeFillTiling = ({ api, handles, onFillGeometriesChange, onZIndexOver
|
|
|
10383
10462
|
layerGeodesic: handle.style.geodesic,
|
|
10384
10463
|
featureStyles: handle.featureStyles,
|
|
10385
10464
|
});
|
|
10465
|
+
if (errorHandler !== null && droppedInvalidFeatures.length > 0) {
|
|
10466
|
+
captureDroppedInvalidFillTilingFeatures(droppedInvalidFeatures, handle.id, sessionReportedDedupeKeysRef.current, payload => {
|
|
10467
|
+
errorHandler.captureException(new Error(`Schema-invalid polygonal geometry excluded from fill tiling: ${JSON.stringify(payload)}`), {
|
|
10468
|
+
level: "warning",
|
|
10469
|
+
fingerprint: ["react-map", "fill-tiling", "invalid-polygonal-geometry"],
|
|
10470
|
+
});
|
|
10471
|
+
});
|
|
10472
|
+
}
|
|
10386
10473
|
featureToGroupKeyByHandleRef.current.set(handle.id, new Map(featureToGroupKey));
|
|
10387
10474
|
if (fillGeometries.size > 0) {
|
|
10388
10475
|
nextRestingFills.set(handle.id, fillGeometries);
|
|
@@ -10395,7 +10482,7 @@ const useShapeFillTiling = ({ api, handles, onFillGeometriesChange, onZIndexOver
|
|
|
10395
10482
|
restingZIndexByHandleRef.current = nextRestingZIndices;
|
|
10396
10483
|
retrySelectionPromotionRef.current?.();
|
|
10397
10484
|
emitWithPromotion();
|
|
10398
|
-
}, [emitWithPromotion]);
|
|
10485
|
+
}, [emitWithPromotion, errorHandler]);
|
|
10399
10486
|
const flushPendingRestingRecompute = react.useCallback(() => {
|
|
10400
10487
|
if (!pendingRestingRecomputeRef.current)
|
|
10401
10488
|
return;
|
package/index.esm.js
CHANGED
|
@@ -9,12 +9,12 @@ import { twMerge } from 'tailwind-merge';
|
|
|
9
9
|
import { useFloating, autoUpdate, offset, flip, useHover, safePolygon, useInteractions, shift, hide, useDismiss, useRole } from '@floating-ui/react';
|
|
10
10
|
import { createPortal } from 'react-dom';
|
|
11
11
|
import { isEqual } from 'es-toolkit';
|
|
12
|
-
import { useGeolocation } from '@trackunit/react-core-hooks';
|
|
12
|
+
import { useGeolocation, useErrorHandlerOrNull } from '@trackunit/react-core-hooks';
|
|
13
13
|
import { Checkbox, ToggleSwitch, RadioGroup as RadioGroup$1, RadioItem, FormGroup, Search as Search$1, SelectField, BaseSelect } from '@trackunit/react-form-components';
|
|
14
14
|
import { z } from 'zod';
|
|
15
15
|
import { PieChart } from 'react-minimal-pie-chart';
|
|
16
16
|
import { tailwindPalette } from '@trackunit/ui-design-tokens';
|
|
17
|
-
import { lngLatToMercatorPxWS, validateBboxWithFallback, extractPositionsFromGeometry, geoJsonPositionSchema, validateFeatureCollection, EMPTY_FEATURE_COLLECTION, validateBbox, projectPolygonalToWebMercator, geoJsonPolygonDifference, unprojectPolygonalFromWebMercator, distanceToGeoJsonPolygonBoundary, getGeoJsonPolygonIntersection,
|
|
17
|
+
import { lngLatToMercatorPxWS, validateBboxWithFallback, extractPositionsFromGeometry, geoJsonPositionSchema, validateFeatureCollection, EMPTY_FEATURE_COLLECTION, validateBbox, projectPolygonalToWebMercator, geoJsonPolygonDifference, unprojectPolygonalFromWebMercator, geoJsonPolygonSchema, geoJsonMultiPolygonSchema, distanceToGeoJsonPolygonBoundary, getGeoJsonPolygonIntersection, isFullyContainedInGeometry, isGeoJsonPointInPolygon, isBboxInsideFeatureCollection, extractEdges, computeGeometryCentroid, mercatorPxToLngLatWS, isPositionInsideRing, lngLatToWebMercatorPx, edgePixelLength, isFullyContainedInGeoJsonGeometry } from '@trackunit/geo-json-utils';
|
|
18
18
|
import { darkenColor, lightenColor } from '@trackunit/react-map-color-utils';
|
|
19
19
|
|
|
20
20
|
var defaultTranslations = {
|
|
@@ -7701,7 +7701,9 @@ const buildContendedShapes = (group) => group.map(record => {
|
|
|
7701
7701
|
for (const other of group) {
|
|
7702
7702
|
if (other.id === record.id)
|
|
7703
7703
|
continue;
|
|
7704
|
-
|
|
7704
|
+
// Pure predicate: geometry was validated once at the computeFillTiling
|
|
7705
|
+
// boundary, so this O(n²) loop performs no safeParse per pair (SAGA-664).
|
|
7706
|
+
if (isFullyContainedInGeometry(record.geometry, other.geometry)) {
|
|
7705
7707
|
containedIn.push(other.id);
|
|
7706
7708
|
}
|
|
7707
7709
|
}
|
|
@@ -7774,6 +7776,37 @@ const computePromotionGroupFills = (members, winnerId, restingPeerFills = new Ma
|
|
|
7774
7776
|
};
|
|
7775
7777
|
const computeFillTiling = (input) => {
|
|
7776
7778
|
const { features, viewportBounds, resolveStackOrder, selectedFeatureId, suppressedFeatureIds, layerGeodesic, featureStyles, } = input;
|
|
7779
|
+
// Validation boundary (SAGA-664): validate each input feature's polygonal
|
|
7780
|
+
// geometry once here — O(n) — dropping malformed geometry. The downstream O(n²)
|
|
7781
|
+
// overlap loop then runs the pure predicates on trusted geometry without a
|
|
7782
|
+
// safeParse per pair. Validate the input (not the densified output), since
|
|
7783
|
+
// densification is our own transform of already-valid geometry.
|
|
7784
|
+
const validFeatures = [];
|
|
7785
|
+
const droppedInvalidFeatures = [];
|
|
7786
|
+
for (const feature of features) {
|
|
7787
|
+
const geometry = polygonalGeometry(feature);
|
|
7788
|
+
if (geometry === null)
|
|
7789
|
+
continue;
|
|
7790
|
+
const parsed = geometry.type === "Polygon"
|
|
7791
|
+
? geoJsonPolygonSchema.safeParse(geometry)
|
|
7792
|
+
: geoJsonMultiPolygonSchema.safeParse(geometry);
|
|
7793
|
+
if (!parsed.success) {
|
|
7794
|
+
droppedInvalidFeatures.push({
|
|
7795
|
+
feature,
|
|
7796
|
+
validationError: parsed.error.format(),
|
|
7797
|
+
});
|
|
7798
|
+
continue;
|
|
7799
|
+
}
|
|
7800
|
+
validFeatures.push(feature);
|
|
7801
|
+
}
|
|
7802
|
+
if (droppedInvalidFeatures.length > 0) {
|
|
7803
|
+
const idList = droppedInvalidFeatures
|
|
7804
|
+
.map(entry => (entry.feature.id !== undefined ? String(entry.feature.id) : undefined))
|
|
7805
|
+
.filter((id) => id !== undefined)
|
|
7806
|
+
.join(", ") || "(no feature ids)";
|
|
7807
|
+
// eslint-disable-next-line no-console -- Intentional: warn devs when schema-invalid polygonal geometry is excluded from fill tiling
|
|
7808
|
+
console.warn(`[computeFillTiling] Excluded ${droppedInvalidFeatures.length} feature(s) with invalid polygonal geometry from fill tiling (full fill still renders; overlap clipping is skipped): ${idList}`);
|
|
7809
|
+
}
|
|
7777
7810
|
// Densify before any polygon/boundary math so clip geometry follows great-circle
|
|
7778
7811
|
// arcs on large polygons (mirrors the ADR-0024 precedent for edge labels). Always
|
|
7779
7812
|
// defer to densifyGeodesicFeatures' per-feature resolution (geodesic override >
|
|
@@ -7782,7 +7815,7 @@ const computeFillTiling = (input) => {
|
|
|
7782
7815
|
// original collection when nothing is densified.
|
|
7783
7816
|
const featureCollection = {
|
|
7784
7817
|
type: "FeatureCollection",
|
|
7785
|
-
features:
|
|
7818
|
+
features: validFeatures,
|
|
7786
7819
|
};
|
|
7787
7820
|
const densifiedFeatures = densifyGeodesicFeatures(featureCollection, layerGeodesic ?? true, featureStyles).features;
|
|
7788
7821
|
const records = [];
|
|
@@ -7875,7 +7908,12 @@ const computeFillTiling = (input) => {
|
|
|
7875
7908
|
fillGeometries.set(id, EMPTY_FILL);
|
|
7876
7909
|
}
|
|
7877
7910
|
}
|
|
7878
|
-
return {
|
|
7911
|
+
return {
|
|
7912
|
+
fillGeometries,
|
|
7913
|
+
featureToGroupKey,
|
|
7914
|
+
featureZIndex,
|
|
7915
|
+
droppedInvalidFeatures,
|
|
7916
|
+
};
|
|
7879
7917
|
};
|
|
7880
7918
|
|
|
7881
7919
|
const DEFAULT_ZOOM = 0;
|
|
@@ -9949,6 +9987,44 @@ const useShapeDecorations = ({ onDecorationsChange, onStyleOverridesChange, api,
|
|
|
9949
9987
|
return useMemo(() => ({ subscribe, getSnapshot }), [subscribe, getSnapshot]);
|
|
9950
9988
|
};
|
|
9951
9989
|
|
|
9990
|
+
const resolveDroppedFeatureDedupeKey = (layerHandleId, entry) => {
|
|
9991
|
+
if (entry.feature.id !== undefined) {
|
|
9992
|
+
return `${layerHandleId}:${String(entry.feature.id)}`;
|
|
9993
|
+
}
|
|
9994
|
+
const geometry = entry.feature.geometry;
|
|
9995
|
+
if (geometry === null) {
|
|
9996
|
+
return `${layerHandleId}:anonymous:null-geometry`;
|
|
9997
|
+
}
|
|
9998
|
+
if (geometry.type === "Polygon") {
|
|
9999
|
+
return `${layerHandleId}:anonymous:Polygon:${geometry.coordinates.flat(2).length}`;
|
|
10000
|
+
}
|
|
10001
|
+
if (geometry.type === "MultiPolygon") {
|
|
10002
|
+
return `${layerHandleId}:anonymous:MultiPolygon:${geometry.coordinates.flat(3).length}`;
|
|
10003
|
+
}
|
|
10004
|
+
return `${layerHandleId}:anonymous:${geometry.type}`;
|
|
10005
|
+
};
|
|
10006
|
+
const captureOnce = (key, report, sessionReportedDedupeKeys, payload) => {
|
|
10007
|
+
if (sessionReportedDedupeKeys.has(key))
|
|
10008
|
+
return; // skip sentry warning if already reported this session
|
|
10009
|
+
sessionReportedDedupeKeys.add(key); // add key to ref set to prevent duplicate warnings in this session
|
|
10010
|
+
report(payload);
|
|
10011
|
+
};
|
|
10012
|
+
/**
|
|
10013
|
+
* Report to Sentry each dropped invalid feature via `report`, at most once per handle/feature per session.
|
|
10014
|
+
* Note: `computeFillTiling` emits `console.warn` separately.
|
|
10015
|
+
*/
|
|
10016
|
+
const captureDroppedInvalidFillTilingFeatures = (droppedInvalidFeatures, layerHandleId, sessionReportedDedupeKeys, report) => {
|
|
10017
|
+
for (const entry of droppedInvalidFeatures) {
|
|
10018
|
+
const key = resolveDroppedFeatureDedupeKey(layerHandleId, entry);
|
|
10019
|
+
const featureId = entry.feature.id !== undefined ? String(entry.feature.id) : undefined;
|
|
10020
|
+
captureOnce(key, report, sessionReportedDedupeKeys, {
|
|
10021
|
+
layerHandleId,
|
|
10022
|
+
featureId,
|
|
10023
|
+
validationError: entry.validationError,
|
|
10024
|
+
});
|
|
10025
|
+
}
|
|
10026
|
+
};
|
|
10027
|
+
|
|
9952
10028
|
// ============================================================================
|
|
9953
10029
|
// Default resolver
|
|
9954
10030
|
// ============================================================================
|
|
@@ -10141,6 +10217,9 @@ const pickFillPromotionCandidates = (fillHits, compareByArea) => {
|
|
|
10141
10217
|
* @internal
|
|
10142
10218
|
*/
|
|
10143
10219
|
const useShapeFillTiling = ({ api, handles, onFillGeometriesChange, onZIndexOverridesChange, onSettledHover, selectedFeatureId, selectedHandleId, suppressedFillIdsByHandle, }) => {
|
|
10220
|
+
const errorHandler = useErrorHandlerOrNull();
|
|
10221
|
+
/** Handle/feature keys already reported to Sentry this session. */
|
|
10222
|
+
const sessionReportedDedupeKeysRef = useRef(new Set());
|
|
10144
10223
|
const tilingContentKey = useMemo(() => buildTilingContentKey(handles), [handles]);
|
|
10145
10224
|
const tilingHandles = useMemo(() => handles.filter((handle) => handle.layerType === "shapes" && handle.overlap?.mode === "tile"), [handles]);
|
|
10146
10225
|
const hasTilingHandles = tilingHandles.length > 0;
|
|
@@ -10374,7 +10453,7 @@ const useShapeFillTiling = ({ api, handles, onFillGeometriesChange, onZIndexOver
|
|
|
10374
10453
|
const nextRestingFills = new Map();
|
|
10375
10454
|
const nextRestingZIndices = new Map();
|
|
10376
10455
|
for (const handle of currentHandles) {
|
|
10377
|
-
const { fillGeometries, featureToGroupKey, featureZIndex } = computeFillTiling({
|
|
10456
|
+
const { fillGeometries, featureToGroupKey, featureZIndex, droppedInvalidFeatures } = computeFillTiling({
|
|
10378
10457
|
features: handle.features.features,
|
|
10379
10458
|
viewportBounds: GLOBAL_BOUNDS,
|
|
10380
10459
|
resolveStackOrder: handle.overlap?.resolveStackOrder ?? defaultShapeStackOrder,
|
|
@@ -10382,6 +10461,14 @@ const useShapeFillTiling = ({ api, handles, onFillGeometriesChange, onZIndexOver
|
|
|
10382
10461
|
layerGeodesic: handle.style.geodesic,
|
|
10383
10462
|
featureStyles: handle.featureStyles,
|
|
10384
10463
|
});
|
|
10464
|
+
if (errorHandler !== null && droppedInvalidFeatures.length > 0) {
|
|
10465
|
+
captureDroppedInvalidFillTilingFeatures(droppedInvalidFeatures, handle.id, sessionReportedDedupeKeysRef.current, payload => {
|
|
10466
|
+
errorHandler.captureException(new Error(`Schema-invalid polygonal geometry excluded from fill tiling: ${JSON.stringify(payload)}`), {
|
|
10467
|
+
level: "warning",
|
|
10468
|
+
fingerprint: ["react-map", "fill-tiling", "invalid-polygonal-geometry"],
|
|
10469
|
+
});
|
|
10470
|
+
});
|
|
10471
|
+
}
|
|
10385
10472
|
featureToGroupKeyByHandleRef.current.set(handle.id, new Map(featureToGroupKey));
|
|
10386
10473
|
if (fillGeometries.size > 0) {
|
|
10387
10474
|
nextRestingFills.set(handle.id, fillGeometries);
|
|
@@ -10394,7 +10481,7 @@ const useShapeFillTiling = ({ api, handles, onFillGeometriesChange, onZIndexOver
|
|
|
10394
10481
|
restingZIndexByHandleRef.current = nextRestingZIndices;
|
|
10395
10482
|
retrySelectionPromotionRef.current?.();
|
|
10396
10483
|
emitWithPromotion();
|
|
10397
|
-
}, [emitWithPromotion]);
|
|
10484
|
+
}, [emitWithPromotion, errorHandler]);
|
|
10398
10485
|
const flushPendingRestingRecompute = useCallback(() => {
|
|
10399
10486
|
if (!pendingRestingRecomputeRef.current)
|
|
10400
10487
|
return;
|
package/package.json
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trackunit/react-map",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.9-alpha-636ec415cd1.0",
|
|
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": "2.2.
|
|
11
|
-
"@trackunit/css-class-variance-utilities": "1.13.
|
|
12
|
-
"@trackunit/react-form-components": "2.2.
|
|
13
|
-
"@trackunit/react-core-hooks": "1.18.
|
|
14
|
-
"@trackunit/geo-json-utils": "1.14.
|
|
15
|
-
"@trackunit/i18n-library-translation": "2.1.
|
|
10
|
+
"@trackunit/react-components": "2.2.6-alpha-636ec415cd1.0",
|
|
11
|
+
"@trackunit/css-class-variance-utilities": "1.13.64-alpha-636ec415cd1.0",
|
|
12
|
+
"@trackunit/react-form-components": "2.2.7-alpha-636ec415cd1.0",
|
|
13
|
+
"@trackunit/react-core-hooks": "1.18.5-alpha-636ec415cd1.0",
|
|
14
|
+
"@trackunit/geo-json-utils": "1.14.67-alpha-636ec415cd1.0",
|
|
15
|
+
"@trackunit/i18n-library-translation": "2.1.5-alpha-636ec415cd1.0",
|
|
16
16
|
"react-minimal-pie-chart": "^8.4.0",
|
|
17
|
-
"@trackunit/react-map-adapter-shared": "0.0.
|
|
18
|
-
"@trackunit/react-map-color-utils": "0.0.
|
|
19
|
-
"@trackunit/ui-design-tokens": "1.13.
|
|
17
|
+
"@trackunit/react-map-adapter-shared": "0.0.48-alpha-636ec415cd1.0",
|
|
18
|
+
"@trackunit/react-map-color-utils": "0.0.33-alpha-636ec415cd1.0",
|
|
19
|
+
"@trackunit/ui-design-tokens": "1.13.63-alpha-636ec415cd1.0",
|
|
20
20
|
"@floating-ui/react": "^0.26.25",
|
|
21
21
|
"es-toolkit": "^1.39.10",
|
|
22
22
|
"tailwind-merge": "^2.0.0",
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { DroppedInvalidFillTilingFeature } from "./shapeFillTiling";
|
|
2
|
+
type ReportDroppedInvalidFillTilingEvent = (payload: Record<string, unknown>) => void;
|
|
3
|
+
/**
|
|
4
|
+
* Report to Sentry each dropped invalid feature via `report`, at most once per handle/feature per session.
|
|
5
|
+
* Note: `computeFillTiling` emits `console.warn` separately.
|
|
6
|
+
*/
|
|
7
|
+
export declare const captureDroppedInvalidFillTilingFeatures: (droppedInvalidFeatures: ReadonlyArray<DroppedInvalidFillTilingFeature>, layerHandleId: string, sessionReportedDedupeKeys: Set<string>, report: ReportDroppedInvalidFillTilingEvent) => void;
|
|
8
|
+
export {};
|
|
@@ -68,6 +68,13 @@ export type FillTilingResult = Readonly<{
|
|
|
68
68
|
* only overrides the promoted feature's value to raise it above its peers.
|
|
69
69
|
*/
|
|
70
70
|
featureZIndex: ReadonlyMap<string, number>;
|
|
71
|
+
/** Features excluded from fill tiling when boundary schema validation fails. */
|
|
72
|
+
droppedInvalidFeatures: ReadonlyArray<DroppedInvalidFillTilingFeature>;
|
|
73
|
+
}>;
|
|
74
|
+
export type DroppedInvalidFillTilingFeature = Readonly<{
|
|
75
|
+
feature: GeoJsonFeature;
|
|
76
|
+
/** `safeParse` failure details (`parsed.error.format()`). */
|
|
77
|
+
validationError: unknown;
|
|
71
78
|
}>;
|
|
72
79
|
/**
|
|
73
80
|
* Planar area of any GeoJSON geometry (outer rings minus holes). Returns 0 for
|
package/migrations/entry.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"entry.js","sourceRoot":"","sources":["../../../../../libs/react/map/migrations/entry.ts"],"names":[],"mappings":"","sourcesContent":["export {};\n"]}
|