@trackunit/react-map 0.1.25 → 0.1.27

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.
Files changed (3) hide show
  1. package/index.cjs.js +79 -21
  2. package/index.esm.js +79 -21
  3. package/package.json +11 -11
package/index.cjs.js CHANGED
@@ -7577,14 +7577,17 @@ const degreesPerPixel = (zoom, tileSize) => 360 / (tileSize * Math.pow(2, zoom))
7577
7577
  const shapesUnderCursor = (position, features, options) => {
7578
7578
  const { zoom, tileSize, strokeWidthFor, visibleFillFor, layerGeodesic, featureStyles } = options;
7579
7579
  // Densify before boundary-distance and point-in-polygon math so stroke/fill
7580
- // hit-tests follow great-circle arcs on large polygons.
7580
+ // hit-tests follow great-circle arcs on large polygons. Always defer to
7581
+ // densifyGeodesicFeatures' per-feature resolution (geodesic override > layer
7582
+ // default) rather than short-circuiting on layerGeodesic === false — that
7583
+ // matches the Mapbox render path, so the hit-test geometry never diverges
7584
+ // from the painted edge when a feature opts into geodesic under a non-geodesic
7585
+ // layer. The util returns the original collection when nothing is densified.
7581
7586
  const featureCollection = {
7582
7587
  type: "FeatureCollection",
7583
7588
  features: Array.from(features),
7584
7589
  };
7585
- const densifiedFeatures = layerGeodesic !== false
7586
- ? reactMapAdapterShared.densifyGeodesicFeatures(featureCollection, layerGeodesic ?? true, featureStyles).features
7587
- : features;
7590
+ const densifiedFeatures = reactMapAdapterShared.densifyGeodesicFeatures(featureCollection, layerGeodesic ?? true, featureStyles).features;
7588
7591
  const degPerPx = degreesPerPixel(zoom, tileSize);
7589
7592
  const hits = [];
7590
7593
  for (const feature of densifiedFeatures) {
@@ -7696,14 +7699,16 @@ const promoteToFront = (order, id) => order.includes(id) ? [id, ...order.filter(
7696
7699
  * preserved and translucent fills do not compound under the winner.
7697
7700
  */
7698
7701
  const computePromotionGroupFills = (members, winnerId, restingPeerFills = new Map(), layerGeodesic = undefined, featureStyles = undefined) => {
7699
- // Densify before peer-vs-winner clip so promotion clips follow great-circle arcs.
7702
+ // Densify before peer-vs-winner clip so promotion clips follow great-circle
7703
+ // arcs. Always defer to densifyGeodesicFeatures' per-feature resolution
7704
+ // (geodesic override > layer default) rather than short-circuiting on
7705
+ // layerGeodesic === false — keeps the clip geometry in step with the render
7706
+ // path. The util returns the original collection when nothing is densified.
7700
7707
  const featureCollection = {
7701
7708
  type: "FeatureCollection",
7702
7709
  features: Array.from(members),
7703
7710
  };
7704
- const densifiedMembers = layerGeodesic !== false
7705
- ? reactMapAdapterShared.densifyGeodesicFeatures(featureCollection, layerGeodesic ?? true, featureStyles).features
7706
- : members;
7711
+ const densifiedMembers = reactMapAdapterShared.densifyGeodesicFeatures(featureCollection, layerGeodesic ?? true, featureStyles).features;
7707
7712
  const geometries = new Map();
7708
7713
  for (const feature of densifiedMembers) {
7709
7714
  if (feature.id === undefined)
@@ -7743,14 +7748,16 @@ const computePromotionGroupFills = (members, winnerId, restingPeerFills = new Ma
7743
7748
  const computeFillTiling = (input) => {
7744
7749
  const { features, viewportBounds, resolveStackOrder, selectedFeatureId, suppressedFeatureIds, layerGeodesic, featureStyles, } = input;
7745
7750
  // Densify before any polygon/boundary math so clip geometry follows great-circle
7746
- // arcs on large polygons (mirrors the ADR-0024 precedent for edge labels).
7751
+ // arcs on large polygons (mirrors the ADR-0024 precedent for edge labels). Always
7752
+ // defer to densifyGeodesicFeatures' per-feature resolution (geodesic override >
7753
+ // layer default) rather than short-circuiting on layerGeodesic === false — keeps
7754
+ // the clip geometry in step with the Mapbox render path. The util returns the
7755
+ // original collection when nothing is densified.
7747
7756
  const featureCollection = {
7748
7757
  type: "FeatureCollection",
7749
7758
  features: Array.from(features),
7750
7759
  };
7751
- const densifiedFeatures = layerGeodesic !== false
7752
- ? reactMapAdapterShared.densifyGeodesicFeatures(featureCollection, layerGeodesic ?? true, featureStyles).features
7753
- : features;
7760
+ const densifiedFeatures = reactMapAdapterShared.densifyGeodesicFeatures(featureCollection, layerGeodesic ?? true, featureStyles).features;
7754
7761
  const records = [];
7755
7762
  for (const feature of densifiedFeatures) {
7756
7763
  if (feature.id === undefined)
@@ -10128,6 +10135,8 @@ const useShapeFillTiling = ({ api, handles, onFillGeometriesChange, onZIndexOver
10128
10135
  }, [suppressedFillIdsByHandle]);
10129
10136
  const debounceTimerRef = react.useRef(null);
10130
10137
  const idleRecomputeTimerRef = react.useRef(null);
10138
+ /** Pending deferred selection re-tile (double-rAF handle); see selection effect. */
10139
+ const selectionRecomputeRafRef = react.useRef(null);
10131
10140
  // Currently emitted overrides — used for hit-testing visible fills in handleSettle.
10132
10141
  const prevFillOverridesRef = react.useRef(EMPTY_OVERRIDES);
10133
10142
  const prevZIndexOverridesRef = react.useRef(EMPTY_ZINDEX_OVERRIDES);
@@ -10382,8 +10391,30 @@ const useShapeFillTiling = ({ api, handles, onFillGeometriesChange, onZIndexOver
10382
10391
  return;
10383
10392
  recompute();
10384
10393
  }, [recompute, tilingContentKey]);
10394
+ // Selection forces the selected feature to the front of its overlap group,
10395
+ // which requires a resting re-tile (computeFillTiling depends on
10396
+ // selectedFeatureId via resolveStackOrder). For large layers that re-tile is
10397
+ // expensive (~600ms for 200 polygons over global bounds) and must NOT block
10398
+ // the interaction's first paint — e.g. a panel opening on the clicked feature.
10399
+ // Defer it past the next paint with a double rAF so the click commits and
10400
+ // paints immediately, then the selected polygon restacks one frame later.
10401
+ // Rapid selections coalesce: each schedule cancels the previous pending one.
10385
10402
  react.useEffect(() => {
10386
- recompute();
10403
+ if (selectionRecomputeRafRef.current !== null) {
10404
+ cancelAnimationFrame(selectionRecomputeRafRef.current);
10405
+ }
10406
+ selectionRecomputeRafRef.current = requestAnimationFrame(() => {
10407
+ selectionRecomputeRafRef.current = requestAnimationFrame(() => {
10408
+ selectionRecomputeRafRef.current = null;
10409
+ recompute();
10410
+ });
10411
+ });
10412
+ return () => {
10413
+ if (selectionRecomputeRafRef.current !== null) {
10414
+ cancelAnimationFrame(selectionRecomputeRafRef.current);
10415
+ selectionRecomputeRafRef.current = null;
10416
+ }
10417
+ };
10387
10418
  }, [recompute, selectedFeatureId]);
10388
10419
  // Suppression changes must trigger a recompute so EMPTY_FILL entries are
10389
10420
  // added / removed even when the map is idle (covers the decorations-vs-tiling
@@ -10635,7 +10666,24 @@ const Layers = ({ layers, api, onEntityClick, onEntityDblClick }) => {
10635
10666
  const [viewportStyleOverrides, setViewportStyleOverrides] = react.useState(new Map());
10636
10667
  const [fillGeometryOverrides, setFillGeometryOverrides] = react.useState(new Map());
10637
10668
  const [zIndexOverrides, setZIndexOverrides] = react.useState(new Map());
10669
+ // Mirror the authoritative hovered entity into a ref. The settle channel fires
10670
+ // on a ~120ms debounce (outside React's render cycle) and needs to read the
10671
+ // current hover synchronously.
10672
+ const hoveredEntityRef = react.useRef(layers.interaction.hoveredEntity);
10673
+ react.useEffect(() => {
10674
+ hoveredEntityRef.current = layers.interaction.hoveredEntity;
10675
+ }, [layers.interaction.hoveredEntity]);
10638
10676
  const onSettledHover = react.useCallback((handleId, featureId) => {
10677
+ // Z-order truth: markers and clusters render above shapes (DOM/symbol portals
10678
+ // over the polygon canvas). When a point entity is the current hovered entity
10679
+ // it is the authoritative top hit under the cursor, so the shape settle — which
10680
+ // is not itself position-gated against the marker layer — must not overwrite it.
10681
+ // Without this guard, moving onto an asset pill that overlaps a site would leave
10682
+ // the covered site as `hoveredEntity` (a stale cross-channel hover write). We read
10683
+ // the single source of truth (`hoveredEntity`) rather than a parallel guard flag.
10684
+ const hovered = hoveredEntityRef.current;
10685
+ if (hovered !== null && (hovered.type === "marker" || hovered.type === "cluster"))
10686
+ return;
10639
10687
  // Fill tiling only fires on polygonal features — shapeType is always "polygon".
10640
10688
  layers.hover({ type: "shape", id: featureId, handleId, shapeType: "polygon" });
10641
10689
  }, [layers]);
@@ -10697,17 +10745,27 @@ const Layers = ({ layers, api, onEntityClick, onEntityDblClick }) => {
10697
10745
  });
10698
10746
  }, [layers.handles, adaptiveByLayer, viewportStyleOverrides, fillGeometryOverrides, zIndexOverrides, queryAt]);
10699
10747
  const { renderedSourceIds, hasSourceReadiness, readyRevision } = useLayerHandleSync(layerPort, enrichedHandles, layers.interaction, decorationLayers);
10700
- const hoveredEntityRef = react.useRef(layers.interaction.hoveredEntity);
10701
- react.useEffect(() => {
10702
- hoveredEntityRef.current = layers.interaction.hoveredEntity;
10703
- }, [layers.interaction.hoveredEntity]);
10704
10748
  const coordinatedSelect = react.useCallback((entity) => {
10705
- if (entity !== null && hoveredEntityRef.current !== null) {
10706
- layers.select(hoveredEntityRef.current);
10707
- }
10708
- else {
10749
+ const hovered = hoveredEntityRef.current;
10750
+ if (entity === null || hovered === null) {
10709
10751
  layers.select(entity);
10752
+ return;
10753
+ }
10754
+ // Default to the entity the click actually carried — it is the freshest,
10755
+ // most direct signal of what the user hit, and it hit-tests per element
10756
+ // for markers/clusters/routes. The single exception is a shape-on-shape
10757
+ // conflict: the adapter resolves polygon clicks with its own z-order and
10758
+ // full (unclipped) hit areas, so it cannot honor the fill-tiling ownership
10759
+ // model that decides which overlapping site is visually on top (ADR-0021).
10760
+ // There the settle-resolved hovered shape is the precise winner, so we
10761
+ // trust it instead. This is also why an asset pill overlapping a site
10762
+ // selects the asset: a marker click is never a shape, so it bypasses the
10763
+ // exception even when a stale site shape is still the hovered entity.
10764
+ if (entity.type === "shape" && hovered.type === "shape") {
10765
+ layers.select(hovered);
10766
+ return;
10710
10767
  }
10768
+ layers.select(entity);
10711
10769
  }, [layers]);
10712
10770
  const fitOnDblClick = useDblClickFit(api, layers.handles);
10713
10771
  const handleDblClick = react.useCallback((entity) => {
package/index.esm.js CHANGED
@@ -7576,14 +7576,17 @@ const degreesPerPixel = (zoom, tileSize) => 360 / (tileSize * Math.pow(2, zoom))
7576
7576
  const shapesUnderCursor = (position, features, options) => {
7577
7577
  const { zoom, tileSize, strokeWidthFor, visibleFillFor, layerGeodesic, featureStyles } = options;
7578
7578
  // Densify before boundary-distance and point-in-polygon math so stroke/fill
7579
- // hit-tests follow great-circle arcs on large polygons.
7579
+ // hit-tests follow great-circle arcs on large polygons. Always defer to
7580
+ // densifyGeodesicFeatures' per-feature resolution (geodesic override > layer
7581
+ // default) rather than short-circuiting on layerGeodesic === false — that
7582
+ // matches the Mapbox render path, so the hit-test geometry never diverges
7583
+ // from the painted edge when a feature opts into geodesic under a non-geodesic
7584
+ // layer. The util returns the original collection when nothing is densified.
7580
7585
  const featureCollection = {
7581
7586
  type: "FeatureCollection",
7582
7587
  features: Array.from(features),
7583
7588
  };
7584
- const densifiedFeatures = layerGeodesic !== false
7585
- ? densifyGeodesicFeatures(featureCollection, layerGeodesic ?? true, featureStyles).features
7586
- : features;
7589
+ const densifiedFeatures = densifyGeodesicFeatures(featureCollection, layerGeodesic ?? true, featureStyles).features;
7587
7590
  const degPerPx = degreesPerPixel(zoom, tileSize);
7588
7591
  const hits = [];
7589
7592
  for (const feature of densifiedFeatures) {
@@ -7695,14 +7698,16 @@ const promoteToFront = (order, id) => order.includes(id) ? [id, ...order.filter(
7695
7698
  * preserved and translucent fills do not compound under the winner.
7696
7699
  */
7697
7700
  const computePromotionGroupFills = (members, winnerId, restingPeerFills = new Map(), layerGeodesic = undefined, featureStyles = undefined) => {
7698
- // Densify before peer-vs-winner clip so promotion clips follow great-circle arcs.
7701
+ // Densify before peer-vs-winner clip so promotion clips follow great-circle
7702
+ // arcs. Always defer to densifyGeodesicFeatures' per-feature resolution
7703
+ // (geodesic override > layer default) rather than short-circuiting on
7704
+ // layerGeodesic === false — keeps the clip geometry in step with the render
7705
+ // path. The util returns the original collection when nothing is densified.
7699
7706
  const featureCollection = {
7700
7707
  type: "FeatureCollection",
7701
7708
  features: Array.from(members),
7702
7709
  };
7703
- const densifiedMembers = layerGeodesic !== false
7704
- ? densifyGeodesicFeatures(featureCollection, layerGeodesic ?? true, featureStyles).features
7705
- : members;
7710
+ const densifiedMembers = densifyGeodesicFeatures(featureCollection, layerGeodesic ?? true, featureStyles).features;
7706
7711
  const geometries = new Map();
7707
7712
  for (const feature of densifiedMembers) {
7708
7713
  if (feature.id === undefined)
@@ -7742,14 +7747,16 @@ const computePromotionGroupFills = (members, winnerId, restingPeerFills = new Ma
7742
7747
  const computeFillTiling = (input) => {
7743
7748
  const { features, viewportBounds, resolveStackOrder, selectedFeatureId, suppressedFeatureIds, layerGeodesic, featureStyles, } = input;
7744
7749
  // Densify before any polygon/boundary math so clip geometry follows great-circle
7745
- // arcs on large polygons (mirrors the ADR-0024 precedent for edge labels).
7750
+ // arcs on large polygons (mirrors the ADR-0024 precedent for edge labels). Always
7751
+ // defer to densifyGeodesicFeatures' per-feature resolution (geodesic override >
7752
+ // layer default) rather than short-circuiting on layerGeodesic === false — keeps
7753
+ // the clip geometry in step with the Mapbox render path. The util returns the
7754
+ // original collection when nothing is densified.
7746
7755
  const featureCollection = {
7747
7756
  type: "FeatureCollection",
7748
7757
  features: Array.from(features),
7749
7758
  };
7750
- const densifiedFeatures = layerGeodesic !== false
7751
- ? densifyGeodesicFeatures(featureCollection, layerGeodesic ?? true, featureStyles).features
7752
- : features;
7759
+ const densifiedFeatures = densifyGeodesicFeatures(featureCollection, layerGeodesic ?? true, featureStyles).features;
7753
7760
  const records = [];
7754
7761
  for (const feature of densifiedFeatures) {
7755
7762
  if (feature.id === undefined)
@@ -10127,6 +10134,8 @@ const useShapeFillTiling = ({ api, handles, onFillGeometriesChange, onZIndexOver
10127
10134
  }, [suppressedFillIdsByHandle]);
10128
10135
  const debounceTimerRef = useRef(null);
10129
10136
  const idleRecomputeTimerRef = useRef(null);
10137
+ /** Pending deferred selection re-tile (double-rAF handle); see selection effect. */
10138
+ const selectionRecomputeRafRef = useRef(null);
10130
10139
  // Currently emitted overrides — used for hit-testing visible fills in handleSettle.
10131
10140
  const prevFillOverridesRef = useRef(EMPTY_OVERRIDES);
10132
10141
  const prevZIndexOverridesRef = useRef(EMPTY_ZINDEX_OVERRIDES);
@@ -10381,8 +10390,30 @@ const useShapeFillTiling = ({ api, handles, onFillGeometriesChange, onZIndexOver
10381
10390
  return;
10382
10391
  recompute();
10383
10392
  }, [recompute, tilingContentKey]);
10393
+ // Selection forces the selected feature to the front of its overlap group,
10394
+ // which requires a resting re-tile (computeFillTiling depends on
10395
+ // selectedFeatureId via resolveStackOrder). For large layers that re-tile is
10396
+ // expensive (~600ms for 200 polygons over global bounds) and must NOT block
10397
+ // the interaction's first paint — e.g. a panel opening on the clicked feature.
10398
+ // Defer it past the next paint with a double rAF so the click commits and
10399
+ // paints immediately, then the selected polygon restacks one frame later.
10400
+ // Rapid selections coalesce: each schedule cancels the previous pending one.
10384
10401
  useEffect(() => {
10385
- recompute();
10402
+ if (selectionRecomputeRafRef.current !== null) {
10403
+ cancelAnimationFrame(selectionRecomputeRafRef.current);
10404
+ }
10405
+ selectionRecomputeRafRef.current = requestAnimationFrame(() => {
10406
+ selectionRecomputeRafRef.current = requestAnimationFrame(() => {
10407
+ selectionRecomputeRafRef.current = null;
10408
+ recompute();
10409
+ });
10410
+ });
10411
+ return () => {
10412
+ if (selectionRecomputeRafRef.current !== null) {
10413
+ cancelAnimationFrame(selectionRecomputeRafRef.current);
10414
+ selectionRecomputeRafRef.current = null;
10415
+ }
10416
+ };
10386
10417
  }, [recompute, selectedFeatureId]);
10387
10418
  // Suppression changes must trigger a recompute so EMPTY_FILL entries are
10388
10419
  // added / removed even when the map is idle (covers the decorations-vs-tiling
@@ -10634,7 +10665,24 @@ const Layers = ({ layers, api, onEntityClick, onEntityDblClick }) => {
10634
10665
  const [viewportStyleOverrides, setViewportStyleOverrides] = useState(new Map());
10635
10666
  const [fillGeometryOverrides, setFillGeometryOverrides] = useState(new Map());
10636
10667
  const [zIndexOverrides, setZIndexOverrides] = useState(new Map());
10668
+ // Mirror the authoritative hovered entity into a ref. The settle channel fires
10669
+ // on a ~120ms debounce (outside React's render cycle) and needs to read the
10670
+ // current hover synchronously.
10671
+ const hoveredEntityRef = useRef(layers.interaction.hoveredEntity);
10672
+ useEffect(() => {
10673
+ hoveredEntityRef.current = layers.interaction.hoveredEntity;
10674
+ }, [layers.interaction.hoveredEntity]);
10637
10675
  const onSettledHover = useCallback((handleId, featureId) => {
10676
+ // Z-order truth: markers and clusters render above shapes (DOM/symbol portals
10677
+ // over the polygon canvas). When a point entity is the current hovered entity
10678
+ // it is the authoritative top hit under the cursor, so the shape settle — which
10679
+ // is not itself position-gated against the marker layer — must not overwrite it.
10680
+ // Without this guard, moving onto an asset pill that overlaps a site would leave
10681
+ // the covered site as `hoveredEntity` (a stale cross-channel hover write). We read
10682
+ // the single source of truth (`hoveredEntity`) rather than a parallel guard flag.
10683
+ const hovered = hoveredEntityRef.current;
10684
+ if (hovered !== null && (hovered.type === "marker" || hovered.type === "cluster"))
10685
+ return;
10638
10686
  // Fill tiling only fires on polygonal features — shapeType is always "polygon".
10639
10687
  layers.hover({ type: "shape", id: featureId, handleId, shapeType: "polygon" });
10640
10688
  }, [layers]);
@@ -10696,17 +10744,27 @@ const Layers = ({ layers, api, onEntityClick, onEntityDblClick }) => {
10696
10744
  });
10697
10745
  }, [layers.handles, adaptiveByLayer, viewportStyleOverrides, fillGeometryOverrides, zIndexOverrides, queryAt]);
10698
10746
  const { renderedSourceIds, hasSourceReadiness, readyRevision } = useLayerHandleSync(layerPort, enrichedHandles, layers.interaction, decorationLayers);
10699
- const hoveredEntityRef = useRef(layers.interaction.hoveredEntity);
10700
- useEffect(() => {
10701
- hoveredEntityRef.current = layers.interaction.hoveredEntity;
10702
- }, [layers.interaction.hoveredEntity]);
10703
10747
  const coordinatedSelect = useCallback((entity) => {
10704
- if (entity !== null && hoveredEntityRef.current !== null) {
10705
- layers.select(hoveredEntityRef.current);
10706
- }
10707
- else {
10748
+ const hovered = hoveredEntityRef.current;
10749
+ if (entity === null || hovered === null) {
10708
10750
  layers.select(entity);
10751
+ return;
10752
+ }
10753
+ // Default to the entity the click actually carried — it is the freshest,
10754
+ // most direct signal of what the user hit, and it hit-tests per element
10755
+ // for markers/clusters/routes. The single exception is a shape-on-shape
10756
+ // conflict: the adapter resolves polygon clicks with its own z-order and
10757
+ // full (unclipped) hit areas, so it cannot honor the fill-tiling ownership
10758
+ // model that decides which overlapping site is visually on top (ADR-0021).
10759
+ // There the settle-resolved hovered shape is the precise winner, so we
10760
+ // trust it instead. This is also why an asset pill overlapping a site
10761
+ // selects the asset: a marker click is never a shape, so it bypasses the
10762
+ // exception even when a stale site shape is still the hovered entity.
10763
+ if (entity.type === "shape" && hovered.type === "shape") {
10764
+ layers.select(hovered);
10765
+ return;
10709
10766
  }
10767
+ layers.select(entity);
10710
10768
  }, [layers]);
10711
10769
  const fitOnDblClick = useDblClickFit(api, layers.handles);
10712
10770
  const handleDblClick = useCallback((entity) => {
package/package.json CHANGED
@@ -1,23 +1,23 @@
1
1
  {
2
2
  "name": "@trackunit/react-map",
3
- "version": "0.1.25",
3
+ "version": "0.1.27",
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.1.36",
11
- "@trackunit/css-class-variance-utilities": "1.13.40",
12
- "@trackunit/react-form-components": "2.1.38",
13
- "@trackunit/react-core-hooks": "1.17.49",
14
- "@trackunit/geo-json-utils": "1.14.43",
15
- "@trackunit/i18n-library-translation": "2.0.37",
16
- "@trackunit/react-modal": "2.1.39",
10
+ "@trackunit/react-components": "2.1.38",
11
+ "@trackunit/css-class-variance-utilities": "1.13.42",
12
+ "@trackunit/react-form-components": "2.1.40",
13
+ "@trackunit/react-core-hooks": "1.17.51",
14
+ "@trackunit/geo-json-utils": "1.14.45",
15
+ "@trackunit/i18n-library-translation": "2.0.39",
16
+ "@trackunit/react-modal": "2.1.41",
17
17
  "react-minimal-pie-chart": "^8.4.0",
18
- "@trackunit/react-map-adapter-shared": "0.0.24",
19
- "@trackunit/react-map-color-utils": "0.0.9",
20
- "@trackunit/ui-design-tokens": "1.13.40",
18
+ "@trackunit/react-map-adapter-shared": "0.0.26",
19
+ "@trackunit/react-map-color-utils": "0.0.11",
20
+ "@trackunit/ui-design-tokens": "1.13.42",
21
21
  "@floating-ui/react": "^0.26.25",
22
22
  "es-toolkit": "^1.39.10",
23
23
  "tailwind-merge": "^2.0.0",