@trackunit/react-map 0.2.8 → 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
- if (geoJsonUtils.isFullyContainedInGeoJsonGeometry(record.geometry, other.geometry) === true) {
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: Array.from(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 { fillGeometries, featureToGroupKey, featureZIndex };
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, isFullyContainedInGeoJsonGeometry, isGeoJsonPointInPolygon, isBboxInsideFeatureCollection, extractEdges, computeGeometryCentroid, mercatorPxToLngLatWS, isPositionInsideRing, lngLatToWebMercatorPx, edgePixelLength } from '@trackunit/geo-json-utils';
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
- if (isFullyContainedInGeoJsonGeometry(record.geometry, other.geometry) === true) {
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: Array.from(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 { fillGeometries, featureToGroupKey, featureZIndex };
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.8",
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.5",
11
- "@trackunit/css-class-variance-utilities": "1.13.63",
12
- "@trackunit/react-form-components": "2.2.6",
13
- "@trackunit/react-core-hooks": "1.18.4",
14
- "@trackunit/geo-json-utils": "1.14.66",
15
- "@trackunit/i18n-library-translation": "2.1.4",
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.47",
18
- "@trackunit/react-map-color-utils": "0.0.32",
19
- "@trackunit/ui-design-tokens": "1.13.62",
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