@trackunit/react-map 0.0.4-alpha-9d327375fc1.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/README.md +296 -0
- package/index.cjs.js +9851 -0
- package/index.d.ts +1 -0
- package/index.esm.js +9752 -0
- package/package.json +34 -0
- package/src/annotations/getDescriptorContentKey.d.ts +14 -0
- package/src/annotations/mapAnnotations.d.ts +76 -0
- package/src/annotations/useMapAnnotation.d.ts +16 -0
- package/src/annotations/useMapAnnotations.d.ts +17 -0
- package/src/appearance/AppearancePreview.d.ts +27 -0
- package/src/appearance/AppearancePreviewGrid.d.ts +29 -0
- package/src/appearance/appearanceTypes.d.ts +84 -0
- package/src/appearance/useMapAppearanceControls.d.ts +29 -0
- package/src/clusters/ClusterMarker.d.ts +41 -0
- package/src/clusters/ClusterStick.d.ts +97 -0
- package/src/clusters/formatClusterCount.d.ts +11 -0
- package/src/clusters/stories/ClusterMarkerCompactFormatsDemo.d.ts +7 -0
- package/src/clusters/stories/ClusterMarkerCountOnlyDemo.d.ts +7 -0
- package/src/clusters/stories/ClusterMarkerDefaultDemo.d.ts +12 -0
- package/src/clusters/stories/ClusterMarkerPlaygroundDemo.d.ts +10 -0
- package/src/clusters/stories/ClusterMarkerStateDemo.d.ts +9 -0
- package/src/clusters/stories/ClusterMarkerWithSegmentsDemo.d.ts +7 -0
- package/src/clusters/stories/ClusterMarkerWithSticksDemo.d.ts +10 -0
- package/src/clusters/useClusterCountFormat.d.ts +19 -0
- package/src/controls/AnnotationRenderer.d.ts +17 -0
- package/src/controls/AnnotationStack.d.ts +21 -0
- package/src/controls/ControlRenderer.d.ts +20 -0
- package/src/controls/Controls.d.ts +15 -0
- package/src/controls/annotationButtonClasses.d.ts +13 -0
- package/src/controls/collapseControls.d.ts +37 -0
- package/src/controls/defineControlStack.d.ts +10 -0
- package/src/controls/deriveResponsiveMode.d.ts +19 -0
- package/src/controls/longitudeToGlobeIcon.d.ts +3 -0
- package/src/controls/renderers/boolean/BooleanButton.d.ts +16 -0
- package/src/controls/renderers/boolean/MenuBooleanItem.d.ts +20 -0
- package/src/controls/renderers/boolean/MenuCheckbox.d.ts +14 -0
- package/src/controls/renderers/boolean/MenuToggle.d.ts +14 -0
- package/src/controls/renderers/button/Button.d.ts +14 -0
- package/src/controls/renderers/button/MenuButton.d.ts +16 -0
- package/src/controls/renderers/custom/Custom.d.ts +22 -0
- package/src/controls/renderers/menu/Menu.d.ts +23 -0
- package/src/controls/renderers/menu/Modal.d.ts +21 -0
- package/src/controls/renderers/menu/SectionHeader.d.ts +14 -0
- package/src/controls/renderers/menu/Separator.d.ts +11 -0
- package/src/controls/renderers/radio-group/MenuRadioGroup.d.ts +15 -0
- package/src/controls/renderers/radio-group/RadioGroup.d.ts +19 -0
- package/src/controls/renderers/radio-group/RadioGroupField.d.ts +17 -0
- package/src/controls/renderers/search/MenuSearch.d.ts +15 -0
- package/src/controls/renderers/search/Search.d.ts +14 -0
- package/src/controls/renderers/select/MenuSelect.d.ts +15 -0
- package/src/controls/renderers/select/Select.d.ts +14 -0
- package/src/controls/renderers/stepper/MenuStepper.d.ts +17 -0
- package/src/controls/renderers/stepper/Stepper.d.ts +18 -0
- package/src/controls/renderers/toggle-group/MenuToggleGroup.d.ts +17 -0
- package/src/controls/renderers/toggle-group/ToggleGroup.d.ts +15 -0
- package/src/controls/renderers/toggle-group/ToggleGroupField.d.ts +19 -0
- package/src/controls/renderingRules.d.ts +26 -0
- package/src/controls/types.d.ts +395 -0
- package/src/controls/useAnimatedPresence.d.ts +15 -0
- package/src/controls/useAnnotationPresence.d.ts +16 -0
- package/src/controls/useControlStack.d.ts +21 -0
- package/src/controls/useControlsConfig.d.ts +27 -0
- package/src/controls/usePopoverPlacement.d.ts +33 -0
- package/src/core/LoadingIndicatorPill.d.ts +9 -0
- package/src/core/MapLoadingState.d.ts +17 -0
- package/src/core/SafeAreaLayoutContext.d.ts +5 -0
- package/src/core/__testHelpers.d.ts +23 -0
- package/src/core/createMapComponent.d.ts +32 -0
- package/src/core/loadingIndicator.d.ts +21 -0
- package/src/core/padWithInsets.d.ts +5 -0
- package/src/core/types.d.ts +208 -0
- package/src/core/useCameraIdle.d.ts +2 -0
- package/src/core/useCameraState.d.ts +3 -0
- package/src/core/useMap.d.ts +57 -0
- package/src/core/useMapAdapterState.d.ts +14 -0
- package/src/core/useMapKeyboardNavigation.d.ts +38 -0
- package/src/core/usePreviewMap.d.ts +43 -0
- package/src/index.d.ts +69 -0
- package/src/interactions/interactionReducer.d.ts +31 -0
- package/src/layers/Layers.d.ts +32 -0
- package/src/layers/__testHelpers.d.ts +24 -0
- package/src/layers/buildExpandedIds.d.ts +17 -0
- package/src/layers/image-overlay/useImageOverlay.d.ts +39 -0
- package/src/layers/internal/MapLayerContext.d.ts +24 -0
- package/src/layers/internal/adaptiveMarkerResolution.d.ts +33 -0
- package/src/layers/internal/computeBounds.d.ts +34 -0
- package/src/layers/internal/countFeaturesInViewport.d.ts +14 -0
- package/src/layers/internal/createLazyGetter.d.ts +16 -0
- package/src/layers/internal/layerHelpers.d.ts +18 -0
- package/src/layers/internal/markerPortalZIndex.d.ts +17 -0
- package/src/layers/internal/useDblClickFit.d.ts +13 -0
- package/src/layers/internal/useEntityInteraction.d.ts +21 -0
- package/src/layers/internal/useFitFeatureBounds.d.ts +19 -0
- package/src/layers/internal/useLayerHandleSync.d.ts +18 -0
- package/src/layers/internal/useLayerReady.d.ts +25 -0
- package/src/layers/internal/useMarkerMountBridge.d.ts +41 -0
- package/src/layers/internal/useViewportContext.d.ts +34 -0
- package/src/layers/mapFocus.d.ts +29 -0
- package/src/layers/routes/useRoute.d.ts +39 -0
- package/src/layers/shared/measureLabelWidth.d.ts +11 -0
- package/src/layers/types.d.ts +131 -0
- package/src/layers/useExpandedIds.d.ts +20 -0
- package/src/layers/useFitToContent.d.ts +57 -0
- package/src/layers/useLayers.d.ts +58 -0
- package/src/layers/useMarkers/adaptiveHelpers.d.ts +156 -0
- package/src/layers/useMarkers/resolveServerClusters.d.ts +26 -0
- package/src/layers/useMarkers/useAdaptiveMarkerHelpers.d.ts +38 -0
- package/src/layers/useMarkers/useMarkers.d.ts +127 -0
- package/src/layers/useShapes/InteractiveDecoration.d.ts +23 -0
- package/src/layers/useShapes/MultiGeometryPlusBadge.d.ts +17 -0
- package/src/layers/useShapes/MultiPartBadge.d.ts +11 -0
- package/src/layers/useShapes/ShapeAnnotationLabel.d.ts +30 -0
- package/src/layers/useShapes/ShapeIcon.d.ts +21 -0
- package/src/layers/useShapes/ShapeLabelPill.d.ts +25 -0
- package/src/layers/useShapes/buildShapeLabelResolutionContext.d.ts +23 -0
- package/src/layers/useShapes/decorationFactories.d.ts +47 -0
- package/src/layers/useShapes/edge/ShapeEdgeLabel.d.ts +13 -0
- package/src/layers/useShapes/edge/computeEdgeAutoPlacement.d.ts +69 -0
- package/src/layers/useShapes/edge/computeEdgeProperties.d.ts +21 -0
- package/src/layers/useShapes/edge/edgeInsetsForFeature.d.ts +27 -0
- package/src/layers/useShapes/edge/edgeReservations.d.ts +36 -0
- package/src/layers/useShapes/edge/findBestEdgePosition.d.ts +247 -0
- package/src/layers/useShapes/extractMultiParts.d.ts +25 -0
- package/src/layers/useShapes/findBestVisibleVertex.d.ts +18 -0
- package/src/layers/useShapes/multiPartDecorations.d.ts +19 -0
- package/src/layers/useShapes/shapeDecorations.d.ts +154 -0
- package/src/layers/useShapes/shapeFeatureBuilders.d.ts +25 -0
- package/src/layers/useShapes/shapeLabelPolicy.d.ts +42 -0
- package/src/layers/useShapes/shapeLabelResolution.d.ts +45 -0
- package/src/layers/useShapes/shapeSyntheticMarkerRender.d.ts +16 -0
- package/src/layers/useShapes/useShapeDecorations.d.ts +56 -0
- package/src/layers/useShapes/useShapeLabelHelpers.d.ts +29 -0
- package/src/layers/useShapes/useShapes.d.ts +50 -0
- package/src/markers/MapMarker.d.ts +115 -0
- package/src/markers/MapMarkerIcon.d.ts +21 -0
- package/src/markers/animation/applyStickFrame.d.ts +15 -0
- package/src/markers/animation/spring.d.ts +45 -0
- package/src/markers/animation/stickFrame.d.ts +82 -0
- package/src/markers/animation/usePillBackgroundPaint.d.ts +15 -0
- package/src/markers/animation/useStickAnimation.d.ts +21 -0
- package/src/markers/animation/useStickTween.d.ts +28 -0
- package/src/markers/geometry/stickPositioning.d.ts +69 -0
- package/src/markers/geometry/useDirectionIndicator.d.ts +8 -0
- package/src/markers/internal/MapMarkerStickSurface.d.ts +47 -0
- package/src/markers/model/mapMarkerDomPortalStacking.d.ts +22 -0
- package/src/markers/model/markerDomTypes.d.ts +68 -0
- package/src/markers/model/markerResolvers.d.ts +34 -0
- package/src/markers/model/markerSizeMap.d.ts +82 -0
- package/src/markers/model/markerTuningParams.d.ts +60 -0
- package/src/markers/model/useMarkerStateResolvers.d.ts +25 -0
- package/src/markers/shared/MarkerAnimatedSurface.d.ts +20 -0
- package/src/markers/shared/MarkerDiscContent.d.ts +11 -0
- package/src/markers/shared/mapMarkerVariants.d.ts +35 -0
- package/src/markers/shared/markerColors.d.ts +55 -0
- package/src/markers/shared/useMarkerColors.d.ts +23 -0
- package/src/markers/stories/demoes/AnimationCircleVsPillDemo.d.ts +7 -0
- package/src/markers/stories/demoes/CircleIconGuidanceDemo.d.ts +2 -0
- package/src/markers/stories/demoes/DefaultDemo.d.ts +14 -0
- package/src/markers/stories/demoes/DirectionBehaviorDemo.d.ts +2 -0
- package/src/markers/stories/demoes/PillLabelLengthsDemo.d.ts +2 -0
- package/src/markers/stories/demoes/StatesDemo.d.ts +7 -0
- package/src/markers/stories/demoes/StickModeDemo.d.ts +2 -0
- package/src/markers/stories/demoes/StickModePlaygroundDemo.d.ts +10 -0
- package/src/markers/stories/helpers/AbsoluteGridPicker.d.ts +17 -0
- package/src/markers/stories/helpers/InlineLabel.d.ts +4 -0
- package/src/markers/stories/helpers/InlineSlider.d.ts +11 -0
- package/src/markers/stories/helpers/LabeledMarker.d.ts +7 -0
- package/src/markers/stories/helpers/MarkerRow.d.ts +4 -0
- package/src/markers/stories/helpers/SectionHeader.d.ts +4 -0
- package/src/markers/stories/helpers/StatesMarkerCell.d.ts +15 -0
- package/src/markers/stories/helpers/ThemedPreview.d.ts +10 -0
- package/src/markers/stories/helpers/mapMarkerStoryConstants.d.ts +4 -0
- package/src/panel/Panel.d.ts +60 -0
- package/src/panel/PanelOrchestrator.d.ts +24 -0
- package/src/panel/preload/preloadInitiators.d.ts +44 -0
- package/src/panel/preload/useHoverPreloadInitiator.d.ts +8 -0
- package/src/panel/preload/usePanelPreload.d.ts +46 -0
- package/src/panel/preload/useProximityPreloadInitiator.d.ts +13 -0
- package/src/panel/store/panels.d.ts +76 -0
- package/src/panel/storyHelpers.d.ts +9 -0
- package/src/panel/usePanel.d.ts +81 -0
- package/src/panel/utils/autoPan.d.ts +78 -0
- package/src/panel/utils/panelShellConstants.d.ts +8 -0
- package/src/panel/utils/useAutoPanResolver.d.ts +23 -0
- package/src/panel/utils/useWheelForwarding.d.ts +18 -0
- package/src/stories/InteractionDisplay.d.ts +9 -0
- package/src/stories/PositionDisplay.d.ts +9 -0
- package/src/stories/_storyCoordUtils.d.ts +8 -0
- package/src/testing/mockMapApi.d.ts +29 -0
- package/src/translation.d.ts +30 -0
- package/translation.cjs.js +30 -0
- package/translation.cjs10.js +30 -0
- package/translation.cjs11.js +30 -0
- package/translation.cjs12.js +30 -0
- package/translation.cjs13.js +30 -0
- package/translation.cjs14.js +30 -0
- package/translation.cjs15.js +30 -0
- package/translation.cjs16.js +30 -0
- package/translation.cjs17.js +30 -0
- package/translation.cjs2.js +30 -0
- package/translation.cjs3.js +30 -0
- package/translation.cjs4.js +30 -0
- package/translation.cjs5.js +30 -0
- package/translation.cjs6.js +30 -0
- package/translation.cjs7.js +30 -0
- package/translation.cjs8.js +30 -0
- package/translation.cjs9.js +30 -0
- package/translation.esm.js +28 -0
- package/translation.esm10.js +28 -0
- package/translation.esm11.js +28 -0
- package/translation.esm12.js +28 -0
- package/translation.esm13.js +28 -0
- package/translation.esm14.js +28 -0
- package/translation.esm15.js +28 -0
- package/translation.esm16.js +28 -0
- package/translation.esm17.js +28 -0
- package/translation.esm2.js +28 -0
- package/translation.esm3.js +28 -0
- package/translation.esm4.js +28 -0
- package/translation.esm5.js +28 -0
- package/translation.esm6.js +28 -0
- package/translation.esm7.js +28 -0
- package/translation.esm8.js +28 -0
- package/translation.esm9.js +28 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { type AdaptiveResolutionContext, type CircleSymbolDescriptor, type RenderMedium } from "@trackunit/react-map-adapter-shared";
|
|
2
|
+
import type { MapTheme } from "../../core/types";
|
|
3
|
+
import type { MarkerDomSize } from "../../markers/model/markerDomTypes";
|
|
4
|
+
/**
|
|
5
|
+
* Tunables for {@link pickRenderMedium}. All optional — sensible defaults are
|
|
6
|
+
* documented inline.
|
|
7
|
+
*/
|
|
8
|
+
export type PickRenderMediumOptions = Readonly<{
|
|
9
|
+
/**
|
|
10
|
+
* If `markersInViewport` exceeds this, the helper returns `"symbol"` even
|
|
11
|
+
* when selected/hovered promotion does not apply. Keeps DOM marker counts
|
|
12
|
+
* bounded so layout cost stays low.
|
|
13
|
+
*
|
|
14
|
+
* @default 200
|
|
15
|
+
*/
|
|
16
|
+
maxMarkersForDom?: number;
|
|
17
|
+
/**
|
|
18
|
+
* When `true`, the helper short-circuits to `"dom"` whenever a marker is
|
|
19
|
+
* hovered or selected, so the rich DOM form is always available for
|
|
20
|
+
* interaction even outside the density envelope.
|
|
21
|
+
*
|
|
22
|
+
* @default true
|
|
23
|
+
*/
|
|
24
|
+
domMediumOnInteraction?: boolean;
|
|
25
|
+
}>;
|
|
26
|
+
/**
|
|
27
|
+
* Default heuristic for `AdaptiveRenderConfig.resolveMode` (ADR-0009).
|
|
28
|
+
*
|
|
29
|
+
* Returns `"dom"` when the marker is interacted with (hover/select) or when
|
|
30
|
+
* the viewport is not over-dense; otherwise `"symbol"`. Pure — no React, no
|
|
31
|
+
* allocation per call.
|
|
32
|
+
*
|
|
33
|
+
* Consumers use this directly:
|
|
34
|
+
*
|
|
35
|
+
* ```ts
|
|
36
|
+
* resolveMode: (_item, ctx) => pickRenderMedium(ctx)
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* Or tune thresholds:
|
|
40
|
+
*
|
|
41
|
+
* ```ts
|
|
42
|
+
* resolveMode: (_item, ctx) =>
|
|
43
|
+
* pickRenderMedium(ctx, { maxMarkersForDom: 80 })
|
|
44
|
+
* ```
|
|
45
|
+
*
|
|
46
|
+
* The library does not impose this heuristic — `resolveMode` always wins. The
|
|
47
|
+
* helper is a documented starting point for the common case (Q6 of the
|
|
48
|
+
* design grilling).
|
|
49
|
+
*/
|
|
50
|
+
export declare const pickRenderMedium: (ctx: AdaptiveResolutionContext, options?: PickRenderMediumOptions) => RenderMedium;
|
|
51
|
+
/**
|
|
52
|
+
* Single zoom→size step. The helper picks the breakpoint with the highest
|
|
53
|
+
* `minZoom` that is still `<= ctx.zoom`.
|
|
54
|
+
*/
|
|
55
|
+
export type MarkerSizeBreakpoint = Readonly<{
|
|
56
|
+
/** Zoom at or above which this size applies. */
|
|
57
|
+
minZoom: number;
|
|
58
|
+
/** Size to use once `ctx.zoom >= minZoom`. */
|
|
59
|
+
size: MarkerDomSize;
|
|
60
|
+
}>;
|
|
61
|
+
/**
|
|
62
|
+
* Tunables for {@link pickMarkerSize}.
|
|
63
|
+
*/
|
|
64
|
+
export type PickMarkerSizeOptions = Readonly<{
|
|
65
|
+
/**
|
|
66
|
+
* Zoom→size ladder, sorted ascending by `minZoom`. The last breakpoint with
|
|
67
|
+
* `minZoom <= ctx.zoom` wins. If `ctx.zoom` is below every breakpoint, the
|
|
68
|
+
* first breakpoint's `size` is returned (the smallest tier acts as the
|
|
69
|
+
* floor).
|
|
70
|
+
*
|
|
71
|
+
* @default DEFAULT_MARKER_SIZE_BREAKPOINTS
|
|
72
|
+
*/
|
|
73
|
+
breakpoints?: ReadonlyArray<MarkerSizeBreakpoint>;
|
|
74
|
+
}>;
|
|
75
|
+
/**
|
|
76
|
+
* Five-tier zoom ladder matching the `MapMarker` `size` prop. The chosen
|
|
77
|
+
* thresholds align with how the existing demos read at typical fleet zooms
|
|
78
|
+
* (city → asset → street).
|
|
79
|
+
*/
|
|
80
|
+
export declare const DEFAULT_MARKER_SIZE_BREAKPOINTS: ReadonlyArray<MarkerSizeBreakpoint>;
|
|
81
|
+
/**
|
|
82
|
+
* Default zoom→`MarkerDomSize` mapping for the DOM branch of an adaptive
|
|
83
|
+
* `render`.
|
|
84
|
+
*
|
|
85
|
+
* Pure. Iterates the breakpoint ladder once and picks the largest `minZoom`
|
|
86
|
+
* still satisfied by `ctx.zoom`. The breakpoint list must be sorted ascending
|
|
87
|
+
* by `minZoom`; the helper does not sort defensively because it runs per
|
|
88
|
+
* marker per frame.
|
|
89
|
+
*
|
|
90
|
+
* ```ts
|
|
91
|
+
* <MapMarker size={pickMarkerSize(ctx)} ... />
|
|
92
|
+
* ```
|
|
93
|
+
*
|
|
94
|
+
* Consumers can tune the ladder for denser or sparser maps:
|
|
95
|
+
*
|
|
96
|
+
* ```ts
|
|
97
|
+
* pickMarkerSize(ctx, {
|
|
98
|
+
* breakpoints: [
|
|
99
|
+
* { minZoom: 0, size: "xs" },
|
|
100
|
+
* { minZoom: 10, size: "md" },
|
|
101
|
+
* { minZoom: 14, size: "xl" },
|
|
102
|
+
* ],
|
|
103
|
+
* })
|
|
104
|
+
* ```
|
|
105
|
+
*/
|
|
106
|
+
export declare const pickMarkerSize: (ctx: AdaptiveResolutionContext, options?: PickMarkerSizeOptions) => MarkerDomSize;
|
|
107
|
+
/**
|
|
108
|
+
* Tunables for {@link pickSymbolDiameter}. The same ladder shape as
|
|
109
|
+
* {@link pickMarkerSize} so `mode: "adaptive"` consumers can share one
|
|
110
|
+
* options object across both branches.
|
|
111
|
+
*/
|
|
112
|
+
export type PickSymbolDiameterOptions = PickMarkerSizeOptions;
|
|
113
|
+
/**
|
|
114
|
+
* Symbol counterpart of {@link pickMarkerSize}: returns a `diameterPx` that
|
|
115
|
+
* matches the `MapMarker` `circle` dimension at the same zoom-derived tier
|
|
116
|
+
* (`MARKER_SIZE_MAP`). Use it inside the symbol arm of an adaptive `render`
|
|
117
|
+
* so the WebGL/canvas dot is the same physical size as the DOM circle the
|
|
118
|
+
* marker is about to morph into.
|
|
119
|
+
*
|
|
120
|
+
* ```ts
|
|
121
|
+
* if (state.medium === "symbol") {
|
|
122
|
+
* return { color, diameterPx: pickSymbolDiameter(ctx) };
|
|
123
|
+
* }
|
|
124
|
+
* ```
|
|
125
|
+
*
|
|
126
|
+
* Pure; defers entirely to {@link pickMarkerSize} for the tier decision.
|
|
127
|
+
*/
|
|
128
|
+
export declare const pickSymbolDiameter: (ctx: AdaptiveResolutionContext, options?: PickSymbolDiameterOptions) => number;
|
|
129
|
+
/**
|
|
130
|
+
* Width of the disc border drawn on DOM markers (Tailwind `border` = 1 px).
|
|
131
|
+
* Shared by {@link pickSymbolDescriptor} so DOM and symbol layers stay in sync.
|
|
132
|
+
*/
|
|
133
|
+
export declare const MARKER_DISC_BORDER_WIDTH_PX = 1;
|
|
134
|
+
/**
|
|
135
|
+
* Full symbol descriptor for adaptive symbol markers.
|
|
136
|
+
*
|
|
137
|
+
* Symbol markers intentionally stay at the `xs` footprint to keep dense-map
|
|
138
|
+
* rendering stable while zooming. We still compute `diameterPx` via
|
|
139
|
+
* {@link pickSymbolDiameter} so the actual pixel value is sourced from the same
|
|
140
|
+
* `MARKER_SIZE_MAP` as DOM markers.
|
|
141
|
+
*
|
|
142
|
+
* Also derives `borderColor`
|
|
143
|
+
* from the same `color-mix()` formula used by `cvaMarkerIndicator` in CSS:
|
|
144
|
+
* - **light**: darken the fill by 30% (mix with black)
|
|
145
|
+
* - **dark**: lighten the fill by 50% (mix with white)
|
|
146
|
+
*
|
|
147
|
+
* Use this instead of the manual `{ color, diameterPx: pickSymbolDiameter(ctx) }`
|
|
148
|
+
* when you want the symbol circle and the DOM disc to look identical:
|
|
149
|
+
*
|
|
150
|
+
* ```ts
|
|
151
|
+
* if (state.medium === "symbol") {
|
|
152
|
+
* return pickSymbolDescriptor(color, theme, sizingCtx);
|
|
153
|
+
* }
|
|
154
|
+
* ```
|
|
155
|
+
*/
|
|
156
|
+
export declare const pickSymbolDescriptor: (color: string, theme: MapTheme, ctx: AdaptiveResolutionContext) => CircleSymbolDescriptor;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ClusterConfig, ResolutionContext } from "../types";
|
|
2
|
+
type ServerClusterConfig<TCluster> = Extract<ClusterConfig<TCluster>, {
|
|
3
|
+
mode: "server";
|
|
4
|
+
}>;
|
|
5
|
+
/** Subset of {@link ResolutionContext} that callers supply from map state. */
|
|
6
|
+
export type ViewportResolutionContext = Readonly<Pick<ResolutionContext, "zoom" | "markersInViewport" | "viewportBounds">>;
|
|
7
|
+
/**
|
|
8
|
+
* Applies `resolveGroups` entity-shape decisions to a server cluster list.
|
|
9
|
+
*
|
|
10
|
+
* Per ADR-0016: expand-only resolution. Each cluster gets a `memberItems`
|
|
11
|
+
* cross-reference from `data` via `getId`/`getMarkerIds`. `"expand"` with
|
|
12
|
+
* non-null `memberItems` drops the cluster from the kept set; `"expand"` with
|
|
13
|
+
* null `memberItems` (any member missing from the data index) silently falls
|
|
14
|
+
* back to `"keep"`.
|
|
15
|
+
*
|
|
16
|
+
* `viewportCtx` carries live map state (zoom, density, bounds). When omitted
|
|
17
|
+
* all three default to zero/null — pass it from the consumer's map state so
|
|
18
|
+
* `resolveGroups` callbacks receive real values.
|
|
19
|
+
*
|
|
20
|
+
* Pure function — no hooks, safe to call inside `useMemo`.
|
|
21
|
+
*/
|
|
22
|
+
export declare const resolveServerClusters: <TItem, TCluster>(cluster: ServerClusterConfig<TCluster>, data: ReadonlyArray<TItem>, getId: (item: TItem) => string, resolveGroups: (cluster: TCluster, memberItems: ReadonlyArray<TItem> | null, ctx: ResolutionContext) => "keep" | "expand", viewportCtx?: ViewportResolutionContext) => Readonly<{
|
|
23
|
+
data: ReadonlyArray<TCluster>;
|
|
24
|
+
memberItems: ReadonlyMap<string, ReadonlyArray<TItem> | null>;
|
|
25
|
+
}>;
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { resolveMapMarkerDomPortalStacking } from "../../markers/model/mapMarkerDomPortalStacking";
|
|
2
|
+
import { resolveAdaptiveMarkerState } from "../../markers/model/markerResolvers";
|
|
3
|
+
import { downgradeMarkerDomSize, upgradeMarkerDomSize } from "../../markers/model/markerSizeMap";
|
|
4
|
+
import { DEFAULT_MARKER_SIZE_BREAKPOINTS, MARKER_DISC_BORDER_WIDTH_PX, pickMarkerSize, pickRenderMedium, pickSymbolDescriptor, pickSymbolDiameter } from "./adaptiveHelpers";
|
|
5
|
+
export type AdaptiveMarkerHelpers = Readonly<{
|
|
6
|
+
pickMarkerSize: typeof pickMarkerSize;
|
|
7
|
+
pickRenderMedium: typeof pickRenderMedium;
|
|
8
|
+
pickSymbolDescriptor: typeof pickSymbolDescriptor;
|
|
9
|
+
pickSymbolDiameter: typeof pickSymbolDiameter;
|
|
10
|
+
resolveAdaptiveMarkerState: typeof resolveAdaptiveMarkerState;
|
|
11
|
+
downgradeMarkerDomSize: typeof downgradeMarkerDomSize;
|
|
12
|
+
upgradeMarkerDomSize: typeof upgradeMarkerDomSize;
|
|
13
|
+
resolveMapMarkerDomPortalStacking: typeof resolveMapMarkerDomPortalStacking;
|
|
14
|
+
DEFAULT_MARKER_SIZE_BREAKPOINTS: typeof DEFAULT_MARKER_SIZE_BREAKPOINTS;
|
|
15
|
+
MARKER_DISC_BORDER_WIDTH_PX: typeof MARKER_DISC_BORDER_WIDTH_PX;
|
|
16
|
+
}>;
|
|
17
|
+
/**
|
|
18
|
+
* Build-your-own hook: stable references to the full adaptive marker kit.
|
|
19
|
+
*
|
|
20
|
+
* Returns the complete set of primitives needed to hand-roll an adaptive marker
|
|
21
|
+
* layer (DOM ↔ symbol mode switching, zoom-based sizing, portal stacking) while
|
|
22
|
+
* staying consistent with `useMarkers`'s built-in rendering pipeline.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```tsx
|
|
26
|
+
* const {
|
|
27
|
+
* pickRenderMedium, pickMarkerSize, pickSymbolDescriptor,
|
|
28
|
+
* resolveAdaptiveMarkerState, resolveMapMarkerDomPortalStacking,
|
|
29
|
+
* } = useAdaptiveMarkerHelpers();
|
|
30
|
+
*
|
|
31
|
+
* // Inside your adaptive render callback:
|
|
32
|
+
* const medium = pickRenderMedium(ctx);
|
|
33
|
+
* if (medium === "symbol") return pickSymbolDescriptor(color, theme, ctx);
|
|
34
|
+
* const size = pickMarkerSize(ctx);
|
|
35
|
+
* const state = resolveAdaptiveMarkerState({ selected, hovered });
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export declare const useAdaptiveMarkerHelpers: () => AdaptiveMarkerHelpers;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { type GeoJsonPosition } from "@trackunit/geo-json-utils";
|
|
2
|
+
import type { ControlConfig } from "../../controls/types";
|
|
3
|
+
import type { ClusterConfig, ClusterInfo, ClusterRenderConfig, FitParticipation, MarkerLayerHandle, RenderConfig, ResolutionContext } from "../types";
|
|
4
|
+
import { type ViewportResolutionContext } from "./resolveServerClusters";
|
|
5
|
+
/**
|
|
6
|
+
* Options for the `useMarkers` hook.
|
|
7
|
+
*
|
|
8
|
+
* Two generics, both inferred from usage:
|
|
9
|
+
* - `TItem` from `data`
|
|
10
|
+
* - `TCluster` from `cluster.data` (server-side) or defaults to `ClusterInfo` (client-side / no clustering)
|
|
11
|
+
*/
|
|
12
|
+
export type UseMarkersOptions<TItem, TCluster = ClusterInfo> = Readonly<{
|
|
13
|
+
/** Unique ID for this layer */
|
|
14
|
+
id: string;
|
|
15
|
+
/** Human-readable name */
|
|
16
|
+
name: string;
|
|
17
|
+
/** Source data array */
|
|
18
|
+
data: ReadonlyArray<TItem>;
|
|
19
|
+
/** Extract position from item. Return null for items without position. */
|
|
20
|
+
getPosition: (item: TItem) => GeoJsonPosition | null;
|
|
21
|
+
/** Extract unique ID from item */
|
|
22
|
+
getId: (item: TItem) => string;
|
|
23
|
+
/**
|
|
24
|
+
* Clustering strategy. Omit to disable clustering.
|
|
25
|
+
* - mode: "client" -- adapter clusters for you (Mapbox native, Supercluster, etc.)
|
|
26
|
+
* - mode: "server" -- clusters are pre-computed, TCluster inferred from cluster.data
|
|
27
|
+
*/
|
|
28
|
+
cluster?: ClusterConfig<TCluster>;
|
|
29
|
+
/** How markers are rendered (symbol, dom, or adaptive) */
|
|
30
|
+
markerRender: RenderConfig<TItem>;
|
|
31
|
+
/**
|
|
32
|
+
* How clusters are rendered. Only relevant when `cluster` is provided.
|
|
33
|
+
* For server-side clusters, the render function receives the full TCluster.
|
|
34
|
+
* For client-side clusters, it receives ClusterInfo (adapter-produced).
|
|
35
|
+
*
|
|
36
|
+
* Per ADR-0016, the render callback's second argument is
|
|
37
|
+
* `ClusterRenderState<TItem>` carrying `memberItems` cross-referenced from
|
|
38
|
+
* `data` (DOM mode only — symbol mode receives `memberItems: null`).
|
|
39
|
+
*/
|
|
40
|
+
clusterRender?: ClusterRenderConfig<TCluster, TItem>;
|
|
41
|
+
/** Optional controls to contribute to the map UI */
|
|
42
|
+
controls?: ReadonlyArray<ControlConfig>;
|
|
43
|
+
/** Whether the layer is currently loading its initial data. Omit for static (non-loading) layers. */
|
|
44
|
+
loading?: boolean;
|
|
45
|
+
/** Controls whether this layer's bounds are included in fit-to-content operations. Default: `"all"` */
|
|
46
|
+
fitParticipation?: FitParticipation;
|
|
47
|
+
/**
|
|
48
|
+
* Per-cluster entity-shape decision (ADR-0016).
|
|
49
|
+
*
|
|
50
|
+
* Called per server cluster with `memberItems` cross-referenced from `data`
|
|
51
|
+
* via `getMarkerIds`. `memberItems` is `null` when any member ID is missing
|
|
52
|
+
* from the `data` index — in that case `"expand"` silently falls back to
|
|
53
|
+
* `"keep"`. Omit to leave existing behaviour unchanged.
|
|
54
|
+
*
|
|
55
|
+
* Expansion is expand-only (`TCluster` is preserved); the cluster feature
|
|
56
|
+
* is removed when `"expand"` is returned with non-null `memberItems`. Member
|
|
57
|
+
* features are not synthesized by the hook — they appear in the marker
|
|
58
|
+
* feature collection because the consumer keeps the corresponding items in
|
|
59
|
+
* `data` (the optimistic-fetch pattern described in ADR-0016).
|
|
60
|
+
*/
|
|
61
|
+
resolveGroups?: (cluster: TCluster, memberItems: ReadonlyArray<TItem> | null, ctx: ResolutionContext) => "keep" | "expand";
|
|
62
|
+
/**
|
|
63
|
+
* Live map state forwarded to every `resolveGroups` call.
|
|
64
|
+
*
|
|
65
|
+
* When omitted, `zoom`, `markersInViewport`, and `viewportBounds` default
|
|
66
|
+
* to `0`, `0`, and `null`. Provide this whenever `resolveGroups` is used so
|
|
67
|
+
* the callback receives real viewport signals instead of zeros.
|
|
68
|
+
*/
|
|
69
|
+
resolutionContext?: ViewportResolutionContext;
|
|
70
|
+
}>;
|
|
71
|
+
/**
|
|
72
|
+
* Return type of `useMarkers`.
|
|
73
|
+
* Extends `MarkerLayerHandle` with convenience count properties.
|
|
74
|
+
*/
|
|
75
|
+
export interface UseMarkersReturn extends MarkerLayerHandle {
|
|
76
|
+
/** All positioned items (excludes items where getPosition returned null) */
|
|
77
|
+
readonly positionedCount: number;
|
|
78
|
+
/** Total items including unpositioned */
|
|
79
|
+
readonly totalCount: number;
|
|
80
|
+
/** Number of clusters (0 when clustering is disabled or data is empty) */
|
|
81
|
+
readonly clusterCount: number;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* `useMarkers` -- creates a marker layer handle from arbitrary data.
|
|
85
|
+
*
|
|
86
|
+
* Generic over:
|
|
87
|
+
* - `TItem`: the data item type (inferred from `data`)
|
|
88
|
+
* - `TCluster`: the cluster data type (inferred from `cluster.data` for server-side,
|
|
89
|
+
* defaults to `ClusterInfo` for client-side or no clustering)
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* ```tsx
|
|
93
|
+
* // Server-side clusters with adaptive rendering: high-importance assets
|
|
94
|
+
* // are promoted to DOM while the rest stay as fast WebGL symbols.
|
|
95
|
+
* const fleet = useMarkers({
|
|
96
|
+
* id: "fleet",
|
|
97
|
+
* name: "Fleet Assets",
|
|
98
|
+
* data: assets,
|
|
99
|
+
* getPosition: (a) => a.location,
|
|
100
|
+
* getId: (a) => a.id,
|
|
101
|
+
* cluster: {
|
|
102
|
+
* mode: "server",
|
|
103
|
+
* data: serverClusters,
|
|
104
|
+
* getPosition: (c) => c.center,
|
|
105
|
+
* getId: (c) => c.clusterId,
|
|
106
|
+
* getMarkerIds: (c) => c.assetIds,
|
|
107
|
+
* getCount: (c) => c.count,
|
|
108
|
+
* getBbox: (c) => c.bbox,
|
|
109
|
+
* },
|
|
110
|
+
* markerRender: {
|
|
111
|
+
* mode: "adaptive",
|
|
112
|
+
* anchor: "center",
|
|
113
|
+
* resolveMode: (a) => (a.importance >= 8 ? "dom" : "symbol"),
|
|
114
|
+
* render: (a, state) => {
|
|
115
|
+
* if (state.medium === "symbol") return { color: statusColor(a.status) };
|
|
116
|
+
* return <AssetMarker asset={a} selected={state.selected} />;
|
|
117
|
+
* },
|
|
118
|
+
* },
|
|
119
|
+
* clusterRender: {
|
|
120
|
+
* mode: "dom",
|
|
121
|
+
* anchor: "center",
|
|
122
|
+
* render: (cluster, state) => <ClusterBubble cluster={cluster} />,
|
|
123
|
+
* },
|
|
124
|
+
* });
|
|
125
|
+
* ```
|
|
126
|
+
*/
|
|
127
|
+
export declare const useMarkers: <TItem, TCluster = ClusterInfo>(options: UseMarkersOptions<TItem, TCluster>) => UseMarkersReturn;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
import type { Entity, ShapeEntity } from "@trackunit/react-map-adapter-shared";
|
|
3
|
+
type InteractiveDecorationProps = Readonly<{
|
|
4
|
+
entity: ShapeEntity;
|
|
5
|
+
select: (entity: Entity | null) => void;
|
|
6
|
+
hover: (entity: Entity | null) => void;
|
|
7
|
+
onClick?: (entity: ShapeEntity) => void;
|
|
8
|
+
onDblClick?: (entity: ShapeEntity) => void;
|
|
9
|
+
onHover?: (entity: ShapeEntity | null) => void;
|
|
10
|
+
children: ReactNode;
|
|
11
|
+
}>;
|
|
12
|
+
/**
|
|
13
|
+
* Uniform interactive wrapper for shape decorations.
|
|
14
|
+
*
|
|
15
|
+
* Renders a visually-transparent `<button>` that provides cursor, keyboard
|
|
16
|
+
* activation, focus-visible ring, and ARIA semantics. Click dispatches entity
|
|
17
|
+
* selection, mouseenter/mouseleave dispatches entity hover — both followed
|
|
18
|
+
* by optional supplementary callbacks.
|
|
19
|
+
*
|
|
20
|
+
* @internal
|
|
21
|
+
*/
|
|
22
|
+
export declare const InteractiveDecoration: ({ entity, select, hover, onClick, onDblClick, onHover, children, }: InteractiveDecorationProps) => import("react/jsx-runtime").JSX.Element;
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
type MultiGeometryPlusBadgeProps = Readonly<{
|
|
2
|
+
size: number;
|
|
3
|
+
color: string;
|
|
4
|
+
/** Stroke color for the cross. Defaults to `"white"`. */
|
|
5
|
+
plusColor?: string;
|
|
6
|
+
className?: string;
|
|
7
|
+
"data-testid"?: string;
|
|
8
|
+
title?: string;
|
|
9
|
+
"aria-label"?: string;
|
|
10
|
+
}>;
|
|
11
|
+
/**
|
|
12
|
+
* Compact circular "+" badge for multi-part GeoJSON geometries.
|
|
13
|
+
*
|
|
14
|
+
* @internal
|
|
15
|
+
*/
|
|
16
|
+
export declare const MultiGeometryPlusBadge: ({ size, color, plusColor, className, "data-testid": dataTestId, title, "aria-label": ariaLabel, }: MultiGeometryPlusBadgeProps) => import("react/jsx-runtime").JSX.Element;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
type MultiPartBadgeProps = Readonly<{
|
|
2
|
+
color: string;
|
|
3
|
+
}>;
|
|
4
|
+
/**
|
|
5
|
+
* Badge rendered on each part of a multi-geometry feature on the map.
|
|
6
|
+
* Compact circular "+" matching `ShapeIcon`'s preview badge at 14px parent size.
|
|
7
|
+
*
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
export declare const MultiPartBadge: ({ color }: MultiPartBadgeProps) => import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { GeoJsonGeometry } from "@trackunit/geo-json-utils";
|
|
2
|
+
import type { MapTheme } from "../../core/types";
|
|
3
|
+
type ShapeAnnotationLabelProps = Readonly<{
|
|
4
|
+
geometry: GeoJsonGeometry | null;
|
|
5
|
+
color: string;
|
|
6
|
+
label: string;
|
|
7
|
+
theme: MapTheme;
|
|
8
|
+
/** True when the underlying shape is hovered on the map — darkens the pill to match edge-label hover style. */
|
|
9
|
+
isHovered?: boolean;
|
|
10
|
+
/** True when the underlying shape is selected on the map — darkens the pill more to match edge-label selected style. */
|
|
11
|
+
isSelected?: boolean;
|
|
12
|
+
onClick?: () => void;
|
|
13
|
+
onMouseEnter?: () => void;
|
|
14
|
+
onMouseLeave?: () => void;
|
|
15
|
+
}>;
|
|
16
|
+
/**
|
|
17
|
+
* Annotation-mode shape label shown in the `AnnotationStack` when
|
|
18
|
+
* the viewport is fully inside a shape and no edge is visible.
|
|
19
|
+
*
|
|
20
|
+
* Composes `ShapeLabelPill` with a `ShapeIcon` preview on the left
|
|
21
|
+
* to preserve the site's visual identity (color + geometry type icon)
|
|
22
|
+
* in the annotation chrome bar. When `onClick` is provided, a wrapping
|
|
23
|
+
* `<button>` handles interaction; pass `fitFeatureBounds` here to
|
|
24
|
+
* zoom-to-fit on click.
|
|
25
|
+
*
|
|
26
|
+
* Intended for use inside a `MapAnnotationDescriptor` with
|
|
27
|
+
* `type: "custom"` — see `useFleetSiteAnnotation` for the canonical usage.
|
|
28
|
+
*/
|
|
29
|
+
export declare const ShapeAnnotationLabel: ({ geometry, color, label, theme, isHovered, isSelected, onClick, onMouseEnter, onMouseLeave, }: ShapeAnnotationLabelProps) => import("react/jsx-runtime").JSX.Element;
|
|
30
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { GeoJsonGeometry } from "@trackunit/geo-json-utils";
|
|
2
|
+
type ShapeIconProps = Readonly<{
|
|
3
|
+
geometry: GeoJsonGeometry | null;
|
|
4
|
+
color: string;
|
|
5
|
+
size: number;
|
|
6
|
+
/** Stroke width in pixels. When omitted, scales automatically with `size`. */
|
|
7
|
+
strokeWidth?: number;
|
|
8
|
+
/** Foreground color for the multi-badge plus. Defaults to `"white"`. */
|
|
9
|
+
badgeColor?: string;
|
|
10
|
+
}>;
|
|
11
|
+
/**
|
|
12
|
+
* Renders a miniature SVG preview of a GeoJSON geometry.
|
|
13
|
+
*
|
|
14
|
+
* For multi-geometries (MultiPolygon, MultiLineString, etc.), shows
|
|
15
|
+
* a "multi" badge in the top-right corner. The badge is absolutely
|
|
16
|
+
* positioned so it does not inflate the icon's layout dimensions.
|
|
17
|
+
*
|
|
18
|
+
* @internal
|
|
19
|
+
*/
|
|
20
|
+
export declare const ShapeIcon: ({ geometry, color, size, strokeWidth, badgeColor }: ShapeIconProps) => import("react/jsx-runtime").JSX.Element | null;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { CSSProperties, ReactNode } from "react";
|
|
2
|
+
import type { MapTheme } from "../../core/types";
|
|
3
|
+
type ShapeLabelPillProps = Readonly<{
|
|
4
|
+
color: string;
|
|
5
|
+
theme: MapTheme;
|
|
6
|
+
borderRadius: string;
|
|
7
|
+
className?: string;
|
|
8
|
+
style?: CSSProperties;
|
|
9
|
+
"data-testid"?: string;
|
|
10
|
+
children: ReactNode;
|
|
11
|
+
}>;
|
|
12
|
+
export declare const cvaShapeLabel: (props?: ({
|
|
13
|
+
theme?: "light" | "dark" | null | undefined;
|
|
14
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
15
|
+
/**
|
|
16
|
+
* Shared styled container for shape labels (edge labels and annotation labels).
|
|
17
|
+
*
|
|
18
|
+
* Always renders a `<div>` — interaction is handled externally by
|
|
19
|
+
* `InteractiveDecoration` (portal path) or a wrapping `<button>` in
|
|
20
|
+
* `ShapeAnnotationLabel` (annotation path).
|
|
21
|
+
*
|
|
22
|
+
* @internal
|
|
23
|
+
*/
|
|
24
|
+
export declare const ShapeLabelPill: ({ color, theme, borderRadius, className, style, "data-testid": dataTestId, children, }: ShapeLabelPillProps) => import("react/jsx-runtime").JSX.Element;
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type GeoJsonBbox, type GeoJsonFeature } from "@trackunit/geo-json-utils";
|
|
2
|
+
import type { MapInteractionState } from "@trackunit/react-map-adapter-shared";
|
|
3
|
+
import type { ShapeLabelResolutionContext } from "./shapeLabelResolution";
|
|
4
|
+
/**
|
|
5
|
+
* Build the per-feature, per-frame context passed to an `edge-auto` anchor's
|
|
6
|
+
* `resolveLabel` callback. Pure: no React, no `api` reads — caller supplies the
|
|
7
|
+
* already-projected viewport bits, the precomputed `shapesInViewport` count for
|
|
8
|
+
* the layer, and the current interaction state.
|
|
9
|
+
*
|
|
10
|
+
* Lives next to `shapeLabelResolution.ts` and is consumed by
|
|
11
|
+
* `useShapeDecorations` once per feature that has at least one `edge-auto`
|
|
12
|
+
* decoration, before any placement work runs.
|
|
13
|
+
*/
|
|
14
|
+
export declare const buildShapeLabelResolutionContext: (feature: GeoJsonFeature, args: Readonly<{
|
|
15
|
+
handleId: string;
|
|
16
|
+
featureKey: string;
|
|
17
|
+
viewportBounds: Readonly<GeoJsonBbox>;
|
|
18
|
+
zoom: number;
|
|
19
|
+
tileSize: number;
|
|
20
|
+
shapesInViewport: number;
|
|
21
|
+
overlappingShapesCount: number;
|
|
22
|
+
interaction: MapInteractionState;
|
|
23
|
+
}>) => ShapeLabelResolutionContext | null;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { Placement } from "@floating-ui/react";
|
|
2
|
+
import type { ShapeEntity } from "@trackunit/react-map-adapter-shared";
|
|
3
|
+
import type { RefCallback } from "react";
|
|
4
|
+
import type { EdgeLabelAnchor, EdgeLabelPlacementResolver } from "./edge/findBestEdgePosition";
|
|
5
|
+
import type { ShapeDecoration } from "./shapeDecorations";
|
|
6
|
+
import type { ResolveShapeLabel } from "./shapeLabelResolution";
|
|
7
|
+
/**
|
|
8
|
+
* Create an edge-auto decoration that places a flush label on the best visible
|
|
9
|
+
* edge of a polygon or line. Returns `undefined` when `label` is nullish, so
|
|
10
|
+
* it can be used directly in a `getDecorations` array and filtered automatically.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```tsx
|
|
14
|
+
* const zones = useShapes({
|
|
15
|
+
* id: "zones",
|
|
16
|
+
* features: zoneFeatureCollection,
|
|
17
|
+
* resolveStyle: () => ({ stroke: "blue" }),
|
|
18
|
+
* getDecorations: (f) => [
|
|
19
|
+
* edgeLabel(f.properties?.name as string | undefined),
|
|
20
|
+
* ],
|
|
21
|
+
* });
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export declare const edgeLabel: (label: string | undefined | null, options?: Readonly<{
|
|
25
|
+
interactive?: boolean;
|
|
26
|
+
onClick?: (entity: ShapeEntity) => void;
|
|
27
|
+
onHover?: (entity: ShapeEntity | null) => void;
|
|
28
|
+
resolveLabel?: ResolveShapeLabel;
|
|
29
|
+
labelAnchor?: EdgeLabelAnchor;
|
|
30
|
+
/**
|
|
31
|
+
* Custom placement resolver. When omitted, the default resolver is used.
|
|
32
|
+
* The resolver receives `context.isForced = true` on the forced retry pass
|
|
33
|
+
* (when no edge fit the label), allowing anchor or side changes without a
|
|
34
|
+
* separate option.
|
|
35
|
+
*/
|
|
36
|
+
labelPlacementResolver?: EdgeLabelPlacementResolver;
|
|
37
|
+
/**
|
|
38
|
+
* Forwarded to `ShapeDecoration.panelAnchorRef`. The edge-label portal
|
|
39
|
+
* container element is passed to this callback so a panel can anchor to it.
|
|
40
|
+
*/
|
|
41
|
+
panelAnchorRef?: RefCallback<HTMLElement>;
|
|
42
|
+
/**
|
|
43
|
+
* Forwarded to `ShapeDecoration.onEdgePlacement`. Called with the outward
|
|
44
|
+
* Floating UI `Placement` when the label is placed on an edge.
|
|
45
|
+
*/
|
|
46
|
+
onEdgePlacement?: (placement: Placement) => void;
|
|
47
|
+
}>) => ShapeDecoration | undefined;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { MapTheme } from "../../../core/types";
|
|
2
|
+
import type { EdgeFitMode, EdgeLabelLayout } from "./findBestEdgePosition";
|
|
3
|
+
type ShapeEdgeLabelProps = Readonly<{
|
|
4
|
+
text: string;
|
|
5
|
+
color: string;
|
|
6
|
+
theme: MapTheme;
|
|
7
|
+
layout: EdgeLabelLayout;
|
|
8
|
+
fitMode: EdgeFitMode;
|
|
9
|
+
/** When true the label is invisible, waiting for its parent shape to finish rendering. */
|
|
10
|
+
hidden?: boolean;
|
|
11
|
+
}>;
|
|
12
|
+
export declare const ShapeEdgeLabel: ({ text, color, theme, layout, fitMode, hidden }: ShapeEdgeLabelProps) => import("react/jsx-runtime").JSX.Element;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { GeoJsonBbox, GeoJsonFeature, GeoJsonPosition } from "@trackunit/geo-json-utils";
|
|
2
|
+
import type { MarkerAnchor } from "@trackunit/react-map-adapter-shared";
|
|
3
|
+
import { type EdgeIdentity, type EdgeInsets, type EdgeLabelAnchor, type EdgeLabelPlacementResolver, type EdgePlacement } from "./findBestEdgePosition";
|
|
4
|
+
/**
|
|
5
|
+
* Placement mode chosen by the caller (typically derived from a `resolveLabel`
|
|
6
|
+
* callback on the `edge-auto` anchor).
|
|
7
|
+
*
|
|
8
|
+
* - `"auto"` — only place when an edge fits the measured label width.
|
|
9
|
+
* - `"force"` — if no edge fits, retry with no width requirement so the best
|
|
10
|
+
* available edge holds the label (may overflow visually).
|
|
11
|
+
*
|
|
12
|
+
* In both modes the label is hidden when the feature is entirely off-screen.
|
|
13
|
+
*/
|
|
14
|
+
export type EdgeAutoPlacementMode = "auto" | "force";
|
|
15
|
+
export type EdgeAutoPlacementOptions = Readonly<{
|
|
16
|
+
/** Pixel width measurement callback for the label text. */
|
|
17
|
+
labelPixelWidth: (label: string) => number;
|
|
18
|
+
/** Max angle from horizontal for readable rotated text (degrees). */
|
|
19
|
+
maxReadableAngleDeg: number | undefined;
|
|
20
|
+
/** Previous edge identity for hysteresis — avoids label jumping during panning. */
|
|
21
|
+
previousEdgeIdentity: EdgeIdentity | undefined;
|
|
22
|
+
/** Previous point position for hysteresis — avoids MultiPoint label jumping during panning. */
|
|
23
|
+
previousPointPosition: GeoJsonPosition | undefined;
|
|
24
|
+
/**
|
|
25
|
+
* Geographic position of the previous anchor, for "don't slide back left" hysteresis.
|
|
26
|
+
* When provided, a left-anchored label will not retreat left once the viewport has
|
|
27
|
+
* pushed it rightward along the edge to stay in view.
|
|
28
|
+
*/
|
|
29
|
+
previousAnchorGeo?: GeoJsonPosition;
|
|
30
|
+
/**
|
|
31
|
+
* The layout side ("above" | "below") from the previous frame.
|
|
32
|
+
* When provided, the label will not flip sides unless the current side
|
|
33
|
+
* no longer fits within the viewport.
|
|
34
|
+
*/
|
|
35
|
+
previousLayoutSide?: "above" | "below";
|
|
36
|
+
/** Per-edge insets from shape decorations (e.g. corner badges) to avoid overlap. */
|
|
37
|
+
edgeInsets: ReadonlyArray<EdgeInsets> | undefined;
|
|
38
|
+
/** Where the label attaches along the selected edge. Defaults to `"left"`. */
|
|
39
|
+
labelAnchor?: EdgeLabelAnchor;
|
|
40
|
+
/** Custom placement resolver. When omitted, `defaultEdgeLabelPlacementResolver` is used. */
|
|
41
|
+
labelPlacementResolver?: EdgeLabelPlacementResolver;
|
|
42
|
+
/** Placement mode. Defaults to `"auto"`. */
|
|
43
|
+
mode?: EdgeAutoPlacementMode;
|
|
44
|
+
}>;
|
|
45
|
+
export type EdgeAutoPlacementOutcome = Readonly<{
|
|
46
|
+
type: "placed";
|
|
47
|
+
placement: EdgePlacement;
|
|
48
|
+
anchor: MarkerAnchor;
|
|
49
|
+
}> | Readonly<{
|
|
50
|
+
type: "hidden";
|
|
51
|
+
}>;
|
|
52
|
+
/**
|
|
53
|
+
* Compute the best placement for a shape label.
|
|
54
|
+
*
|
|
55
|
+
* Centralizes layout decisions: geometry branching (Point vs polygon/line),
|
|
56
|
+
* pixel width estimation, edge-finding for non-point geometries, and
|
|
57
|
+
* placement-to-anchor mapping.
|
|
58
|
+
*
|
|
59
|
+
* Returns a discriminated outcome:
|
|
60
|
+
* - `"placed"` — label positioned on an edge with placement + anchor
|
|
61
|
+
* - `"hidden"` — shape is off-screen, no qualifying edge, or (mode `"auto"`) no
|
|
62
|
+
* edge fits the label width
|
|
63
|
+
*
|
|
64
|
+
* Whether the viewport is fully inside the shape is no longer decided here —
|
|
65
|
+
* callers compute that as part of the `ShapeLabelResolutionContext` so the
|
|
66
|
+
* `resolveLabel` callback can react to it (e.g. hide, or fall back to a
|
|
67
|
+
* caller-managed annotation).
|
|
68
|
+
*/
|
|
69
|
+
export declare const computeEdgeAutoPlacement: (feature: GeoJsonFeature, label: string, viewportBounds: Readonly<GeoJsonBbox>, zoom: number, tileSize: number, options: EdgeAutoPlacementOptions) => EdgeAutoPlacementOutcome;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type GeoJsonGeometry, type GeoJsonPosition } from "@trackunit/geo-json-utils";
|
|
2
|
+
/**
|
|
3
|
+
* Computed properties injected into `sourceProperties` for all edge-* anchors.
|
|
4
|
+
* Render functions read these from the `item` argument via portal descriptors.
|
|
5
|
+
*/
|
|
6
|
+
export type EdgeComputedProperties = Readonly<{
|
|
7
|
+
/** Screen-space angle of the edge in degrees, normalized to [-90, 90]. */
|
|
8
|
+
angleDeg: number;
|
|
9
|
+
/** Which side of the edge faces away from the shape interior. "above" for LineStrings. */
|
|
10
|
+
outwardSide: "above" | "below";
|
|
11
|
+
/** Length of the edge in screen pixels at the current zoom level. */
|
|
12
|
+
edgePixelLength: number;
|
|
13
|
+
}>;
|
|
14
|
+
/**
|
|
15
|
+
* Compute edge-specific properties from resolved edge endpoints.
|
|
16
|
+
*
|
|
17
|
+
* For Polygons, `outwardSide` uses the cross product with the geometry centroid
|
|
18
|
+
* to determine which side faces away from the interior. For LineStrings (no
|
|
19
|
+
* interior), `outwardSide` defaults to `"above"`.
|
|
20
|
+
*/
|
|
21
|
+
export declare const computeEdgeProperties: (start: GeoJsonPosition, end: GeoJsonPosition, geometry: GeoJsonGeometry, zoom: number, tileSize: number) => EdgeComputedProperties;
|