@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 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 != null && updateById.has(f.id)) {
82
- merged.push(updateById.get(f.id));
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) deactivate = activate(handle) ?? null;
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.32",
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.24"
41
+ "@orbat-mapper/control-measures": "^0.2.0-alpha.25"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@arethetypeswrong/core": "^0.18.3",