@trackunit/react-map 0.1.3 → 0.1.5
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 +1255 -15
- package/index.esm.js +1256 -16
- package/package.json +10 -10
- package/src/index.d.ts +2 -1
- package/src/layers/types.d.ts +60 -1
- package/src/layers/useShapes/InteractiveDecoration.d.ts +3 -0
- package/src/layers/useShapes/shapeFillTiling.d.ts +114 -0
- package/src/layers/useShapes/shapeLabelResolution.d.ts +1 -1
- package/src/layers/useShapes/shapeStackOrder.d.ts +39 -0
- package/src/layers/useShapes/useManualDblClick.d.ts +13 -0
- package/src/layers/useShapes/useShapeFillTiling.d.ts +89 -0
- package/src/layers/useShapes/useShapes.d.ts +20 -2
package/package.json
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trackunit/react-map",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
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.
|
|
11
|
-
"@trackunit/css-class-variance-utilities": "1.13.
|
|
12
|
-
"@trackunit/react-form-components": "2.1.
|
|
13
|
-
"@trackunit/react-core-hooks": "1.17.
|
|
14
|
-
"@trackunit/geo-json-utils": "1.14.
|
|
15
|
-
"@trackunit/i18n-library-translation": "2.0.
|
|
16
|
-
"@trackunit/react-modal": "2.1.
|
|
10
|
+
"@trackunit/react-components": "2.1.24",
|
|
11
|
+
"@trackunit/css-class-variance-utilities": "1.13.30",
|
|
12
|
+
"@trackunit/react-form-components": "2.1.25",
|
|
13
|
+
"@trackunit/react-core-hooks": "1.17.38",
|
|
14
|
+
"@trackunit/geo-json-utils": "1.14.32",
|
|
15
|
+
"@trackunit/i18n-library-translation": "2.0.26",
|
|
16
|
+
"@trackunit/react-modal": "2.1.26",
|
|
17
17
|
"react-minimal-pie-chart": "^8.4.0",
|
|
18
|
-
"@trackunit/react-map-adapter-shared": "0.0.
|
|
19
|
-
"@trackunit/ui-design-tokens": "1.13.
|
|
18
|
+
"@trackunit/react-map-adapter-shared": "0.0.11",
|
|
19
|
+
"@trackunit/ui-design-tokens": "1.13.30",
|
|
20
20
|
"@floating-ui/react": "^0.26.25",
|
|
21
21
|
"es-toolkit": "^1.39.10",
|
|
22
22
|
"tailwind-merge": "^2.0.0",
|
package/src/index.d.ts
CHANGED
|
@@ -22,7 +22,7 @@ export type { ControlMiddleware, MiddlewareContext } from "./controls/renderingR
|
|
|
22
22
|
export type { CategoryMetadata } from "./controls/collapseControls";
|
|
23
23
|
export type { ClusterEntity, Entity, MapInteractionState, MarkerEntity, RouteEntity, ShapeEntity, ShapeType, } from "@trackunit/react-map-adapter-shared";
|
|
24
24
|
export { geometryTypeToShapeType, INITIAL_INTERACTION_STATE } from "@trackunit/react-map-adapter-shared";
|
|
25
|
-
export type { AdaptiveMarkerResolution, AdaptiveRenderConfig, AdaptiveRenderState, AdaptiveResolutionContext, CircleSymbolDescriptor, ClientClusterConfig, ClusterConfig, ClusterInfo, ClusterRenderState, ClusterSymbolStyle, CommonRenderState, DomPortalStackingInput, DomPortalStackingResolver, DomPortalStackingResult, DomRenderConfig, DomRenderState, FitParticipation, GeoJsonFeature, GeoJsonFeatureCollection, GeoJsonGeometry, ImageOverlayHandle, LayerHandle, LayerMeta, MarkerAnchor, MarkerDomPortalStackGeometry, MarkerDomPortalStackPhase, MarkerLayerHandle, RenderConfig, RenderMedium, ResolutionContext, RouteLayerHandle, RouteStyle, ServerClusterConfig, ShapeInteractiveMode, ShapeLayerHandle, ShapeStyle, ShapeStyleOverrides, SymbolDescriptor, SymbolRenderConfig, SymbolRenderState, } from "./layers/types";
|
|
25
|
+
export type { AdaptiveMarkerResolution, AdaptiveRenderConfig, AdaptiveRenderState, AdaptiveResolutionContext, CircleSymbolDescriptor, ClientClusterConfig, ClusterConfig, ClusterInfo, ClusterRenderState, ClusterSymbolStyle, CommonRenderState, DomPortalStackingInput, DomPortalStackingResolver, DomPortalStackingResult, DomRenderConfig, DomRenderState, FitParticipation, GeoJsonFeature, GeoJsonFeatureCollection, GeoJsonGeometry, ImageOverlayHandle, LayerHandle, LayerMeta, MarkerAnchor, MarkerDomPortalStackGeometry, MarkerDomPortalStackPhase, MarkerLayerHandle, RenderConfig, RenderMedium, ResolutionContext, RouteLayerHandle, RouteStyle, ServerClusterConfig, ShapeInteractiveMode, ShapeLayerHandle, ShapeOverlapConfig, ShapeStyle, ShapeStyleOverrides, ShapesUnderCursorHit, SymbolDescriptor, SymbolRenderConfig, SymbolRenderState, } from "./layers/types";
|
|
26
26
|
export { computeMarkerDomPortalZIndex } from "./layers/types";
|
|
27
27
|
export { ANCHOR_SELECTOR, HIT_SURFACE_SELECTOR } from "@trackunit/react-map-adapter-shared";
|
|
28
28
|
export { ClusterMarker, type ClusterMarkerProps, type ClusterMarkerStickInput, type ClusterSegment, } from "./clusters/ClusterMarker";
|
|
@@ -46,6 +46,7 @@ export { type AutoPanContext, type AutoPanResult } from "./panel/utils/autoPan";
|
|
|
46
46
|
export type { DecorationAnchor, EdgeSide, ShapeDecoration } from "./layers/useShapes/shapeDecorations";
|
|
47
47
|
export { type AnnotationContext, type ShapeLabelPolicy } from "./layers/useShapes/shapeLabelPolicy";
|
|
48
48
|
export type { ResolveShapeLabel, ShapeLabelResolution, ShapeLabelResolutionContext, } from "./layers/useShapes/shapeLabelResolution";
|
|
49
|
+
export { type ContendedShape, type ResolveShapeStackOrder, type ShapeStackContext, } from "./layers/useShapes/shapeStackOrder";
|
|
49
50
|
export { useImageOverlay, type UseImageOverlayOptions, type UseImageOverlayReturn, } from "./layers/image-overlay/useImageOverlay";
|
|
50
51
|
export { buildExpandedIds, useExpandedIds, type MapFocus, type MapFocusTier, type MapFocusTierDisplay, } from "./layers/mapFocus";
|
|
51
52
|
export { useRoute, type UseRouteOptions, type UseRouteReturn } from "./layers/routes/useRoute";
|
package/src/layers/types.d.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import type { GeoJsonBbox, GeoJsonFeature, GeoJsonFeatureCollection, GeoJsonPosition } from "@trackunit/geo-json-utils";
|
|
1
|
+
import type { GeoJsonBbox, GeoJsonFeature, GeoJsonFeatureCollection, GeoJsonGeometry, GeoJsonPosition } from "@trackunit/geo-json-utils";
|
|
2
2
|
import type { AdaptiveMarkerResolution, ClusterConfig, ClusterRenderConfig, RenderConfig, RouteStyle, ShapeInteractiveMode, ShapeStyle } from "@trackunit/react-map-adapter-shared";
|
|
3
3
|
import type { CategoryKey, ControlConfig } from "../controls/types";
|
|
4
4
|
import type { ShapeDecoration } from "./useShapes/shapeDecorations";
|
|
5
|
+
import type { ShapesUnderCursorHit } from "./useShapes/shapeFillTiling";
|
|
5
6
|
import type { ShapeLabelResolutionContext } from "./useShapes/shapeLabelResolution";
|
|
7
|
+
import type { ResolveShapeStackOrder } from "./useShapes/shapeStackOrder";
|
|
8
|
+
export type { ShapesUnderCursorHit };
|
|
6
9
|
export type { AdaptiveMarkerResolution, AdaptiveRenderConfig, AdaptiveRenderState, AdaptiveResolutionContext, CircleSymbolDescriptor, ClientClusterConfig, ClusterConfig, ClusterDomRenderConfig, ClusterInfo, ClusterRenderConfig, ClusterRenderState, ClusterSymbolRenderConfig, ClusterSymbolStyle, CommonRenderState, DomPortalStackingInput, DomPortalStackingResolver, DomPortalStackingResult, DomRenderConfig, DomRenderState, GeoJsonFeature, GeoJsonFeatureCollection, GeoJsonGeometry, MarkerAnchor, MarkerDomPortalStackGeometry, MarkerDomPortalStackPhase, PixelOffset, RenderConfig, RenderMedium, ResolutionContext, RouteStyle, ServerClusterConfig, ShapeInteractiveMode, ShapeStyle, ShapeStyleOverrides, SymbolDescriptor, SymbolRenderConfig, SymbolRenderState, } from "@trackunit/react-map-adapter-shared";
|
|
7
10
|
export { computeMarkerDomPortalZIndex } from "@trackunit/react-map-adapter-shared";
|
|
8
11
|
/**
|
|
@@ -13,6 +16,22 @@ export { computeMarkerDomPortalZIndex } from "@trackunit/react-map-adapter-share
|
|
|
13
16
|
* - `"none"` — never participates in any fit operation.
|
|
14
17
|
*/
|
|
15
18
|
export type FitParticipation = "all" | "initial" | "none";
|
|
19
|
+
/**
|
|
20
|
+
* Opt-in fill-tiling configuration for a shape layer (ADR-0021).
|
|
21
|
+
*
|
|
22
|
+
* When set, overlapping polygon fills are clipped so every pixel is painted by
|
|
23
|
+
* exactly one feature (no compounded translucency). Strokes always stay full.
|
|
24
|
+
* Ownership of shared regions is decided by `resolveStackOrder`, biased toward
|
|
25
|
+
* the feature the cursor is most inside.
|
|
26
|
+
*
|
|
27
|
+
* Orthogonal to `interactive`: this controls the *visual* tiling only and never
|
|
28
|
+
* implies hit-testing behaviour.
|
|
29
|
+
*/
|
|
30
|
+
export type ShapeOverlapConfig = Readonly<{
|
|
31
|
+
mode: "tile";
|
|
32
|
+
/** Swappable ranking policy. Defaults to `defaultShapeStackOrder`. */
|
|
33
|
+
resolveStackOrder?: ResolveShapeStackOrder;
|
|
34
|
+
}>;
|
|
16
35
|
/**
|
|
17
36
|
* Metadata common to all layer types.
|
|
18
37
|
* Every layer hook returns an object extending this shape.
|
|
@@ -93,12 +112,52 @@ export interface ShapeLayerHandle extends LayerMeta {
|
|
|
93
112
|
readonly featureStyles?: ReadonlyMap<string, ShapeStyle>;
|
|
94
113
|
/** Which parts of the shape respond to interaction */
|
|
95
114
|
readonly interactive: ShapeInteractiveMode;
|
|
115
|
+
/**
|
|
116
|
+
* Fill-tiling configuration. When present (`mode: "tile"`), overlapping fills
|
|
117
|
+
* are clipped and ranked. Absent = legacy compounding fills. Orthogonal to
|
|
118
|
+
* `interactive`.
|
|
119
|
+
*/
|
|
120
|
+
readonly overlap?: ShapeOverlapConfig;
|
|
121
|
+
/**
|
|
122
|
+
* Per-feature clipped fill geometry keyed by `String(feature.id)`, filled by
|
|
123
|
+
* `<Layers>` for layers with `overlap.mode === "tile"`. Strokes keep the full
|
|
124
|
+
* geometry; only the fill consumes this clipped geometry.
|
|
125
|
+
*/
|
|
126
|
+
readonly featureFillGeometries?: ReadonlyMap<string, GeoJsonGeometry>;
|
|
127
|
+
/**
|
|
128
|
+
* Per-feature z-index override for fill rendering, filled by `<Layers>` for
|
|
129
|
+
* layers with `overlap.mode === "tile"` (ADR-0021 Path B). Higher value = visually
|
|
130
|
+
* on top. Only features in an overlap group receive an entry; the adapter uses
|
|
131
|
+
* the feature's default z-index for absent entries. The hook keeps resting values
|
|
132
|
+
* stable across hovers; only the promoted feature's value is boosted on hover.
|
|
133
|
+
*/
|
|
134
|
+
readonly featureZIndexOverrides?: ReadonlyMap<string, number>;
|
|
96
135
|
/**
|
|
97
136
|
* Returns all decorations for a feature, including auto-generated ones
|
|
98
137
|
* (e.g. multi-geometry "+" badges). Merging of custom and auto decorations
|
|
99
138
|
* is handled by `useShapes` at construction time.
|
|
100
139
|
*/
|
|
101
140
|
readonly getDecorations: (feature: GeoJsonFeature) => ReadonlyArray<ShapeDecoration>;
|
|
141
|
+
/**
|
|
142
|
+
* Geometric hit-test at the given map position. Returns all features whose
|
|
143
|
+
* fill or stroke region contains `position`, with fill/stroke flags and
|
|
144
|
+
* distance-to-boundary for tiebreaks.
|
|
145
|
+
*
|
|
146
|
+
* Injected by `<Layers>` with live viewport context (zoom + tile-size).
|
|
147
|
+
* `undefined` when the handle is used outside `<Layers>` (e.g. in unit tests).
|
|
148
|
+
*/
|
|
149
|
+
readonly queryShapesAt?: (position: GeoJsonPosition) => ReadonlyArray<ShapesUnderCursorHit>;
|
|
150
|
+
/**
|
|
151
|
+
* Called by `<Layers>` after the pointer settle debounce fires and the
|
|
152
|
+
* stroke hit-test completes for this layer. Receives all fill + stroke hits
|
|
153
|
+
* (sorted by feature id). Wired from `UseShapesOptions.onShapesUnderCursor`;
|
|
154
|
+
* always present but is a no-op when that option is not provided.
|
|
155
|
+
*
|
|
156
|
+
* Only fires for layers with `overlap: { mode: "tile" }`.
|
|
157
|
+
*/
|
|
158
|
+
readonly onShapesUnderCursor?: (hits: ReadonlyArray<ShapesUnderCursorHit>, ctx: Readonly<{
|
|
159
|
+
position: GeoJsonPosition;
|
|
160
|
+
}>) => void;
|
|
102
161
|
}
|
|
103
162
|
/**
|
|
104
163
|
* Route layer handle -- returned by `useRoute`.
|
|
@@ -17,6 +17,9 @@ type InteractiveDecorationProps = Readonly<{
|
|
|
17
17
|
* selection, mouseenter/mouseleave dispatches entity hover — both followed
|
|
18
18
|
* by optional supplementary callbacks.
|
|
19
19
|
*
|
|
20
|
+
* Double-click is detected via time-based logic (`useManualDblClick`) rather
|
|
21
|
+
* than the native `dblclick` event. See that hook for the rationale.
|
|
22
|
+
*
|
|
20
23
|
* @internal
|
|
21
24
|
*/
|
|
22
25
|
export declare const InteractiveDecoration: ({ entity, select, hover, onClick, onDblClick, onHover, children, }: InteractiveDecorationProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { type GeoJsonBbox, type GeoJsonFeature, type GeoJsonGeometry, type GeoJsonMultiPolygon, type GeoJsonPosition } from "@trackunit/geo-json-utils";
|
|
2
|
+
import type { ShapeStyle } from "@trackunit/react-map-adapter-shared";
|
|
3
|
+
import type { ResolveShapeStackOrder } from "./shapeStackOrder";
|
|
4
|
+
/**
|
|
5
|
+
* Pure fill-tiling core for ADR-0021. Given a set of features, the current
|
|
6
|
+
* viewport, a stack-order resolver, and an optional cursor, it computes the
|
|
7
|
+
* clipped fill geometry for every feature whose fill overlaps a neighbour.
|
|
8
|
+
*
|
|
9
|
+
* Non-overlapping features and the top-ranked feature of each overlap group
|
|
10
|
+
* keep their full geometry (no entry returned → adapter falls back to full).
|
|
11
|
+
* Fully covered features map to an empty MultiPolygon (no fill painted).
|
|
12
|
+
*
|
|
13
|
+
* Kept free of React/rAF so it is unit-testable; the surrounding hook owns the
|
|
14
|
+
* pointermove throttling and emits the result through the adapter.
|
|
15
|
+
*/
|
|
16
|
+
/** Empty fill = painted by nobody (fully covered loser or suppressed feature). */
|
|
17
|
+
export declare const EMPTY_FILL: GeoJsonMultiPolygon;
|
|
18
|
+
/**
|
|
19
|
+
* A feature's fill is suppressed when `fillOpacity` is exactly `0` on the
|
|
20
|
+
* resolved style. Suppressed features are excluded from tiling stack
|
|
21
|
+
* computation (no overlap ownership, no clip neighbour) and from fill
|
|
22
|
+
* hit-testing (stroke remains interactive).
|
|
23
|
+
*/
|
|
24
|
+
export declare const isFillSuppressed: (style: ShapeStyle | undefined) => boolean;
|
|
25
|
+
/** Per-feature visible fill lookup for hit-testing (clipped geometry from tiling). */
|
|
26
|
+
export type VisibleFillLookup = (featureId: string) => GeoJsonGeometry | null | undefined;
|
|
27
|
+
export type FillTilingInput = Readonly<{
|
|
28
|
+
features: ReadonlyArray<GeoJsonFeature>;
|
|
29
|
+
/** Only features whose bbox intersects this are considered. */
|
|
30
|
+
viewportBounds: GeoJsonBbox;
|
|
31
|
+
resolveStackOrder: ResolveShapeStackOrder;
|
|
32
|
+
zoom: number;
|
|
33
|
+
/**
|
|
34
|
+
* Feature id that is currently selected. Selection forces the feature to the
|
|
35
|
+
* top of its overlap group, overriding resting order. Takes highest priority.
|
|
36
|
+
*/
|
|
37
|
+
selectedFeatureId?: string | null;
|
|
38
|
+
/**
|
|
39
|
+
* Feature ids whose fill is suppressed (`fillOpacity === 0`). Suppressed
|
|
40
|
+
* features are excluded from the tiling stack (no overlap group, no clip
|
|
41
|
+
* of neighbours) and receive `EMPTY_FILL` when their bbox is in the
|
|
42
|
+
* viewport. They get no `featureToGroupKey` entry so promotion is a no-op.
|
|
43
|
+
*/
|
|
44
|
+
suppressedFeatureIds?: ReadonlySet<string>;
|
|
45
|
+
}>;
|
|
46
|
+
export type FillTilingResult = Readonly<{
|
|
47
|
+
/** Feature id → clipped fill geometry. Only clipped/covered features appear. */
|
|
48
|
+
fillGeometries: ReadonlyMap<string, GeoJsonGeometry>;
|
|
49
|
+
/**
|
|
50
|
+
* Feature id → group key. Lets the hook look up which group a stroke-hover
|
|
51
|
+
* hit belongs to so it can set the promotion overlay on the next hover.
|
|
52
|
+
*/
|
|
53
|
+
featureToGroupKey: ReadonlyMap<string, string>;
|
|
54
|
+
/**
|
|
55
|
+
* Resting z-index per feature in each overlap group (rank 0 = highest value =
|
|
56
|
+
* visually on top). Only features that belong to an overlap group are included.
|
|
57
|
+
* Used by Path B promotion: the hook keeps these stable across hover events and
|
|
58
|
+
* only overrides the promoted feature's value to raise it above its peers.
|
|
59
|
+
*/
|
|
60
|
+
featureZIndex: ReadonlyMap<string, number>;
|
|
61
|
+
}>;
|
|
62
|
+
/**
|
|
63
|
+
* Planar area of any GeoJSON geometry (outer rings minus holes). Returns 0 for
|
|
64
|
+
* non-polygonal types (Point, LineString, …). Relative magnitude only — not
|
|
65
|
+
* geodesically accurate, but consistent enough for stack-order and tiebreak use.
|
|
66
|
+
*/
|
|
67
|
+
export declare const geometryArea: (geometry: GeoJsonGeometry) => number;
|
|
68
|
+
/** Geographic degrees spanned by one screen pixel at a given zoom and tile size. */
|
|
69
|
+
export declare const degreesPerPixel: (zoom: number, tileSize: number) => number;
|
|
70
|
+
/** A single hit-test result from `shapesUnderCursor`. */
|
|
71
|
+
export type ShapesUnderCursorHit = Readonly<{
|
|
72
|
+
featureId: string;
|
|
73
|
+
/** True when the position is inside the polygon fill (holes respected). */
|
|
74
|
+
fill: boolean;
|
|
75
|
+
/** True when the position is within half the rendered stroke width of any boundary ring. */
|
|
76
|
+
stroke: boolean;
|
|
77
|
+
/**
|
|
78
|
+
* Minimum distance from `position` to the nearest boundary segment, in geographic
|
|
79
|
+
* degrees. `null` when the geometry could not be parsed. Used by callers for
|
|
80
|
+
* tiebreaks (e.g. "top stroke hit = smallest area").
|
|
81
|
+
*/
|
|
82
|
+
distanceToBoundary: number | null;
|
|
83
|
+
}>;
|
|
84
|
+
/**
|
|
85
|
+
* Adapter-agnostic hit test: given a cursor `position` and a list of GeoJSON
|
|
86
|
+
* features, returns all features whose fill or stroke region contains the
|
|
87
|
+
* position, along with raw distance-to-boundary for caller tiebreaks.
|
|
88
|
+
*
|
|
89
|
+
* - **fill**: point-in-polygon on the feature's **visible** fill geometry when
|
|
90
|
+
* `visibleFillFor` is supplied (clipped tiling output); otherwise the full
|
|
91
|
+
* feature geometry. Stroke distance always uses the full outline.
|
|
92
|
+
* - **stroke**: `distanceToBoundary < max(strokeWidth, 3) / 2` pixels, converted
|
|
93
|
+
* to geographic degrees via `degreesPerPixel(zoom, tileSize)`.
|
|
94
|
+
* - Bbox-prefiltered for performance; results are in deterministic (feature-id) order.
|
|
95
|
+
*/
|
|
96
|
+
export declare const shapesUnderCursor: (position: GeoJsonPosition, features: ReadonlyArray<GeoJsonFeature>, options: Readonly<{
|
|
97
|
+
zoom: number;
|
|
98
|
+
tileSize: number;
|
|
99
|
+
strokeWidthFor: (featureId: string) => number;
|
|
100
|
+
/**
|
|
101
|
+
* Optional per-feature visible fill geometry (from fill tiling). Absent entry
|
|
102
|
+
* = full geometry; null / empty MultiPolygon = no visible fill.
|
|
103
|
+
*/
|
|
104
|
+
visibleFillFor?: VisibleFillLookup;
|
|
105
|
+
}>) => Array<ShapesUnderCursorHit>;
|
|
106
|
+
/**
|
|
107
|
+
* Hover-promotion overlay for one overlap group: the winner renders at full
|
|
108
|
+
* geometry (caller removes its fill override). Each peer is clipped against the
|
|
109
|
+
* winner's full outline starting from its **resting** fill (when present), not
|
|
110
|
+
* its full geometry — so peer-to-peer overlap ownership from resting tiling is
|
|
111
|
+
* preserved and translucent fills do not compound under the winner.
|
|
112
|
+
*/
|
|
113
|
+
export declare const computePromotionGroupFills: (members: ReadonlyArray<GeoJsonFeature>, winnerId: string, restingPeerFills?: ReadonlyMap<string, GeoJsonGeometry>) => ReadonlyMap<string, GeoJsonGeometry>;
|
|
114
|
+
export declare const computeFillTiling: (input: FillTilingInput) => FillTilingResult;
|
|
@@ -21,7 +21,7 @@ export type ShapeLabelResolutionContext = Readonly<{
|
|
|
21
21
|
zoom: number;
|
|
22
22
|
/** Number of features from the same layer that intersect the viewport. */
|
|
23
23
|
shapesInViewport: number;
|
|
24
|
-
/** Number of other in-viewport features whose
|
|
24
|
+
/** Number of other in-viewport features whose **geometry shares area** with this feature's polygon (bbox prefilter, then polygon intersection for Polygon/MultiPolygon geometries; bbox-only for Point/Line types). Fully contained inner polygons are not counted — their fill does not obscure the map below. */
|
|
25
25
|
overlappingShapesCount: number;
|
|
26
26
|
/** GeoJSON geometry type of the feature. */
|
|
27
27
|
geometryType: GeoJsonGeometry["type"];
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { GeoJsonFeature } from "@trackunit/geo-json-utils";
|
|
2
|
+
/**
|
|
3
|
+
* Precomputed geometric facts about a single contended shape — a feature whose
|
|
4
|
+
* fill overlaps at least one other feature in the same layer. The library
|
|
5
|
+
* computes these facts; the resolver only orders them.
|
|
6
|
+
*/
|
|
7
|
+
export type ContendedShape = Readonly<{
|
|
8
|
+
/** The contended feature. Its `id` (stringified) is what the resolver returns. */
|
|
9
|
+
feature: GeoJsonFeature;
|
|
10
|
+
/** Planar area of the feature's fill, used as the stable tiebreak. */
|
|
11
|
+
area: number;
|
|
12
|
+
/** IDs of other contended shapes that fully contain this one. */
|
|
13
|
+
containedIn: ReadonlyArray<string>;
|
|
14
|
+
}>;
|
|
15
|
+
/**
|
|
16
|
+
* Context for a single resolve pass.
|
|
17
|
+
*/
|
|
18
|
+
export type ShapeStackContext = Readonly<{
|
|
19
|
+
/** Current map zoom level. */
|
|
20
|
+
zoom: number;
|
|
21
|
+
}>;
|
|
22
|
+
/**
|
|
23
|
+
* Decides fill ownership for a set of overlapping shapes. Returns the contended
|
|
24
|
+
* feature IDs ordered **top-first** — the first ID owns shared pixels, later
|
|
25
|
+
* IDs are clipped against the winners ahead of them.
|
|
26
|
+
*
|
|
27
|
+
* Pure: same inputs must yield the same ordering. Swappable per-layer via the
|
|
28
|
+
* `overlap.resolveStackOrder` option on `useShapes`.
|
|
29
|
+
*/
|
|
30
|
+
export type ResolveShapeStackOrder = (contended: ReadonlyArray<ContendedShape>, ctx: ShapeStackContext) => ReadonlyArray<string>;
|
|
31
|
+
/**
|
|
32
|
+
* Default stack-order policy (ADR-0021 "golden path"). In priority order:
|
|
33
|
+
*
|
|
34
|
+
* 1. **Containment** — a shape contained within another ranks above its
|
|
35
|
+
* container, so the specific inner site beats the broad outer one.
|
|
36
|
+
* 2. **Smaller area on top** — stable tiebreak; inner shapes are typically
|
|
37
|
+
* smaller, so this reinforces containment and keeps nested sites visible.
|
|
38
|
+
*/
|
|
39
|
+
export declare const defaultShapeStackOrder: ResolveShapeStackOrder;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Time-based double-click detection for interactive DOM overlays.
|
|
3
|
+
*
|
|
4
|
+
* Call the returned `dispatch` on every `click` event. When two calls arrive
|
|
5
|
+
* within `DOUBLE_CLICK_THRESHOLD_MS`, the second call is treated as a
|
|
6
|
+
* double-click and `onDblClick` is invoked; otherwise `onClick` is invoked.
|
|
7
|
+
*
|
|
8
|
+
* A third rapid click resets the window (triple-click is not treated as a
|
|
9
|
+
* second double-click).
|
|
10
|
+
*
|
|
11
|
+
* @internal
|
|
12
|
+
*/
|
|
13
|
+
export declare const useManualDblClick: <TArg>(onClick: ((arg: TArg) => void) | undefined, onDblClick: ((arg: TArg) => void) | undefined) => ((arg: TArg) => void);
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { GeoJsonGeometry, GeoJsonPosition } from "@trackunit/geo-json-utils";
|
|
2
|
+
import type { MapApi } from "../../core/types";
|
|
3
|
+
import type { LayerHandle, ShapesUnderCursorHit } from "../types";
|
|
4
|
+
/**
|
|
5
|
+
* Per-handle, per-feature clipped fill geometries (ADR-0021), keyed
|
|
6
|
+
* `handleId → (featureId → geometry)`. Mirrors the style-overrides channel in
|
|
7
|
+
* `useShapeDecorations`.
|
|
8
|
+
*/
|
|
9
|
+
export type FillGeometryOverrides = ReadonlyMap<string, ReadonlyMap<string, GeoJsonGeometry>>;
|
|
10
|
+
/**
|
|
11
|
+
* Per-handle, per-feature z-index overrides for fill promotion (ADR-0021 Path B),
|
|
12
|
+
* keyed `handleId → (featureId → zIndex)`. Higher value = visually on top.
|
|
13
|
+
* Only features in an overlap group receive an entry.
|
|
14
|
+
*/
|
|
15
|
+
export type ZIndexOverrides = ReadonlyMap<string, ReadonlyMap<string, number>>;
|
|
16
|
+
type UseShapeFillTilingConfig = Readonly<{
|
|
17
|
+
api: MapApi | undefined;
|
|
18
|
+
handles: ReadonlyArray<LayerHandle>;
|
|
19
|
+
onFillGeometriesChange: (overrides: FillGeometryOverrides) => void;
|
|
20
|
+
onZIndexOverridesChange: (overrides: ZIndexOverrides) => void;
|
|
21
|
+
/**
|
|
22
|
+
* Called once per pointer-settle with the overall top hit (smallest area
|
|
23
|
+
* across all handles/groups). Fires only when at least one candidate hit
|
|
24
|
+
* exists and decoration hover is not active.
|
|
25
|
+
*/
|
|
26
|
+
onSettledHover?: (handleId: string, featureId: string) => void;
|
|
27
|
+
/**
|
|
28
|
+
* Currently selected feature id. The matching feature is forced to the front
|
|
29
|
+
* of its overlap group, overriding resting order.
|
|
30
|
+
*/
|
|
31
|
+
selectedFeatureId?: string | null;
|
|
32
|
+
/**
|
|
33
|
+
* Per-handle set of feature ids whose fill is suppressed (`fillOpacity === 0`
|
|
34
|
+
* on the resolved context-aware style). Suppressed features are excluded from
|
|
35
|
+
* tiling stack computation and return no fill hit during settle / queryAt.
|
|
36
|
+
* Derived from `viewportStyleOverrides` in `<Layers>` via `isFillSuppressed`.
|
|
37
|
+
*/
|
|
38
|
+
suppressedFillIdsByHandle?: ReadonlyMap<string, ReadonlySet<string>>;
|
|
39
|
+
}>;
|
|
40
|
+
/** Return type of `useShapeFillTiling`. */
|
|
41
|
+
export type UseShapeFillTilingReturn = Readonly<{
|
|
42
|
+
/**
|
|
43
|
+
* Imperative hit-test for a given handle and position. Uses the current
|
|
44
|
+
* viewport snapshot captured by the last render. Returns an empty array
|
|
45
|
+
* when the handle id is unknown or has no polygonal features.
|
|
46
|
+
*/
|
|
47
|
+
queryAt: (handleId: string, position: GeoJsonPosition) => ReadonlyArray<ShapesUnderCursorHit>;
|
|
48
|
+
/**
|
|
49
|
+
* Promote a feature to the front of its overlap group (sticky until the next
|
|
50
|
+
* hover or selection). Used when the pointer is over a shape decoration
|
|
51
|
+
* (e.g. edge label) that is not on the fill/stroke geometry — same
|
|
52
|
+
* promotion channel as stroke/fill settle promotion.
|
|
53
|
+
*/
|
|
54
|
+
promoteFeature: (handleId: string, featureId: string) => void;
|
|
55
|
+
/**
|
|
56
|
+
* Symmetric counterpart to `promoteFeature`. Clears the promotion for
|
|
57
|
+
* `featureId` in its overlap group only when it is the current group winner;
|
|
58
|
+
* no-op otherwise.
|
|
59
|
+
*/
|
|
60
|
+
clearPromotion: (handleId: string, featureId: string) => void;
|
|
61
|
+
/**
|
|
62
|
+
* Suppress pointer-settle fill promotion while a shape decoration (edge label,
|
|
63
|
+
* etc.) is hovered — the cursor may sit over another polygon's fill visually.
|
|
64
|
+
*/
|
|
65
|
+
setDecorationHoverActive: (active: boolean) => void;
|
|
66
|
+
}>;
|
|
67
|
+
/**
|
|
68
|
+
* `useShapeFillTiling` — clips overlapping polygon fills for shape handles that
|
|
69
|
+
* opted in via `overlap: { mode: "tile" }`, and promotes shapes on stroke-hover
|
|
70
|
+
* settle (sticky, ~120ms debounce), decoration hover (edge labels, etc.), or when
|
|
71
|
+
* a shape is selected (selection always forces front). Emits the result through
|
|
72
|
+
* `onFillGeometriesChange` and `onZIndexOverridesChange`; `<Layers>` merges them
|
|
73
|
+
* into each shape handle's `featureFillGeometries` and `featureZIndexOverrides`.
|
|
74
|
+
*
|
|
75
|
+
* **Path B (ADR-0021):** `computeFillTiling` is promotion-unaware — it only runs on
|
|
76
|
+
* viewport / feature-set / selection changes and produces stable resting fills and
|
|
77
|
+
* resting z-indices. Hover promotion removes the resting clip for each group's
|
|
78
|
+
* current sticky winner (so covered area can paint on top) and raises its z-index.
|
|
79
|
+
* Non-winners in an active group are re-clipped against the winner's full outline
|
|
80
|
+
* only (exclusive rings stay painted). Promotion clears when the pointer leaves
|
|
81
|
+
* all sites on the handle.
|
|
82
|
+
*
|
|
83
|
+
* Resting tiling (viewport + feature set) is deferred while the map is moving
|
|
84
|
+
* and coalesced on `idle` so pan/zoom does not run `computeFillTiling` every frame.
|
|
85
|
+
*
|
|
86
|
+
* @internal
|
|
87
|
+
*/
|
|
88
|
+
export declare const useShapeFillTiling: ({ api, handles, onFillGeometriesChange, onZIndexOverridesChange, onSettledHover, selectedFeatureId, suppressedFillIdsByHandle, }: UseShapeFillTilingConfig) => UseShapeFillTilingReturn;
|
|
89
|
+
export {};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { type GeoJsonFeature, type GeoJsonFeatureCollection } from "@trackunit/geo-json-utils";
|
|
1
|
+
import { type GeoJsonFeature, type GeoJsonFeatureCollection, type GeoJsonPosition } from "@trackunit/geo-json-utils";
|
|
2
2
|
import type { CategoryKey, ControlConfig } from "../../controls/types";
|
|
3
|
-
import type { FitParticipation, ShapeInteractiveMode, ShapeLayerHandle, ShapeStyle } from "../types";
|
|
3
|
+
import type { FitParticipation, ShapeInteractiveMode, ShapeLayerHandle, ShapeOverlapConfig, ShapeStyle, ShapesUnderCursorHit } from "../types";
|
|
4
4
|
import type { ShapeDecoration } from "./shapeDecorations";
|
|
5
5
|
import type { ShapeLabelResolutionContext } from "./shapeLabelResolution";
|
|
6
6
|
export type UseShapesOptions = Readonly<{
|
|
@@ -14,6 +14,13 @@ export type UseShapesOptions = Readonly<{
|
|
|
14
14
|
resolveStyle: (feature: GeoJsonFeature, ctx?: ShapeLabelResolutionContext) => ShapeStyle;
|
|
15
15
|
/** Which parts of the shape respond to interaction. Default: `"stroke"` */
|
|
16
16
|
interactive?: ShapeInteractiveMode;
|
|
17
|
+
/**
|
|
18
|
+
* Opt-in fill tiling for overlapping polygons (ADR-0021). When set to
|
|
19
|
+
* `{ mode: "tile" }`, overlapping fills are clipped so every pixel is painted
|
|
20
|
+
* by exactly one feature and ownership follows the cursor. Strokes stay full.
|
|
21
|
+
* Orthogonal to `interactive` — neither implies the other.
|
|
22
|
+
*/
|
|
23
|
+
overlap?: ShapeOverlapConfig;
|
|
17
24
|
/**
|
|
18
25
|
* Controls to contribute to the map UI, keyed by semantic area.
|
|
19
26
|
* Omit entirely (or omit a key) to contribute nothing to that area.
|
|
@@ -31,6 +38,17 @@ export type UseShapesOptions = Readonly<{
|
|
|
31
38
|
loading?: boolean;
|
|
32
39
|
/** Controls whether this layer's bounds are included in fit-to-content operations. Default: `"all"` */
|
|
33
40
|
fitParticipation?: FitParticipation;
|
|
41
|
+
/**
|
|
42
|
+
* Called by `<Layers>` after the pointer settle debounce fires and the
|
|
43
|
+
* stroke hit-test completes for this layer. Receives the full hit list
|
|
44
|
+
* (fill + stroke flags) sorted by feature id. Useful for custom
|
|
45
|
+
* hover-promotion, tooltips, or analytics.
|
|
46
|
+
*
|
|
47
|
+
* Only fires for layers with `overlap: { mode: "tile" }`.
|
|
48
|
+
*/
|
|
49
|
+
onShapesUnderCursor?: (hits: ReadonlyArray<ShapesUnderCursorHit>, ctx: Readonly<{
|
|
50
|
+
position: GeoJsonPosition;
|
|
51
|
+
}>) => void;
|
|
34
52
|
}>;
|
|
35
53
|
export type UseShapesReturn = ShapeLayerHandle;
|
|
36
54
|
/**
|