@orbat-mapper/tactical-draw 0.2.0-alpha.26 → 0.2.0-alpha.27

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
@@ -180,6 +180,12 @@ interface MapAdapter {
180
180
  * silently ignored.
181
181
  */
182
182
  removeFeatures(layerId: LayerId, ids: readonly string[]): void;
183
+ /**
184
+ * Defer an upcoming removal from `layerId` until the most recent update to
185
+ * `updatedLayerId` has completed in the engine. Optional because synchronous
186
+ * adapters already make cross-layer write order visible atomically.
187
+ */
188
+ deferLayerRemovalUntilLayerUpdate?(layerId: LayerId, updatedLayerId: LayerId): void;
183
189
  /**
184
190
  * Subscribe to click-style "pick" events for a specific layer. Basemap and
185
191
  * other-layer clicks never fire the handler.
@@ -1518,7 +1524,7 @@ declare class TacticalDraw {
1518
1524
  * Routes every measure through [[renderMeasureForAdapter]] so pixel sizing is
1519
1525
  * resolved against the live map. Callers own any edit-exclusion policy — see
1520
1526
  * `render()` (excludes the edited measure, owned by the preview layer) versus
1521
- * `restoreOriginalToGraphics` (no exclusion, the edit is ending).
1527
+ * `settleToGraphics` (no exclusion, the edit is ending).
1522
1528
  */
1523
1529
  private renderMeasuresToGraphics;
1524
1530
  /**
package/dist/index.mjs CHANGED
@@ -1897,6 +1897,38 @@ function runEditInteraction(spec) {
1897
1897
  attach(interaction) {
1898
1898
  ctx = interaction;
1899
1899
  driver = adapter.createEditPointerDriver();
1900
+ let beforeTeardownRan = false;
1901
+ const runBeforeTeardown = (settlement) => {
1902
+ if (beforeTeardownRan) return;
1903
+ beforeTeardownRan = true;
1904
+ try {
1905
+ spec.onBeforeTeardown?.(settlement);
1906
+ } catch {}
1907
+ };
1908
+ const controllerContext = {
1909
+ get settled() {
1910
+ return interaction.settled;
1911
+ },
1912
+ commit(produce) {
1913
+ return interaction.commit(() => {
1914
+ const value = produce();
1915
+ runBeforeTeardown({
1916
+ status: "committed",
1917
+ value
1918
+ });
1919
+ return value;
1920
+ });
1921
+ },
1922
+ fail(error) {
1923
+ if (!interaction.settled) runBeforeTeardown({
1924
+ status: "rejected",
1925
+ error
1926
+ });
1927
+ interaction.fail(error);
1928
+ },
1929
+ writeLayer: interaction.writeLayer,
1930
+ onTeardown: interaction.onTeardown
1931
+ };
1900
1932
  const unsubDown = driver.onPointerDown(spec.onDown);
1901
1933
  const unsubMove = driver.onPointerMove(spec.onMove);
1902
1934
  const unsubUp = driver.onPointerUp(spec.onUp);
@@ -1905,6 +1937,10 @@ function runEditInteraction(spec) {
1905
1937
  const unsubGraphics = adapter.onPick(graphicsLayer, onGraphicsPick);
1906
1938
  adapter.on("click", onClick);
1907
1939
  interaction.onTeardown(() => {
1940
+ runBeforeTeardown({
1941
+ status: "rejected",
1942
+ error: void 0
1943
+ });
1908
1944
  adapter.off("click", onClick);
1909
1945
  unsubPreview();
1910
1946
  unsubHandles();
@@ -1915,7 +1951,7 @@ function runEditInteraction(spec) {
1915
1951
  spec.extraTeardown?.();
1916
1952
  driver.dispose();
1917
1953
  }, spec.onAbort);
1918
- spec.attach(interaction, driver, shell);
1954
+ spec.attach(controllerContext, driver, shell);
1919
1955
  }
1920
1956
  });
1921
1957
  }
@@ -2983,7 +3019,7 @@ const ALLOWED_EDIT_MODES = new Set([
2983
3019
  * - close / abort / signal / preempt / destroy paths
2984
3020
  */
2985
3021
  function runEdit(opts) {
2986
- const { adapter, measure, graphicsLayer, previewLayer, guideLayer, handlesLayer, graphicsStyle, signal, closeOnClickAway = true, modes: initialModes, interactionStyle, onSession, handleIdPrefix, guide: guideOpt, restoreOriginalToGraphics, wasOnGraphicsLayer, onSettled, onClickAwayGraphicsPick, snap } = opts;
3022
+ const { adapter, measure, graphicsLayer, previewLayer, guideLayer, handlesLayer, graphicsStyle, signal, closeOnClickAway = true, modes: initialModes, interactionStyle, onSession, handleIdPrefix, guide: guideOpt, settleToGraphics, wasOnGraphicsLayer, onSettled, onClickAwayGraphicsPick, snap } = opts;
2987
3023
  const renderOpts = graphicsStyle ? { graphicsStyle } : void 0;
2988
3024
  let sizeAnchor = opts.sizeAnchor ?? "ground";
2989
3025
  const metadata = getControlMeasureMetadata(measure.kind);
@@ -3858,6 +3894,9 @@ function runEdit(opts) {
3858
3894
  previewLayer,
3859
3895
  handlesLayer,
3860
3896
  closeOnClickAway,
3897
+ onBeforeTeardown: (settlement) => {
3898
+ settleToGraphics(settlement.status === "committed" ? settlement.value.graphic : void 0);
3899
+ },
3861
3900
  onViewChange: rescaleOnViewChange,
3862
3901
  onDown: onDriverDown,
3863
3902
  onMove: onDriverMove,
@@ -3879,13 +3918,15 @@ function runEdit(opts) {
3879
3918
  dragInFlight = false;
3880
3919
  }
3881
3920
  },
3882
- onAbort: wasOnGraphicsLayer ? restoreOriginalToGraphics : void 0,
3883
3921
  attach(interaction, createdDriver, createdShell) {
3884
3922
  ctx = interaction;
3885
3923
  driver = createdDriver;
3886
3924
  shell = createdShell;
3887
- liftOriginalFromGraphics();
3888
3925
  renderPreview();
3926
+ if (wasOnGraphicsLayer) {
3927
+ adapter.deferLayerRemovalUntilLayerUpdate?.(graphicsLayer, previewLayer);
3928
+ liftOriginalFromGraphics();
3929
+ }
3889
3930
  renderHandlesLayer();
3890
3931
  recomputeDirty();
3891
3932
  if (onSession) onSession(session);
@@ -4878,7 +4919,7 @@ function symbolBox(adapter, capability, symbol, padding = 0) {
4878
4919
  };
4879
4920
  }
4880
4921
  function runPointSymbolEdit(opts) {
4881
- const { adapter, capability, symbol, graphicsLayer, previewLayer, handlesLayer, signal, closeOnClickAway = true, interactionStyle, onSession, handleIdPrefix, restoreOriginalToGraphics, wasOnGraphicsLayer, onSettled, onClickAwayGraphicsPick, snap } = opts;
4922
+ const { adapter, capability, symbol, graphicsLayer, previewLayer, handlesLayer, signal, closeOnClickAway = true, interactionStyle, onSession, handleIdPrefix, settleToGraphics, wasOnGraphicsLayer, onSettled, onClickAwayGraphicsPick, snap } = opts;
4882
4923
  let modes = normalisePointModes(opts.modes);
4883
4924
  let working = clonePointSymbol(symbol);
4884
4925
  let dirty = false;
@@ -5063,6 +5104,9 @@ function runPointSymbolEdit(opts) {
5063
5104
  handlesLayer,
5064
5105
  closeOnClickAway,
5065
5106
  onViewChange: render,
5107
+ onBeforeTeardown: (settlement) => {
5108
+ settleToGraphics(settlement.status === "committed" ? settlement.value.graphic : void 0);
5109
+ },
5066
5110
  onDown,
5067
5111
  onMove,
5068
5112
  onUp,
@@ -5075,15 +5119,15 @@ function runPointSymbolEdit(opts) {
5075
5119
  facadeOnSettled: onSettled,
5076
5120
  emitSettle: (settlement) => settleFromInteraction(settlement, commitEmitter, settleEmitter),
5077
5121
  extraTeardown: () => adapter.reconcileLayerIconResources(previewLayer, []),
5078
- onAbort: () => {
5079
- if (wasOnGraphicsLayer) restoreOriginalToGraphics();
5080
- },
5081
5122
  attach(interaction, createdDriver, createdShell) {
5082
5123
  ctx = interaction;
5083
5124
  driver = createdDriver;
5084
5125
  shell = createdShell;
5085
- if (wasOnGraphicsLayer) adapter.removeFeatures(graphicsLayer, [`${symbol.id}:point-symbol`]);
5086
5126
  render();
5127
+ if (wasOnGraphicsLayer) {
5128
+ adapter.deferLayerRemovalUntilLayerUpdate?.(graphicsLayer, previewLayer);
5129
+ adapter.removeFeatures(graphicsLayer, [`${symbol.id}:point-symbol`]);
5130
+ }
5087
5131
  onSession?.(session);
5088
5132
  }
5089
5133
  });
@@ -5205,7 +5249,7 @@ var TacticalDraw = class {
5205
5249
  * Routes every measure through [[renderMeasureForAdapter]] so pixel sizing is
5206
5250
  * resolved against the live map. Callers own any edit-exclusion policy — see
5207
5251
  * `render()` (excludes the edited measure, owned by the preview layer) versus
5208
- * `restoreOriginalToGraphics` (no exclusion, the edit is ending).
5252
+ * `settleToGraphics` (no exclusion, the edit is ending).
5209
5253
  */
5210
5254
  renderMeasuresToGraphics(graphics) {
5211
5255
  const features = [];
@@ -5501,7 +5545,12 @@ var TacticalDraw = class {
5501
5545
  const preempt = this.guardAndPreempt();
5502
5546
  if (preempt) return preempt;
5503
5547
  const wasOnGraphicsLayer = this.lastRenderedMeasures.some((m) => m.id === measure.id);
5504
- const restoreOriginalToGraphics = () => this.renderMeasuresToGraphics(this.lastRenderedMeasures);
5548
+ const settleToGraphics = (settledSymbol) => {
5549
+ if (!wasOnGraphicsLayer) return;
5550
+ const graphics = settledSymbol ? this.lastRenderedMeasures.map((graphic) => graphic.id === measure.id ? settledSymbol : graphic) : this.lastRenderedMeasures;
5551
+ this.renderMeasuresToGraphics(graphics);
5552
+ this.adapter.deferLayerRemovalUntilLayerUpdate?.(this.previewLayer, this.graphicsLayer);
5553
+ };
5505
5554
  const handleIdPrefix = `__td_edit_${++this.editCounter}__`;
5506
5555
  return this.runWithSlot((onSettled) => runPointSymbolEdit({
5507
5556
  adapter: this.adapter,
@@ -5516,7 +5565,7 @@ var TacticalDraw = class {
5516
5565
  interactionStyle: this.resolveInteractionStyle(options?.interactionStyle),
5517
5566
  onSession: options?.onSession,
5518
5567
  handleIdPrefix,
5519
- restoreOriginalToGraphics,
5568
+ settleToGraphics,
5520
5569
  wasOnGraphicsLayer,
5521
5570
  onSettled,
5522
5571
  onClickAwayGraphicsPick: (event) => this.dispatchHostPick(this.graphicsLayer, event),
@@ -5532,8 +5581,11 @@ var TacticalDraw = class {
5532
5581
  const preempt = this.guardAndPreempt();
5533
5582
  if (preempt) return preempt;
5534
5583
  const wasOnGraphicsLayer = this.lastRenderedMeasures.some((m) => m.id === measure.id);
5535
- const restoreOriginalToGraphics = () => {
5536
- this.renderMeasuresToGraphics(this.lastRenderedMeasures);
5584
+ const settleToGraphics = (settledMeasure) => {
5585
+ if (!wasOnGraphicsLayer) return;
5586
+ const graphics = settledMeasure ? this.lastRenderedMeasures.map((graphic) => graphic.id === measure.id ? settledMeasure : graphic) : this.lastRenderedMeasures;
5587
+ this.renderMeasuresToGraphics(graphics);
5588
+ this.adapter.deferLayerRemovalUntilLayerUpdate?.(this.previewLayer, this.graphicsLayer);
5537
5589
  };
5538
5590
  const handleIdPrefix = `__td_edit_${++this.editCounter}__`;
5539
5591
  const mergedInteractionStyle = this.resolveInteractionStyle(options?.interactionStyle);
@@ -5557,7 +5609,7 @@ var TacticalDraw = class {
5557
5609
  handleIdPrefix,
5558
5610
  guide: options?.guide,
5559
5611
  sizeAnchor: options?.sizeAnchor,
5560
- restoreOriginalToGraphics,
5612
+ settleToGraphics,
5561
5613
  wasOnGraphicsLayer,
5562
5614
  onSettled,
5563
5615
  onClickAwayGraphicsPick,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orbat-mapper/tactical-draw",
3
- "version": "0.2.0-alpha.26",
3
+ "version": "0.2.0-alpha.27",
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",