@orbat-mapper/tactical-draw 0.2.0-alpha.32 → 0.2.0-alpha.34
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/dist/index.d.mts +81 -2
- package/dist/index.mjs +131 -9
- package/package.json +2 -2
package/dist/index.d.mts
CHANGED
|
@@ -233,7 +233,9 @@ interface MapAdapter {
|
|
|
233
233
|
* - features with an `id` that already exists are updated in place;
|
|
234
234
|
* - features with a fresh `id` are added;
|
|
235
235
|
* - features in the layer whose `id` is missing from the incoming set are
|
|
236
|
-
* removed
|
|
236
|
+
* removed;
|
|
237
|
+
* - the incoming array order is authoritative for render order — later
|
|
238
|
+
* features draw on top, so a call that only reorders is a visible change.
|
|
237
239
|
*
|
|
238
240
|
* Features must carry a stable `id`. Use this when you can compute the
|
|
239
241
|
* complete desired state of a layer in one go; for partial removals use
|
|
@@ -375,6 +377,14 @@ interface FeatureOps {
|
|
|
375
377
|
updates: Feature[];
|
|
376
378
|
/** Features whose id was absent from the incoming set. */
|
|
377
379
|
removes?: Feature[];
|
|
380
|
+
/**
|
|
381
|
+
* Complete, ordered snapshot of the layer after this op. The order is
|
|
382
|
+
* authoritative for render order (later features draw on top), so
|
|
383
|
+
* `applyFeatureOps` must leave the layer rendering in `full`'s order — a
|
|
384
|
+
* pure reorder ships identical content in a new order and still counts as a
|
|
385
|
+
* change. Adapters that rebuild the layer from `full` satisfy this for free;
|
|
386
|
+
* incremental adapters must reconcile order against it.
|
|
387
|
+
*/
|
|
378
388
|
full: Feature[];
|
|
379
389
|
replace?: boolean;
|
|
380
390
|
}
|
|
@@ -635,6 +645,15 @@ interface TextSizeProps {
|
|
|
635
645
|
* - no usable size or resolution: `null`.
|
|
636
646
|
*/
|
|
637
647
|
declare function resolveTextSizePixels(props: TextSizeProps, resolution: number): number | null;
|
|
648
|
+
/** Fallback halo color when a `textHalo` feature carries no `textHaloColor`. */
|
|
649
|
+
declare const TEXT_HALO_COLOR = "#ffffff";
|
|
650
|
+
/**
|
|
651
|
+
* Outward depth (px) of a `textHalo` halo from the glyph edge, scaled to font
|
|
652
|
+
* size. This is the halo's *outward* reach, not a stroke width — a canvas
|
|
653
|
+
* stroke straddles the glyph edge (half in, half out), so a caller drawing
|
|
654
|
+
* one must double this value to get the same visible depth.
|
|
655
|
+
*/
|
|
656
|
+
declare function resolveTextHaloWidth(textSizePixels?: number): number;
|
|
638
657
|
//#endregion
|
|
639
658
|
//#region src/gestures/hit-test.d.ts
|
|
640
659
|
type ToPixel = (lonLat: Position) => PixelCoordinate | null;
|
|
@@ -877,6 +896,15 @@ interface InteractionStyle {
|
|
|
877
896
|
scaleHandle?: HandleStyle;
|
|
878
897
|
rotateHandle?: HandleStyle;
|
|
879
898
|
boxOutline?: GuideStyle;
|
|
899
|
+
/**
|
|
900
|
+
* Line style for the passive, non-interactive selection highlight rendered
|
|
901
|
+
* by {@link TacticalDraw.setHighlightedGraphics}. A dashed, muted variant of
|
|
902
|
+
* `boxOutline` so a locked/highlighted selection reads as visually distinct
|
|
903
|
+
* from an active edit's oriented transform box. Delivered per-feature via
|
|
904
|
+
* `properties.style` on the highlight outline `LineString`, resolved through
|
|
905
|
+
* the same built-in → façade → per-call slot system as every other field.
|
|
906
|
+
*/
|
|
907
|
+
highlightOutline?: GuideStyle;
|
|
880
908
|
/**
|
|
881
909
|
* Pixel padding added on every side of the fitted transform box
|
|
882
910
|
* (`fitTransformBox`, ADR-0017) so the box doesn't tightly hug the rendered
|
|
@@ -1547,6 +1575,12 @@ type DrawPointSymbolDraft = Pick<PointSymbol, "kind" | "sidc"> & Partial<Omit<Po
|
|
|
1547
1575
|
*/
|
|
1548
1576
|
interface TacticalDrawLayerOptions {
|
|
1549
1577
|
graphics?: LayerId;
|
|
1578
|
+
/**
|
|
1579
|
+
* Passive selection-highlight outlines ({@link TacticalDraw.setHighlightedGraphics}).
|
|
1580
|
+
* Resolved immediately above `graphics` so highlight boxes stack over base
|
|
1581
|
+
* graphics but below the preview / guide / handles interaction chrome.
|
|
1582
|
+
*/
|
|
1583
|
+
highlight?: LayerId;
|
|
1550
1584
|
preview?: LayerId;
|
|
1551
1585
|
guide?: LayerId;
|
|
1552
1586
|
handles?: LayerId;
|
|
@@ -1733,6 +1767,7 @@ interface DrawOptions {
|
|
|
1733
1767
|
declare class TacticalDraw {
|
|
1734
1768
|
private readonly adapter;
|
|
1735
1769
|
private readonly graphicsLayer;
|
|
1770
|
+
private readonly highlightLayer;
|
|
1736
1771
|
private readonly previewLayer;
|
|
1737
1772
|
private readonly guideLayer;
|
|
1738
1773
|
private readonly handlesLayer;
|
|
@@ -1794,6 +1829,20 @@ declare class TacticalDraw {
|
|
|
1794
1829
|
*/
|
|
1795
1830
|
private lastRenderedMeasures;
|
|
1796
1831
|
private snapGeometryFeatures;
|
|
1832
|
+
/**
|
|
1833
|
+
* Ids of graphics currently carrying a passive selection-highlight outline
|
|
1834
|
+
* ({@link setHighlightedGraphics}). Declarative set-replace, no session —
|
|
1835
|
+
* re-projected on every view change and re-evaluated at the end of every
|
|
1836
|
+
* `render()` so outlines track geometry, style, and dropped graphics.
|
|
1837
|
+
*/
|
|
1838
|
+
private highlightedIds;
|
|
1839
|
+
/**
|
|
1840
|
+
* Whether the highlight layer currently holds any outline. Lets
|
|
1841
|
+
* `renderHighlights` skip a redundant empty write on every view change /
|
|
1842
|
+
* render while no highlights are set, but still emit the one clearing write
|
|
1843
|
+
* that removes outlines when the set transitions to empty.
|
|
1844
|
+
*/
|
|
1845
|
+
private hasHighlightOutlines;
|
|
1797
1846
|
constructor(adapter: MapAdapter, options?: TacticalDrawOptions);
|
|
1798
1847
|
/** Replace snapping configuration without recreating the map adapter or façade. */
|
|
1799
1848
|
setSnappingOptions(options?: SnappingOptions): void;
|
|
@@ -1858,6 +1907,34 @@ declare class TacticalDraw {
|
|
|
1858
1907
|
* pure style replay).
|
|
1859
1908
|
*/
|
|
1860
1909
|
render(measures: readonly Graphic[]): void;
|
|
1910
|
+
/**
|
|
1911
|
+
* Replace the passive selection-highlight set with `ids`. Declarative and
|
|
1912
|
+
* idempotent (mirrors {@link render}): a non-interactive outline box is drawn
|
|
1913
|
+
* around each still-rendered graphic on the dedicated highlight layer — no
|
|
1914
|
+
* draw / edit / transform session, no promise, no pick subscription. Passing
|
|
1915
|
+
* `[]` clears every highlight. Unknown ids and ids currently lifted into an
|
|
1916
|
+
* active edit / transform session are skipped (the live session's own visuals
|
|
1917
|
+
* supersede the passive outline). Hosts use it for Excalidraw-style
|
|
1918
|
+
* "locked selection" chrome.
|
|
1919
|
+
*/
|
|
1920
|
+
setHighlightedGraphics(ids: readonly string[]): void;
|
|
1921
|
+
/**
|
|
1922
|
+
* Project the current {@link highlightedIds} into one outline `LineString`
|
|
1923
|
+
* per graphic and reconcile them onto the highlight layer. Idempotent and
|
|
1924
|
+
* id-keyed via `adapter.updateLayerFeatures`, so a set-replace removes stale
|
|
1925
|
+
* outlines and an unchanged set is a cheap no-op write. Skips ids that are
|
|
1926
|
+
* unknown (not in the last render) or lifted into an active session, and
|
|
1927
|
+
* bails gracefully when the adapter cannot invert pixels to coordinates
|
|
1928
|
+
* (`getCoordinateFromPixel` is optional in the MapAdapter ABI).
|
|
1929
|
+
*/
|
|
1930
|
+
private renderHighlights;
|
|
1931
|
+
/**
|
|
1932
|
+
* Fit the passive-highlight transform box for one graphic: the point-symbol
|
|
1933
|
+
* footprint for symbols, the snug text footprint for Text graphics, and the
|
|
1934
|
+
* projected-geometry bounding box (at the current bearing) for every other
|
|
1935
|
+
* control measure. Returns `null` when nothing projects this frame.
|
|
1936
|
+
*/
|
|
1937
|
+
private highlightBoxForGraphic;
|
|
1861
1938
|
/**
|
|
1862
1939
|
* Nested-call guard + preempt, shared by `draw` and `edit`. Returns a
|
|
1863
1940
|
* pre-rejected promise when called re-entrantly from inside another
|
|
@@ -2093,6 +2170,7 @@ declare class TacticalDraw {
|
|
|
2093
2170
|
/** @internal — exposed for tests and future controllers. */
|
|
2094
2171
|
get layerIds(): {
|
|
2095
2172
|
graphics: LayerId;
|
|
2173
|
+
highlight: LayerId;
|
|
2096
2174
|
preview: LayerId;
|
|
2097
2175
|
guide: LayerId;
|
|
2098
2176
|
handles: LayerId;
|
|
@@ -2104,6 +2182,7 @@ declare class TacticalDraw {
|
|
|
2104
2182
|
*/
|
|
2105
2183
|
private applyGuideStyle;
|
|
2106
2184
|
private baseGuideStyle;
|
|
2185
|
+
private baseHighlightStyle;
|
|
2107
2186
|
/**
|
|
2108
2187
|
* Resolve handle slots (plus `boxOutline`) across built-in → façade →
|
|
2109
2188
|
* per-call with per-slot, per-property shallow merge. Explicit `undefined`
|
|
@@ -2196,4 +2275,4 @@ declare function nearestWorldCopyLng(lng: number, refLng: number): number;
|
|
|
2196
2275
|
/** `nearestWorldCopyLng` applied to a lon/lat position. */
|
|
2197
2276
|
declare function nearestWorldCopyPosition(position: Position, refLng: number): Position;
|
|
2198
2277
|
//#endregion
|
|
2199
|
-
export { BaseMapAdapter, type CombinedAbort, DEFAULT_LABEL_SIZE_PIXELS, DEFAULT_TEXT_SIZE_MAX_PIXELS, DEFAULT_TEXT_SIZE_MIN_PIXELS, type DrawMeasureDraft, type DrawOptions, type DrawPointSymbolDraft, type DrawSession, type EditChangeEvent, type EditManyOptions, type EditMode, type EditOptions, type EditPointerDriver, type EditSession, type FeatureOps, type Graphic, type GraphicEditChangeEvent, type GraphicEditSession, type GraphicGeometrySnappingOptions, type GraphicRender, type GraphicSnapshot, type GuideStyle, HATCH_PATTERNS, type HandleStyle, type HatchPattern, type IconResource, type InteractionHit, type InteractionStyle, type LabelExtent, type LayerId, type MapAdapter, type MapCursor, type MapEvent, type MapEventHandler, type MergedPickFeature, type NativeEventEntry, type PickCandidate, type PickCandidateHit, type PickCandidateHitKind, type PickCandidateInput, type PickEvaluationContext, type PickEvent, type PickHandler, type PickOptions, type PickPointerType, type PixelCoordinate, type PointSymbol, type PointSymbolAnchor, type PointSymbolCapability, PointSymbolCapabilityRequiredError, type PointSymbolColors, type PointSymbolFootprint, type PointSymbolGraphicModifiers, type PointSymbolRender, type PointSymbolSize, type PointSymbolStyle, type PointerCallback, PointerCallbackHub, type PointerEventInfo, type ResolvedPickPresentation, type RotatedScreenBounds, type ScreenBounds, type SessionChangeSource, type SessionHistory, type SessionHistoryState, type SettleReason, type SizeAnchor, type SnapCandidate, type SnapCandidateProvider, type SnapCandidateRequest, type SnapInteraction, type SnapResult, type SnappingOptions, type StyleOptions, TOUCH_HIT_TOLERANCE_PX, TacticalDraw, TacticalDrawAbortError, type TacticalDrawAbortReason, TacticalDrawDestroyedError, type TacticalDrawLayerOptions, type TacticalDrawOptions, type TextSizeProps, type TextStyleOptions, type ToPixel, type TransformBox, type TransformChangeEvent, type TransformSession, type TransientChangeEvent, type TransientPhase, bindAbortable, clonePointSymbol, coerceHandleStyle, combineWithHostSignal, effectivePointSymbolPixelSize, evaluatePickCandidates, fillPatternImageDimension, fillPatternImageId, getSizeAnchor, hitTestFeature, hitTestIconFeature, ignoreAbort, isPointSymbol, isTacticalDrawAbortError, makeFillPatternImage, mergeNativeAndCachedPickFeatures, nearestWorldCopyLng, nearestWorldCopyPosition, pickHitTolerance, pickPointerType, pointSymbolFootprint, renderOrderToVisualOrder, resizePointSymbolSize, resolveGroundTextSizePixels, resolvePickPresentation, resolveTextSizePixels, tryControlMeasureIdFromFeature };
|
|
2278
|
+
export { BaseMapAdapter, type CombinedAbort, DEFAULT_LABEL_SIZE_PIXELS, DEFAULT_TEXT_SIZE_MAX_PIXELS, DEFAULT_TEXT_SIZE_MIN_PIXELS, type DrawMeasureDraft, type DrawOptions, type DrawPointSymbolDraft, type DrawSession, type EditChangeEvent, type EditManyOptions, type EditMode, type EditOptions, type EditPointerDriver, type EditSession, type FeatureOps, type Graphic, type GraphicEditChangeEvent, type GraphicEditSession, type GraphicGeometrySnappingOptions, type GraphicRender, type GraphicSnapshot, type GuideStyle, HATCH_PATTERNS, type HandleStyle, type HatchPattern, type IconResource, type InteractionHit, type InteractionStyle, type LabelExtent, type LayerId, type MapAdapter, type MapCursor, type MapEvent, type MapEventHandler, type MergedPickFeature, type NativeEventEntry, type PickCandidate, type PickCandidateHit, type PickCandidateHitKind, type PickCandidateInput, type PickEvaluationContext, type PickEvent, type PickHandler, type PickOptions, type PickPointerType, type PixelCoordinate, type PointSymbol, type PointSymbolAnchor, type PointSymbolCapability, PointSymbolCapabilityRequiredError, type PointSymbolColors, type PointSymbolFootprint, type PointSymbolGraphicModifiers, type PointSymbolRender, type PointSymbolSize, type PointSymbolStyle, type PointerCallback, PointerCallbackHub, type PointerEventInfo, type ResolvedPickPresentation, type RotatedScreenBounds, type ScreenBounds, type SessionChangeSource, type SessionHistory, type SessionHistoryState, type SettleReason, type SizeAnchor, type SnapCandidate, type SnapCandidateProvider, type SnapCandidateRequest, type SnapInteraction, type SnapResult, type SnappingOptions, type StyleOptions, TEXT_HALO_COLOR, TOUCH_HIT_TOLERANCE_PX, TacticalDraw, TacticalDrawAbortError, type TacticalDrawAbortReason, TacticalDrawDestroyedError, type TacticalDrawLayerOptions, type TacticalDrawOptions, type TextSizeProps, type TextStyleOptions, type ToPixel, type TransformBox, type TransformChangeEvent, type TransformSession, type TransientChangeEvent, type TransientPhase, bindAbortable, clonePointSymbol, coerceHandleStyle, combineWithHostSignal, effectivePointSymbolPixelSize, evaluatePickCandidates, fillPatternImageDimension, fillPatternImageId, getSizeAnchor, hitTestFeature, hitTestIconFeature, ignoreAbort, isPointSymbol, isTacticalDrawAbortError, makeFillPatternImage, mergeNativeAndCachedPickFeatures, nearestWorldCopyLng, nearestWorldCopyPosition, pickHitTolerance, pickPointerType, pointSymbolFootprint, renderOrderToVisualOrder, resizePointSymbolSize, resolveGroundTextSizePixels, resolvePickPresentation, resolveTextHaloWidth, resolveTextSizePixels, tryControlMeasureIdFromFeature };
|
package/dist/index.mjs
CHANGED
|
@@ -76,14 +76,10 @@ var BaseMapAdapter = class {
|
|
|
76
76
|
else adds.push(f);
|
|
77
77
|
updateById.set(f.id, f);
|
|
78
78
|
}
|
|
79
|
-
const merged = [];
|
|
79
|
+
const merged = [...updateById.values()];
|
|
80
80
|
const removed = [];
|
|
81
|
-
for (const f of existing) if (f.id
|
|
82
|
-
|
|
83
|
-
updateById.delete(f.id);
|
|
84
|
-
} else if (f.id != null && !updateById.has(f.id)) removed.push(f);
|
|
85
|
-
else merged.push(f);
|
|
86
|
-
for (const f of updateById.values()) merged.push(f);
|
|
81
|
+
for (const f of existing) if (f.id == null) merged.push(f);
|
|
82
|
+
else if (!updateById.has(f.id)) removed.push(f);
|
|
87
83
|
this.featureCache.set(layerId, merged);
|
|
88
84
|
this.applyFeatureOps(layerId, {
|
|
89
85
|
adds,
|
|
@@ -700,6 +696,17 @@ function resolveTextSizePixels(props, resolution) {
|
|
|
700
696
|
}
|
|
701
697
|
return resolveGroundTextSizePixels(meters, resolution, props);
|
|
702
698
|
}
|
|
699
|
+
/** Fallback halo color when a `textHalo` feature carries no `textHaloColor`. */
|
|
700
|
+
const TEXT_HALO_COLOR = "#ffffff";
|
|
701
|
+
/**
|
|
702
|
+
* Outward depth (px) of a `textHalo` halo from the glyph edge, scaled to font
|
|
703
|
+
* size. This is the halo's *outward* reach, not a stroke width — a canvas
|
|
704
|
+
* stroke straddles the glyph edge (half in, half out), so a caller drawing
|
|
705
|
+
* one must double this value to get the same visible depth.
|
|
706
|
+
*/
|
|
707
|
+
function resolveTextHaloWidth(textSizePixels) {
|
|
708
|
+
return Math.max(1.5, (textSizePixels ?? 24) / 8);
|
|
709
|
+
}
|
|
703
710
|
//#endregion
|
|
704
711
|
//#region src/gestures/hit-test.ts
|
|
705
712
|
/** Clamped parameter t ∈ [0, 1] of `pixel` projected onto the segment a→b in pixel space, or null for a zero-length segment. */
|
|
@@ -5843,6 +5850,7 @@ function runPointSymbolEdit(opts) {
|
|
|
5843
5850
|
var TacticalDraw = class {
|
|
5844
5851
|
adapter;
|
|
5845
5852
|
graphicsLayer;
|
|
5853
|
+
highlightLayer;
|
|
5846
5854
|
previewLayer;
|
|
5847
5855
|
guideLayer;
|
|
5848
5856
|
handlesLayer;
|
|
@@ -5904,6 +5912,20 @@ var TacticalDraw = class {
|
|
|
5904
5912
|
*/
|
|
5905
5913
|
lastRenderedMeasures = [];
|
|
5906
5914
|
snapGeometryFeatures = [];
|
|
5915
|
+
/**
|
|
5916
|
+
* Ids of graphics currently carrying a passive selection-highlight outline
|
|
5917
|
+
* ({@link setHighlightedGraphics}). Declarative set-replace, no session —
|
|
5918
|
+
* re-projected on every view change and re-evaluated at the end of every
|
|
5919
|
+
* `render()` so outlines track geometry, style, and dropped graphics.
|
|
5920
|
+
*/
|
|
5921
|
+
highlightedIds = [];
|
|
5922
|
+
/**
|
|
5923
|
+
* Whether the highlight layer currently holds any outline. Lets
|
|
5924
|
+
* `renderHighlights` skip a redundant empty write on every view change /
|
|
5925
|
+
* render while no highlights are set, but still emit the one clearing write
|
|
5926
|
+
* that removes outlines when the set transitions to empty.
|
|
5927
|
+
*/
|
|
5928
|
+
hasHighlightOutlines = false;
|
|
5907
5929
|
constructor(adapter, options) {
|
|
5908
5930
|
this.adapter = adapter;
|
|
5909
5931
|
this.graphicsStyle = resolveStyleHints(BUILT_IN_GRAPHICS_STYLE, options?.graphicsStyle, void 0);
|
|
@@ -5915,6 +5937,7 @@ var TacticalDraw = class {
|
|
|
5915
5937
|
const owned = /* @__PURE__ */ new Set();
|
|
5916
5938
|
const provided = options?.layers ?? {};
|
|
5917
5939
|
this.graphicsLayer = resolveSlot(adapter, provided.graphics, owned);
|
|
5940
|
+
this.highlightLayer = resolveSlot(adapter, provided.highlight, owned);
|
|
5918
5941
|
this.previewLayer = resolveSlot(adapter, provided.preview, owned);
|
|
5919
5942
|
this.guideLayer = resolveSlot(adapter, provided.guide, owned);
|
|
5920
5943
|
this.handlesLayer = resolveSlot(adapter, provided.handles, owned);
|
|
@@ -5923,6 +5946,7 @@ var TacticalDraw = class {
|
|
|
5923
5946
|
this.adapter.setLayerStyle(this.graphicsLayer, graphicsLayerStyle);
|
|
5924
5947
|
this.adapter.setLayerStyle(this.previewLayer, graphicsLayerStyle);
|
|
5925
5948
|
this.adapter.setLayerStyle(this.guideLayer, guideToStyleOptions(this.baseGuideStyle()));
|
|
5949
|
+
this.adapter.setLayerStyle(this.highlightLayer, guideToStyleOptions(this.baseHighlightStyle()));
|
|
5926
5950
|
this.adapter.onViewChange(this.handleViewChange);
|
|
5927
5951
|
}
|
|
5928
5952
|
/** Replace snapping configuration without recreating the map adapter or façade. */
|
|
@@ -5970,6 +5994,7 @@ var TacticalDraw = class {
|
|
|
5970
5994
|
*/
|
|
5971
5995
|
handleViewChange = () => {
|
|
5972
5996
|
if (this.destroyed || !this.hasRendered) return;
|
|
5997
|
+
this.renderHighlights();
|
|
5973
5998
|
const excluded = this.excludedInteractionIds();
|
|
5974
5999
|
const visible = excluded.size > 0 ? this.lastRenderedMeasures.filter((m) => !excluded.has(m.id)) : this.lastRenderedMeasures;
|
|
5975
6000
|
const nativeGroundIconScaling = this.adapter.scalesGroundPointSymbolsNatively === true;
|
|
@@ -6067,6 +6092,88 @@ var TacticalDraw = class {
|
|
|
6067
6092
|
if (handle.graphicIds.some((id) => !rendered.has(id))) handle.abort("removed");
|
|
6068
6093
|
});
|
|
6069
6094
|
}
|
|
6095
|
+
this.renderHighlights();
|
|
6096
|
+
}
|
|
6097
|
+
/**
|
|
6098
|
+
* Replace the passive selection-highlight set with `ids`. Declarative and
|
|
6099
|
+
* idempotent (mirrors {@link render}): a non-interactive outline box is drawn
|
|
6100
|
+
* around each still-rendered graphic on the dedicated highlight layer — no
|
|
6101
|
+
* draw / edit / transform session, no promise, no pick subscription. Passing
|
|
6102
|
+
* `[]` clears every highlight. Unknown ids and ids currently lifted into an
|
|
6103
|
+
* active edit / transform session are skipped (the live session's own visuals
|
|
6104
|
+
* supersede the passive outline). Hosts use it for Excalidraw-style
|
|
6105
|
+
* "locked selection" chrome.
|
|
6106
|
+
*/
|
|
6107
|
+
setHighlightedGraphics(ids) {
|
|
6108
|
+
this.assertAlive();
|
|
6109
|
+
this.highlightedIds = ids.slice();
|
|
6110
|
+
this.renderHighlights();
|
|
6111
|
+
}
|
|
6112
|
+
/**
|
|
6113
|
+
* Project the current {@link highlightedIds} into one outline `LineString`
|
|
6114
|
+
* per graphic and reconcile them onto the highlight layer. Idempotent and
|
|
6115
|
+
* id-keyed via `adapter.updateLayerFeatures`, so a set-replace removes stale
|
|
6116
|
+
* outlines and an unchanged set is a cheap no-op write. Skips ids that are
|
|
6117
|
+
* unknown (not in the last render) or lifted into an active session, and
|
|
6118
|
+
* bails gracefully when the adapter cannot invert pixels to coordinates
|
|
6119
|
+
* (`getCoordinateFromPixel` is optional in the MapAdapter ABI).
|
|
6120
|
+
*/
|
|
6121
|
+
renderHighlights() {
|
|
6122
|
+
if (this.destroyed) return;
|
|
6123
|
+
if (this.highlightedIds.length === 0 && !this.hasHighlightOutlines) return;
|
|
6124
|
+
const fromPixel = this.adapter.getCoordinateFromPixel?.bind(this.adapter);
|
|
6125
|
+
if (!fromPixel) return;
|
|
6126
|
+
const resolved = this.resolveInteractionStyle(void 0);
|
|
6127
|
+
const style = resolved.highlightOutline;
|
|
6128
|
+
const padding = resolved.boxPadding ?? 0;
|
|
6129
|
+
const excluded = this.excludedInteractionIds();
|
|
6130
|
+
const bearing = this.adapter.getBearing();
|
|
6131
|
+
const features = [];
|
|
6132
|
+
for (const id of this.highlightedIds) {
|
|
6133
|
+
if (excluded.has(id)) continue;
|
|
6134
|
+
const graphic = this.lastRenderedMeasures.find((g) => g.id === id);
|
|
6135
|
+
if (!graphic) continue;
|
|
6136
|
+
const box = this.highlightBoxForGraphic(graphic, bearing, padding);
|
|
6137
|
+
if (!box) continue;
|
|
6138
|
+
let invertFailed = false;
|
|
6139
|
+
const invert = (pixel) => {
|
|
6140
|
+
const coord = fromPixel(pixel);
|
|
6141
|
+
if (!coord) {
|
|
6142
|
+
invertFailed = true;
|
|
6143
|
+
return [pixel[0], pixel[1]];
|
|
6144
|
+
}
|
|
6145
|
+
return coord;
|
|
6146
|
+
};
|
|
6147
|
+
const feature = boxOutlineFeature(box, invert, `__td_highlight__:${id}`, style);
|
|
6148
|
+
if (!invertFailed) features.push(feature);
|
|
6149
|
+
}
|
|
6150
|
+
this.adapter.updateLayerFeatures(this.highlightLayer, features);
|
|
6151
|
+
this.hasHighlightOutlines = features.length > 0;
|
|
6152
|
+
}
|
|
6153
|
+
/**
|
|
6154
|
+
* Fit the passive-highlight transform box for one graphic: the point-symbol
|
|
6155
|
+
* footprint for symbols, the snug text footprint for Text graphics, and the
|
|
6156
|
+
* projected-geometry bounding box (at the current bearing) for every other
|
|
6157
|
+
* control measure. Returns `null` when nothing projects this frame.
|
|
6158
|
+
*/
|
|
6159
|
+
highlightBoxForGraphic(graphic, bearing, padding) {
|
|
6160
|
+
if (isPointSymbol(graphic)) {
|
|
6161
|
+
if (!this.pointSymbols) return null;
|
|
6162
|
+
return pointSymbolFootprint(graphic, this.pointSymbols.render(graphic), this.adapter)?.box ?? null;
|
|
6163
|
+
}
|
|
6164
|
+
const render = renderMeasureForAdapter(this.adapter, graphic, { graphicsStyle: this.graphicsStyle });
|
|
6165
|
+
if (graphic.kind === "text") return fitTextFootprintFromFeatures(render.features, (lonLat) => this.adapter.getPixelFromCoordinate(lonLat), {
|
|
6166
|
+
resolution: this.adapter.getResolution(),
|
|
6167
|
+
bearing
|
|
6168
|
+
});
|
|
6169
|
+
const positions = [];
|
|
6170
|
+
for (const feature of render.features) if (feature.geometry) collectFeaturePositions(feature.geometry, positions);
|
|
6171
|
+
const pixels = [];
|
|
6172
|
+
for (const position of positions) {
|
|
6173
|
+
const pixel = this.adapter.getPixelFromCoordinate(position);
|
|
6174
|
+
if (pixel) pixels.push(pixel);
|
|
6175
|
+
}
|
|
6176
|
+
return fitTransformBox(pixels, bearing, padding);
|
|
6070
6177
|
}
|
|
6071
6178
|
/**
|
|
6072
6179
|
* Nested-call guard + preempt, shared by `draw` and `edit`. Returns a
|
|
@@ -6121,6 +6228,7 @@ var TacticalDraw = class {
|
|
|
6121
6228
|
clearSlot();
|
|
6122
6229
|
this.clearSnapResult();
|
|
6123
6230
|
if (extraSettle) extraSettle();
|
|
6231
|
+
this.renderHighlights();
|
|
6124
6232
|
};
|
|
6125
6233
|
this.activeInteraction.begin();
|
|
6126
6234
|
try {
|
|
@@ -6132,7 +6240,10 @@ var TacticalDraw = class {
|
|
|
6132
6240
|
}
|
|
6133
6241
|
if (!settledDuringCreate) {
|
|
6134
6242
|
this.activeInteraction.set(handle);
|
|
6135
|
-
if (activate && !handle.settled)
|
|
6243
|
+
if (activate && !handle.settled) {
|
|
6244
|
+
deactivate = activate(handle) ?? null;
|
|
6245
|
+
this.renderHighlights();
|
|
6246
|
+
}
|
|
6136
6247
|
}
|
|
6137
6248
|
handle.promise.then(clearSlot, clearSlot);
|
|
6138
6249
|
return handle.promise;
|
|
@@ -6648,6 +6759,7 @@ var TacticalDraw = class {
|
|
|
6648
6759
|
get layerIds() {
|
|
6649
6760
|
return {
|
|
6650
6761
|
graphics: this.graphicsLayer,
|
|
6762
|
+
highlight: this.highlightLayer,
|
|
6651
6763
|
preview: this.previewLayer,
|
|
6652
6764
|
guide: this.guideLayer,
|
|
6653
6765
|
handles: this.handlesLayer
|
|
@@ -6670,6 +6782,9 @@ var TacticalDraw = class {
|
|
|
6670
6782
|
baseGuideStyle() {
|
|
6671
6783
|
return mergeSlot(BUILT_IN_INTERACTION_STYLE.guide, this.defaultInteractionStyle?.guide);
|
|
6672
6784
|
}
|
|
6785
|
+
baseHighlightStyle() {
|
|
6786
|
+
return mergeSlot(BUILT_IN_INTERACTION_STYLE.highlightOutline, this.defaultInteractionStyle?.highlightOutline);
|
|
6787
|
+
}
|
|
6673
6788
|
/**
|
|
6674
6789
|
* Resolve handle slots (plus `boxOutline`) across built-in → façade →
|
|
6675
6790
|
* per-call with per-slot, per-property shallow merge. Explicit `undefined`
|
|
@@ -6697,6 +6812,8 @@ var TacticalDraw = class {
|
|
|
6697
6812
|
}
|
|
6698
6813
|
const boxOutline = mergeSlot(mergeSlot(BUILT_IN_INTERACTION_STYLE.boxOutline, this.defaultInteractionStyle?.boxOutline), perCall?.boxOutline);
|
|
6699
6814
|
if (boxOutline) out.boxOutline = boxOutline;
|
|
6815
|
+
const highlightOutline = mergeSlot(mergeSlot(BUILT_IN_INTERACTION_STYLE.highlightOutline, this.defaultInteractionStyle?.highlightOutline), perCall?.highlightOutline);
|
|
6816
|
+
if (highlightOutline) out.highlightOutline = highlightOutline;
|
|
6700
6817
|
const boxPadding = perCall && "boxPadding" in perCall ? perCall.boxPadding : this.defaultInteractionStyle && "boxPadding" in this.defaultInteractionStyle ? this.defaultInteractionStyle.boxPadding : void 0;
|
|
6701
6818
|
if (typeof boxPadding === "number") out.boxPadding = boxPadding;
|
|
6702
6819
|
return out;
|
|
@@ -6737,6 +6854,11 @@ const BUILT_IN_INTERACTION_STYLE = {
|
|
|
6737
6854
|
strokeColor: "#4f7cff",
|
|
6738
6855
|
strokeWidth: 1
|
|
6739
6856
|
},
|
|
6857
|
+
highlightOutline: {
|
|
6858
|
+
strokeColor: "#8ea3c9",
|
|
6859
|
+
strokeWidth: 1.5,
|
|
6860
|
+
strokeDash: [4, 4]
|
|
6861
|
+
},
|
|
6740
6862
|
vertexHandle: {
|
|
6741
6863
|
pointRadius: 6,
|
|
6742
6864
|
fillColor: "#ffffff",
|
|
@@ -6976,4 +7098,4 @@ function nearestWorldCopyPosition(position, refLng) {
|
|
|
6976
7098
|
return [nearestWorldCopyLng(position[0], refLng), ...position.slice(1)];
|
|
6977
7099
|
}
|
|
6978
7100
|
//#endregion
|
|
6979
|
-
export { BaseMapAdapter, DEFAULT_LABEL_SIZE_PIXELS, DEFAULT_TEXT_SIZE_MAX_PIXELS, DEFAULT_TEXT_SIZE_MIN_PIXELS, HATCH_PATTERNS, PointSymbolCapabilityRequiredError, PointerCallbackHub, TOUCH_HIT_TOLERANCE_PX, TacticalDraw, TacticalDrawAbortError, TacticalDrawDestroyedError, bindAbortable, clonePointSymbol, coerceHandleStyle, combineWithHostSignal, effectivePointSymbolPixelSize, evaluatePickCandidates, fillPatternImageDimension, fillPatternImageId, getSizeAnchor, hitTestFeature, hitTestIconFeature, ignoreAbort, isPointSymbol, isTacticalDrawAbortError, makeFillPatternImage, mergeNativeAndCachedPickFeatures, nearestWorldCopyLng, nearestWorldCopyPosition, pickHitTolerance, pickPointerType, pointSymbolFootprint, renderOrderToVisualOrder, resizePointSymbolSize, resolveGroundTextSizePixels, resolvePickPresentation, resolveTextSizePixels, tryControlMeasureIdFromFeature };
|
|
7101
|
+
export { BaseMapAdapter, DEFAULT_LABEL_SIZE_PIXELS, DEFAULT_TEXT_SIZE_MAX_PIXELS, DEFAULT_TEXT_SIZE_MIN_PIXELS, HATCH_PATTERNS, PointSymbolCapabilityRequiredError, PointerCallbackHub, TEXT_HALO_COLOR, TOUCH_HIT_TOLERANCE_PX, TacticalDraw, TacticalDrawAbortError, TacticalDrawDestroyedError, bindAbortable, clonePointSymbol, coerceHandleStyle, combineWithHostSignal, effectivePointSymbolPixelSize, evaluatePickCandidates, fillPatternImageDimension, fillPatternImageId, getSizeAnchor, hitTestFeature, hitTestIconFeature, ignoreAbort, isPointSymbol, isTacticalDrawAbortError, makeFillPatternImage, mergeNativeAndCachedPickFeatures, nearestWorldCopyLng, nearestWorldCopyPosition, pickHitTolerance, pickPointerType, pointSymbolFootprint, renderOrderToVisualOrder, resizePointSymbolSize, resolveGroundTextSizePixels, resolvePickPresentation, resolveTextHaloWidth, resolveTextSizePixels, tryControlMeasureIdFromFeature };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orbat-mapper/tactical-draw",
|
|
3
|
-
"version": "0.2.0-alpha.
|
|
3
|
+
"version": "0.2.0-alpha.34",
|
|
4
4
|
"description": "Engine-agnostic draw/edit core for tactical control measures (MIL-STD-2525 / APP-6). The MapAdapter ABI and bring-your-own-engine entry point for the tactical-draw package family.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Orbat Mapper",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"access": "public"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@orbat-mapper/control-measures": "^0.2.0-alpha.
|
|
41
|
+
"@orbat-mapper/control-measures": "^0.2.0-alpha.25"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@arethetypeswrong/core": "^0.18.3",
|