@orbat-mapper/tactical-draw 0.9.2 → 0.10.1

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
@@ -1,4 +1,4 @@
1
- import { A as PinOverridePatch, B as resolvePinPresentation, C as makeFillPatternImage, D as PointerEventInfo, E as PointerCallback, F as PinPresentationStatus, I as PinPresentationStatusEvent, L as PinPresentationStore, M as PinPresentationCapability, N as PinPresentationPatch, O as DEFAULT_PIN_PRESENTATION, P as PinPresentationState, R as PinRender, S as isMinePattern, T as EditPointerDriver, _ as HatchPattern, a as LayerId, b as fillPatternImageDimension, c as MapEvent, d as PickHandler, f as PickOptions, g as HATCH_PATTERNS, h as TextStyleOptions, i as ImageOverlayResource, j as PinPresentation, k as PinFootprint, l as MapEventHandler, m as StyleOptions, n as HandleStyle, o as MapAdapter, p as PixelCoordinate, r as IconResource, s as MapCursor, t as FeatureGroup, u as PickEvent, v as MINE_PATTERN_TILE_SIZE, w as parseColor, x as fillPatternImageId, y as Rgba, z as isValidPinNumber } from "./map-adapter-DY-iZRcb.mjs";
1
+ import { A as PinOverridePatch, B as resolvePinPresentation, C as makeFillPatternImage, D as PointerEventInfo, E as PointerCallback, F as PinPresentationStatus, I as PinPresentationStatusEvent, L as PinPresentationStore, M as PinPresentationCapability, N as PinPresentationPatch, O as DEFAULT_PIN_PRESENTATION, P as PinPresentationState, R as PinRender, S as isMinePattern, T as EditPointerDriver, _ as HatchPattern, a as LayerId, b as fillPatternImageDimension, c as MapEvent, d as PickHandler, f as PickOptions, g as HATCH_PATTERNS, h as TextStyleOptions, i as ImageOverlayResource, j as PinPresentation, k as PinFootprint, l as MapEventHandler, m as StyleOptions, n as HandleStyle, o as MapAdapter, p as PixelCoordinate, r as IconResource, s as MapCursor, t as FeatureGroup, u as PickEvent, v as MINE_PATTERN_TILE_SIZE, w as parseColor, x as fillPatternImageId, y as Rgba, z as isValidPinNumber } from "./map-adapter-2Cmj-tem.mjs";
2
2
  import { AmplifierPlacement, AmplifierPlacements, ControlMeasure, ControlMeasureKind, ControlMeasureRender, ControlMeasureStyle, SymbolColors, TextAmplifiers } from "@orbat-mapper/control-measures";
3
3
  import { Feature, Position } from "geojson";
4
4
 
@@ -1662,6 +1662,12 @@ declare class TacticalDraw {
1662
1662
  private transformCounter;
1663
1663
  /** Snapshot of the host's latest authoritative Graphic membership and order. */
1664
1664
  private lastRenderedMeasures;
1665
+ /**
1666
+ * Id index over {@link lastRenderedMeasures}, rebuilt alongside it so
1667
+ * per-id lookups (e.g. {@link renderHighlights}) are O(1) instead of an
1668
+ * O(document) linear scan on every highlighted id, every frame.
1669
+ */
1670
+ private lastRenderedMeasuresById;
1665
1671
  /** Whether any last-rendered graphic reads the whole camera, not just scale. */
1666
1672
  private lastRenderedHasViewDependent;
1667
1673
  private snapGeometryFeatures;
@@ -1703,6 +1709,15 @@ declare class TacticalDraw {
1703
1709
  * that removes outlines when the set transitions to empty.
1704
1710
  */
1705
1711
  private hasHighlightOutlines;
1712
+ /**
1713
+ * Coalesces bursts of {@link setHighlightedGraphics} (e.g. hover across a
1714
+ * list of rows) into at most one {@link renderHighlights} per animation
1715
+ * frame. Every other caller — `render()`, view-change, session start /
1716
+ * settle — needs highlight state updated synchronously and in step with
1717
+ * its own write, so those call {@link flushHighlights} instead, which
1718
+ * cancels any pending coalesced frame before rendering immediately.
1719
+ */
1720
+ private readonly highlightCoalescer;
1706
1721
  constructor(adapter: MapAdapter, options?: TacticalDrawOptions);
1707
1722
  setPinPresentation(defaults: PinPresentationPatch): void;
1708
1723
  setPinOverride(id: string, patch: PinOverridePatch): void;
@@ -1830,6 +1845,14 @@ declare class TacticalDraw {
1830
1845
  * "locked selection" chrome.
1831
1846
  */
1832
1847
  setHighlightedGraphics(ids: readonly string[]): void;
1848
+ /**
1849
+ * Cancel any pending coalesced highlight frame and re-project highlights
1850
+ * immediately. Used by every caller other than `setHighlightedGraphics`
1851
+ * (`render()`, view-change, session start / settle) so their own
1852
+ * synchronous effects and the highlight layer update together, with no
1853
+ * frame of lag and no stale coalesced call left to fire afterwards.
1854
+ */
1855
+ private flushHighlights;
1833
1856
  /**
1834
1857
  * Project the current {@link highlightedIds} into one outline `LineString`
1835
1858
  * per graphic and reconcile them onto the highlight layer. Idempotent and
package/dist/index.mjs CHANGED
@@ -1091,7 +1091,8 @@ function validateOverride(patch) {
1091
1091
  for (const key of [
1092
1092
  "mode",
1093
1093
  "occlusion",
1094
- "prioritizePins"
1094
+ "prioritizePins",
1095
+ "depthSort"
1095
1096
  ]) if (Object.hasOwn(patch, key)) throw new TypeError(`Pin setting ${key} is view-wide only`);
1096
1097
  }
1097
1098
  const DEFAULT_PIN_PRESENTATION = Object.freeze({
@@ -1102,6 +1103,7 @@ const DEFAULT_PIN_PRESENTATION = Object.freeze({
1102
1103
  sizePixels: 32,
1103
1104
  occlusion: "none",
1104
1105
  prioritizePins: true,
1106
+ depthSort: true,
1105
1107
  stem: Object.freeze({
1106
1108
  color: "#172033",
1107
1109
  width: 2,
@@ -7642,6 +7644,12 @@ var TacticalDraw = class {
7642
7644
  transformCounter = 0;
7643
7645
  /** Snapshot of the host's latest authoritative Graphic membership and order. */
7644
7646
  lastRenderedMeasures = [];
7647
+ /**
7648
+ * Id index over {@link lastRenderedMeasures}, rebuilt alongside it so
7649
+ * per-id lookups (e.g. {@link renderHighlights}) are O(1) instead of an
7650
+ * O(document) linear scan on every highlighted id, every frame.
7651
+ */
7652
+ lastRenderedMeasuresById = /* @__PURE__ */ new Map();
7645
7653
  /** Whether any last-rendered graphic reads the whole camera, not just scale. */
7646
7654
  lastRenderedHasViewDependent = false;
7647
7655
  snapGeometryFeatures = [];
@@ -7683,6 +7691,15 @@ var TacticalDraw = class {
7683
7691
  * that removes outlines when the set transitions to empty.
7684
7692
  */
7685
7693
  hasHighlightOutlines = false;
7694
+ /**
7695
+ * Coalesces bursts of {@link setHighlightedGraphics} (e.g. hover across a
7696
+ * list of rows) into at most one {@link renderHighlights} per animation
7697
+ * frame. Every other caller — `render()`, view-change, session start /
7698
+ * settle — needs highlight state updated synchronously and in step with
7699
+ * its own write, so those call {@link flushHighlights} instead, which
7700
+ * cancels any pending coalesced frame before rendering immediately.
7701
+ */
7702
+ highlightCoalescer = createFrameCoalescer(() => this.renderHighlights());
7686
7703
  constructor(adapter, options) {
7687
7704
  this.adapter = adapter;
7688
7705
  this.offsetDragModifier = options?.offsetDragModifier ?? false;
@@ -7715,7 +7732,7 @@ var TacticalDraw = class {
7715
7732
  this.invalidateGraphicGroupCache(false);
7716
7733
  this.refreshActiveEditPresentation();
7717
7734
  this.reconcileGraphicsStack();
7718
- this.renderHighlights();
7735
+ this.flushHighlights();
7719
7736
  });
7720
7737
  }
7721
7738
  setPinPresentation(defaults) {
@@ -7949,7 +7966,7 @@ var TacticalDraw = class {
7949
7966
  const resolutionChanged = !Object.is(resolution, this.lastViewResolution);
7950
7967
  this.lastViewResolution = resolution;
7951
7968
  if (!this.hasRendered) return;
7952
- this.renderHighlights();
7969
+ this.flushHighlights();
7953
7970
  const nativePointSymbolScaling = this.adapter.scalesGroundPointSymbolsNatively === true;
7954
7971
  if (!resolutionChanged && !this.lastRenderedHasViewDependent) return;
7955
7972
  const excluded = this.graphicsStackExcludedIds();
@@ -8038,6 +8055,7 @@ var TacticalDraw = class {
8038
8055
  }
8039
8056
  }
8040
8057
  this.lastRenderedMeasures = measures.slice();
8058
+ this.lastRenderedMeasuresById = new Map(this.lastRenderedMeasures.map((g) => [g.id, g]));
8041
8059
  this.lastRenderedHasViewDependent = this.lastRenderedMeasures.some((g) => !this.renderGraphicGroup(g).pin && graphicViewDependence(g, this.adapter.scalesGroundPointSymbolsNatively === true) === "view");
8042
8060
  this.graphicStack.acknowledgeHostRender(measures);
8043
8061
  if (activeEdit && "syncHostMembership" in activeEdit && measures.some((graphic) => graphic.id === activeEdit.measureId && graphic.kind === activeEdit.graphicKind)) activeEdit.syncHostMembership(true);
@@ -8056,7 +8074,7 @@ var TacticalDraw = class {
8056
8074
  });
8057
8075
  else if (this.activeInteraction.isStarting) this.activeInteraction.cancel("removed");
8058
8076
  }
8059
- this.renderHighlights();
8077
+ this.flushHighlights();
8060
8078
  }
8061
8079
  /**
8062
8080
  * Replace the passive selection-highlight set with `ids`. Declarative and
@@ -8071,6 +8089,17 @@ var TacticalDraw = class {
8071
8089
  setHighlightedGraphics(ids) {
8072
8090
  this.assertAlive();
8073
8091
  this.highlightedIds = ids.slice();
8092
+ this.highlightCoalescer.schedule();
8093
+ }
8094
+ /**
8095
+ * Cancel any pending coalesced highlight frame and re-project highlights
8096
+ * immediately. Used by every caller other than `setHighlightedGraphics`
8097
+ * (`render()`, view-change, session start / settle) so their own
8098
+ * synchronous effects and the highlight layer update together, with no
8099
+ * frame of lag and no stale coalesced call left to fire afterwards.
8100
+ */
8101
+ flushHighlights() {
8102
+ this.highlightCoalescer.cancel();
8074
8103
  this.renderHighlights();
8075
8104
  }
8076
8105
  /**
@@ -8096,7 +8125,7 @@ var TacticalDraw = class {
8096
8125
  const pinGroups = [];
8097
8126
  for (const id of this.highlightedIds) {
8098
8127
  if (excluded.has(id)) continue;
8099
- const graphic = this.lastRenderedMeasures.find((g) => g.id === id);
8128
+ const graphic = this.lastRenderedMeasuresById.get(id);
8100
8129
  if (!graphic) continue;
8101
8130
  if (isPointSymbol(graphic)) {
8102
8131
  const pin = this.renderGraphicGroup(graphic).pin;
@@ -8149,16 +8178,13 @@ var TacticalDraw = class {
8149
8178
  if (!this.images) return null;
8150
8179
  return imageFootprint(graphic, renderImageForAdapter(this.adapter, this.images, graphic).render, this.adapter)?.box ?? null;
8151
8180
  }
8152
- const render = renderMeasureForAdapter(this.adapter, graphic, {
8153
- graphicsStyle: this.graphicsStyle,
8154
- symbolColors: this.symbolColors
8155
- });
8156
- if (graphic.kind === "text") return fitTextFootprintFromFeatures(render.features, (lonLat) => this.adapter.getPixelFromCoordinate(lonLat), {
8181
+ const features = this.renderGraphicGroup(graphic).features;
8182
+ if (graphic.kind === "text") return fitTextFootprintFromFeatures(features, (lonLat) => this.adapter.getPixelFromCoordinate(lonLat), {
8157
8183
  resolution: this.adapter.getResolution(),
8158
8184
  bearing
8159
8185
  });
8160
8186
  const positions = [];
8161
- for (const feature of render.features) if (feature.geometry) collectFeaturePositions(feature.geometry, positions);
8187
+ for (const feature of features) if (feature.geometry) collectFeaturePositions(feature.geometry, positions);
8162
8188
  const pixels = [];
8163
8189
  for (const position of positions) {
8164
8190
  const pixel = this.adapter.getPixelFromCoordinate(position);
@@ -8219,7 +8245,7 @@ var TacticalDraw = class {
8219
8245
  clearSlot();
8220
8246
  this.clearSnapResult();
8221
8247
  if (extraSettle) extraSettle();
8222
- this.renderHighlights();
8248
+ this.flushHighlights();
8223
8249
  };
8224
8250
  this.activeInteraction.begin();
8225
8251
  try {
@@ -8233,7 +8259,7 @@ var TacticalDraw = class {
8233
8259
  this.activeInteraction.set(handle);
8234
8260
  if (activate && !handle.settled) {
8235
8261
  deactivate = activate(handle) ?? null;
8236
- this.renderHighlights();
8262
+ this.flushHighlights();
8237
8263
  }
8238
8264
  }
8239
8265
  handle.promise.then(clearSlot, clearSlot);
@@ -8780,6 +8806,7 @@ var TacticalDraw = class {
8780
8806
  this.unsubscribePins();
8781
8807
  if (this.destroyed) return;
8782
8808
  this.destroyed = true;
8809
+ this.highlightCoalescer.cancel();
8783
8810
  if (this.activeInteraction.isActive) {
8784
8811
  this.activeEdit = null;
8785
8812
  this.activeTransform = null;
@@ -11,6 +11,8 @@ interface PinPresentation {
11
11
  sizePixels: number;
12
12
  occlusion: "none" | "terrain";
13
13
  prioritizePins: boolean;
14
+ /** Paint whole pins far to near by camera depth; ties retain layer order. Default on. */
15
+ depthSort: boolean;
14
16
  stem: {
15
17
  color: string;
16
18
  width: number;
@@ -49,7 +51,7 @@ type PinPresentationPatch = Partial<Omit<PinPresentation, "stem" | "dot" | "halo
49
51
  plate?: Partial<PinPresentation["plate"]>;
50
52
  };
51
53
  /** Per-unit appearance and dimensions; spatial rules belong to the view. */
52
- type PinOverridePatch = Omit<PinPresentationPatch, "mode" | "occlusion" | "prioritizePins">;
54
+ type PinOverridePatch = Omit<PinPresentationPatch, "mode" | "occlusion" | "prioritizePins" | "depthSort">;
53
55
  interface PinPresentationState {
54
56
  defaults: PinPresentationPatch;
55
57
  overrides: Record<string, PinOverridePatch>;
@@ -1,4 +1,4 @@
1
- import { a as LayerId, o as MapAdapter, p as PixelCoordinate } from "./map-adapter-DY-iZRcb.mjs";
1
+ import { a as LayerId, o as MapAdapter, p as PixelCoordinate } from "./map-adapter-2Cmj-tem.mjs";
2
2
  import { Feature } from "geojson";
3
3
 
4
4
  //#region src/testing/feature-group-conformance.d.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orbat-mapper/tactical-draw",
3
- "version": "0.9.2",
3
+ "version": "0.10.1",
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",