@orbat-mapper/tactical-draw 0.2.0-alpha.37 → 0.2.0-alpha.38
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 { S as PointerEventInfo, _ as fillPatternImageDimension, a as MapAdapter, b as EditPointerDriver, c as MapEventHandler, d as PickOptions, f as PixelCoordinate, g as HatchPattern, h as HATCH_PATTERNS, i as LayerId, l as PickEvent, m as TextStyleOptions, n as HandleStyle, o as MapCursor, p as StyleOptions, r as IconResource, s as MapEvent, t as FeatureGroup, u as PickHandler, v as fillPatternImageId, x as PointerCallback, y as makeFillPatternImage } from "./map-adapter-
|
|
1
|
+
import { S as PointerEventInfo, _ as fillPatternImageDimension, a as MapAdapter, b as EditPointerDriver, c as MapEventHandler, d as PickOptions, f as PixelCoordinate, g as HatchPattern, h as HATCH_PATTERNS, i as LayerId, l as PickEvent, m as TextStyleOptions, n as HandleStyle, o as MapCursor, p as StyleOptions, r as IconResource, s as MapEvent, t as FeatureGroup, u as PickHandler, v as fillPatternImageId, x as PointerCallback, y as makeFillPatternImage } from "./map-adapter-BIlrWk6y.mjs";
|
|
2
2
|
import { AmplifierPlacement, AmplifierPlacements, ControlMeasure, ControlMeasureKind, ControlMeasureRender, ControlMeasureStyle, TextAmplifiers } from "@orbat-mapper/control-measures";
|
|
3
3
|
import { Feature, Position } from "geojson";
|
|
4
4
|
|
|
@@ -400,6 +400,15 @@ interface PointSymbolCapability {
|
|
|
400
400
|
readonly defaultRotation: number;
|
|
401
401
|
render(symbol: PointSymbol): PointSymbolRender;
|
|
402
402
|
invalidate(): void;
|
|
403
|
+
/**
|
|
404
|
+
* Monotonic counter that increments on each `invalidate()` call. Consumers
|
|
405
|
+
* (e.g. a render cache keyed on graphic snapshot identity) may read this to
|
|
406
|
+
* detect that previously rendered point-symbol output is stale even though
|
|
407
|
+
* the graphic snapshot itself is unchanged. Optional — when absent,
|
|
408
|
+
* invalidation cannot be detected by cache layers, so they must not attempt
|
|
409
|
+
* to key on it.
|
|
410
|
+
*/
|
|
411
|
+
readonly generation?: number;
|
|
403
412
|
}
|
|
404
413
|
declare function isPointSymbol(graphic: unknown): graphic is PointSymbol;
|
|
405
414
|
declare function clonePointSymbol(symbol: PointSymbol): PointSymbol;
|
|
@@ -1443,6 +1452,23 @@ declare class TacticalDraw {
|
|
|
1443
1452
|
/** Snapshot of the host's latest authoritative Graphic membership and order. */
|
|
1444
1453
|
private lastRenderedMeasures;
|
|
1445
1454
|
private snapGeometryFeatures;
|
|
1455
|
+
/**
|
|
1456
|
+
* Rendered graphics-layer groups, keyed on Graphic snapshot identity. See
|
|
1457
|
+
* {@link renderGraphicGroup} for the invalidation contract; swapped for a
|
|
1458
|
+
* fresh map (not cleared entry-by-entry) so stale entries are collectable.
|
|
1459
|
+
*/
|
|
1460
|
+
private graphicGroupCache;
|
|
1461
|
+
/**
|
|
1462
|
+
* Last {@link PointSymbolCapability.generation} observed from `pointSymbols`,
|
|
1463
|
+
* initialised from the capability at construction. Compared at the top of
|
|
1464
|
+
* every {@link reconcileGraphicsStack} so a host calling `invalidate()` after
|
|
1465
|
+
* changing renderer-global config (e.g. milsymbol options) drops the render
|
|
1466
|
+
* cache even though the Graphic snapshots handed to `render()` are
|
|
1467
|
+
* unchanged — otherwise the cache would keep serving pre-invalidation
|
|
1468
|
+
* output and the capability would never be re-consulted. `undefined` when
|
|
1469
|
+
* there is no capability, or the capability doesn't expose `generation`.
|
|
1470
|
+
*/
|
|
1471
|
+
private lastSeenPointSymbolGeneration;
|
|
1446
1472
|
/**
|
|
1447
1473
|
* Ids of graphics currently carrying a passive selection-highlight outline
|
|
1448
1474
|
* ({@link setHighlightedGraphics}). Declarative set-replace, no session —
|
|
@@ -1462,8 +1488,34 @@ declare class TacticalDraw {
|
|
|
1462
1488
|
setSnappingOptions(options?: SnappingOptions): void;
|
|
1463
1489
|
/** Clear transient host affordances when snapping or its interaction ends. */
|
|
1464
1490
|
private clearSnapResult;
|
|
1465
|
-
/**
|
|
1491
|
+
/**
|
|
1492
|
+
* Render one atomic Graphic group for the engine-neutral graphics stack,
|
|
1493
|
+
* reusing the previous render when nothing that feeds it has changed.
|
|
1494
|
+
*
|
|
1495
|
+
* The cache is keyed on the Graphic snapshot's object identity: hosts hand
|
|
1496
|
+
* `render()` a fresh object whenever a graphic's data changes, so a changed
|
|
1497
|
+
* graphic misses by construction. (A host that mutates a Graphic in place and
|
|
1498
|
+
* re-renders the same reference is outside the contract — `render()` has
|
|
1499
|
+
* always been documented as taking snapshots.) Rendered output also depends
|
|
1500
|
+
* on live view state and on the resolved graphics style; the style is fixed
|
|
1501
|
+
* at construction, and every view change drops the whole cache. Crucially,
|
|
1502
|
+
* the view cannot change during a box drag (drag-pan is disabled), so every
|
|
1503
|
+
* graphic outside the selection stays a cache hit for the whole gesture and
|
|
1504
|
+
* per-frame reconcile cost becomes O(selection) rather than O(document).
|
|
1505
|
+
*/
|
|
1466
1506
|
private renderGraphicGroup;
|
|
1507
|
+
/**
|
|
1508
|
+
* Render a Graphic group from scratch and stamp it with a fresh revision,
|
|
1509
|
+
* drawn from the module-scoped {@link nextGraphicGroupRevision} counter (see
|
|
1510
|
+
* its doc comment for why it isn't a per-instance field).
|
|
1511
|
+
*/
|
|
1512
|
+
private buildGraphicGroup;
|
|
1513
|
+
/**
|
|
1514
|
+
* Drop every cached render. Called whenever an input shared by all graphic
|
|
1515
|
+
* renders changes — the live view state, or (via {@link reconcileGraphicsStack})
|
|
1516
|
+
* the point-symbol capability's `generation`.
|
|
1517
|
+
*/
|
|
1518
|
+
private invalidateGraphicGroupCache;
|
|
1467
1519
|
/** Submit one complete grouped desired state for the host stack plus overrides. */
|
|
1468
1520
|
private reconcileGraphicsStack;
|
|
1469
1521
|
/** Atomically publish several live renders into their host-owned Graphic slots. */
|
package/dist/index.mjs
CHANGED
|
@@ -486,7 +486,8 @@ var BaseMapAdapter = class {
|
|
|
486
486
|
const nextGroups = groups.map((group) => ({
|
|
487
487
|
id: group.id,
|
|
488
488
|
features: [...group.features],
|
|
489
|
-
iconResources: [...group.iconResources]
|
|
489
|
+
iconResources: [...group.iconResources],
|
|
490
|
+
revision: group.revision
|
|
490
491
|
}));
|
|
491
492
|
const nextFeatures = nextGroups.flatMap((group) => group.features);
|
|
492
493
|
const nextResourcesById = /* @__PURE__ */ new Map();
|
|
@@ -4768,6 +4769,42 @@ function boundProperty(key, value) {
|
|
|
4768
4769
|
return value !== void 0 && Number.isFinite(value) && value > 0 ? { [key]: value } : {};
|
|
4769
4770
|
}
|
|
4770
4771
|
//#endregion
|
|
4772
|
+
//#region src/tactical-draw/frame-coalescer.ts
|
|
4773
|
+
/**
|
|
4774
|
+
* @param run Callback invoked with the latest scheduled args, at most once per frame.
|
|
4775
|
+
*/
|
|
4776
|
+
function createFrameCoalescer(run) {
|
|
4777
|
+
const raf = globalThis.requestAnimationFrame?.bind(globalThis);
|
|
4778
|
+
const cancelRaf = globalThis.cancelAnimationFrame?.bind(globalThis);
|
|
4779
|
+
if (!raf || !cancelRaf) return {
|
|
4780
|
+
schedule(...args) {
|
|
4781
|
+
run(...args);
|
|
4782
|
+
},
|
|
4783
|
+
cancel() {}
|
|
4784
|
+
};
|
|
4785
|
+
let handle = null;
|
|
4786
|
+
let latestArgs = null;
|
|
4787
|
+
return {
|
|
4788
|
+
schedule(...args) {
|
|
4789
|
+
latestArgs = args;
|
|
4790
|
+
if (handle !== null) return;
|
|
4791
|
+
handle = raf(() => {
|
|
4792
|
+
handle = null;
|
|
4793
|
+
const pending = latestArgs;
|
|
4794
|
+
latestArgs = null;
|
|
4795
|
+
if (pending) run(...pending);
|
|
4796
|
+
});
|
|
4797
|
+
},
|
|
4798
|
+
cancel() {
|
|
4799
|
+
if (handle !== null) {
|
|
4800
|
+
cancelRaf(handle);
|
|
4801
|
+
handle = null;
|
|
4802
|
+
}
|
|
4803
|
+
latestArgs = null;
|
|
4804
|
+
}
|
|
4805
|
+
};
|
|
4806
|
+
}
|
|
4807
|
+
//#endregion
|
|
4771
4808
|
//#region src/tactical-draw/transform-controller.ts
|
|
4772
4809
|
/** No point symbol may scale below this factor of its own size — mirrors the box-drag floor. */
|
|
4773
4810
|
const MIN_POINT_SYMBOL_SIZE = .1;
|
|
@@ -5479,12 +5516,14 @@ function runGroupTransform(opts) {
|
|
|
5479
5516
|
renderHandlesLayer();
|
|
5480
5517
|
recomputeDirty();
|
|
5481
5518
|
}
|
|
5519
|
+
const boxDragPaintCoalescer = createFrameCoalescer(paintBoxDragSnapshot);
|
|
5482
5520
|
const onDriverMove = (pixel, info) => {
|
|
5483
5521
|
if (ctx.settled) return;
|
|
5484
|
-
if (activeBoxDrag)
|
|
5522
|
+
if (activeBoxDrag) boxDragPaintCoalescer.schedule(pixel, info?.shiftKey);
|
|
5485
5523
|
};
|
|
5486
5524
|
const onDriverUp = (pixel, info) => {
|
|
5487
5525
|
if (!activeBoxDrag) return;
|
|
5526
|
+
boxDragPaintCoalescer.cancel();
|
|
5488
5527
|
if (ctx.settled) {
|
|
5489
5528
|
activeBoxDrag = null;
|
|
5490
5529
|
dragInFlight = false;
|
|
@@ -5545,6 +5584,7 @@ function runGroupTransform(opts) {
|
|
|
5545
5584
|
if (settlement.status === "rejected") settleEmitter.emit(settleReasonFromError(settlement.error));
|
|
5546
5585
|
},
|
|
5547
5586
|
extraTeardown: () => {
|
|
5587
|
+
boxDragPaintCoalescer.cancel();
|
|
5548
5588
|
if (activeBoxDrag !== null) {
|
|
5549
5589
|
driver.setDragPanEnabled(true);
|
|
5550
5590
|
activeBoxDrag = null;
|
|
@@ -5930,6 +5970,17 @@ var GraphicStackCoordinator = class {
|
|
|
5930
5970
|
//#endregion
|
|
5931
5971
|
//#region src/tactical-draw/tactical-draw.ts
|
|
5932
5972
|
/**
|
|
5973
|
+
* Source of {@link FeatureGroup.revision} stamps. Module-scoped rather than a
|
|
5974
|
+
* per-instance counter: adapters keep per-group bundles on host-supplied layer
|
|
5975
|
+
* handles, and `destroy()` intentionally leaves those layers untouched so a
|
|
5976
|
+
* host can attach a fresh façade to the same adapter/layer for reuse. An
|
|
5977
|
+
* instance counter would restart at 1 on the new façade, so a bundle the
|
|
5978
|
+
* previous façade stored at revision 1 — with different content — would
|
|
5979
|
+
* compare equal and the adapter would wrongly skip the repaint. A counter
|
|
5980
|
+
* shared across every façade in the JS realm guarantees revisions never repeat.
|
|
5981
|
+
*/
|
|
5982
|
+
let nextGraphicGroupRevision = 0;
|
|
5983
|
+
/**
|
|
5933
5984
|
* `TacticalDraw` façade — slice #6 (issue #45).
|
|
5934
5985
|
*
|
|
5935
5986
|
* Provides the constructor + lifecycle + `render(measures)` reconcile path.
|
|
@@ -5998,6 +6049,23 @@ var TacticalDraw = class {
|
|
|
5998
6049
|
lastRenderedMeasures = [];
|
|
5999
6050
|
snapGeometryFeatures = [];
|
|
6000
6051
|
/**
|
|
6052
|
+
* Rendered graphics-layer groups, keyed on Graphic snapshot identity. See
|
|
6053
|
+
* {@link renderGraphicGroup} for the invalidation contract; swapped for a
|
|
6054
|
+
* fresh map (not cleared entry-by-entry) so stale entries are collectable.
|
|
6055
|
+
*/
|
|
6056
|
+
graphicGroupCache = /* @__PURE__ */ new WeakMap();
|
|
6057
|
+
/**
|
|
6058
|
+
* Last {@link PointSymbolCapability.generation} observed from `pointSymbols`,
|
|
6059
|
+
* initialised from the capability at construction. Compared at the top of
|
|
6060
|
+
* every {@link reconcileGraphicsStack} so a host calling `invalidate()` after
|
|
6061
|
+
* changing renderer-global config (e.g. milsymbol options) drops the render
|
|
6062
|
+
* cache even though the Graphic snapshots handed to `render()` are
|
|
6063
|
+
* unchanged — otherwise the cache would keep serving pre-invalidation
|
|
6064
|
+
* output and the capability would never be re-consulted. `undefined` when
|
|
6065
|
+
* there is no capability, or the capability doesn't expose `generation`.
|
|
6066
|
+
*/
|
|
6067
|
+
lastSeenPointSymbolGeneration;
|
|
6068
|
+
/**
|
|
6001
6069
|
* Ids of graphics currently carrying a passive selection-highlight outline
|
|
6002
6070
|
* ({@link setHighlightedGraphics}). Declarative set-replace, no session —
|
|
6003
6071
|
* re-projected on every view change and re-evaluated at the end of every
|
|
@@ -6017,6 +6085,7 @@ var TacticalDraw = class {
|
|
|
6017
6085
|
this.defaultInteractionStyle = options?.interactionStyle;
|
|
6018
6086
|
this.generateId = options?.generateId ?? (() => `td-${++this.idCounter}`);
|
|
6019
6087
|
this.pointSymbols = options?.pointSymbols;
|
|
6088
|
+
this.lastSeenPointSymbolGeneration = this.pointSymbols?.generation;
|
|
6020
6089
|
this.snappingOptions = options?.snapping;
|
|
6021
6090
|
this.resolveSnap = createSnapResolver(adapter, () => this.lastRenderedMeasures, () => this.snapGeometryFeatures, () => this.snappingOptions);
|
|
6022
6091
|
const owned = /* @__PURE__ */ new Set();
|
|
@@ -6044,27 +6113,68 @@ var TacticalDraw = class {
|
|
|
6044
6113
|
clearSnapResult() {
|
|
6045
6114
|
this.snappingOptions?.onSnap?.(null);
|
|
6046
6115
|
}
|
|
6047
|
-
/**
|
|
6116
|
+
/**
|
|
6117
|
+
* Render one atomic Graphic group for the engine-neutral graphics stack,
|
|
6118
|
+
* reusing the previous render when nothing that feeds it has changed.
|
|
6119
|
+
*
|
|
6120
|
+
* The cache is keyed on the Graphic snapshot's object identity: hosts hand
|
|
6121
|
+
* `render()` a fresh object whenever a graphic's data changes, so a changed
|
|
6122
|
+
* graphic misses by construction. (A host that mutates a Graphic in place and
|
|
6123
|
+
* re-renders the same reference is outside the contract — `render()` has
|
|
6124
|
+
* always been documented as taking snapshots.) Rendered output also depends
|
|
6125
|
+
* on live view state and on the resolved graphics style; the style is fixed
|
|
6126
|
+
* at construction, and every view change drops the whole cache. Crucially,
|
|
6127
|
+
* the view cannot change during a box drag (drag-pan is disabled), so every
|
|
6128
|
+
* graphic outside the selection stays a cache hit for the whole gesture and
|
|
6129
|
+
* per-frame reconcile cost becomes O(selection) rather than O(document).
|
|
6130
|
+
*/
|
|
6048
6131
|
renderGraphicGroup(graphic) {
|
|
6049
|
-
const
|
|
6132
|
+
const cached = this.graphicGroupCache.get(graphic);
|
|
6133
|
+
if (cached) return cached;
|
|
6134
|
+
const group = this.buildGraphicGroup(graphic);
|
|
6135
|
+
this.graphicGroupCache.set(graphic, group);
|
|
6136
|
+
return group;
|
|
6137
|
+
}
|
|
6138
|
+
/**
|
|
6139
|
+
* Render a Graphic group from scratch and stamp it with a fresh revision,
|
|
6140
|
+
* drawn from the module-scoped {@link nextGraphicGroupRevision} counter (see
|
|
6141
|
+
* its doc comment for why it isn't a per-instance field).
|
|
6142
|
+
*/
|
|
6143
|
+
buildGraphicGroup(graphic) {
|
|
6144
|
+
const revision = ++nextGraphicGroupRevision;
|
|
6050
6145
|
if (isPointSymbol(graphic)) {
|
|
6051
6146
|
if (!this.pointSymbols) throw new PointSymbolCapabilityRequiredError();
|
|
6052
6147
|
const plan = renderPointSymbolForAdapter(this.adapter, this.pointSymbols, graphic);
|
|
6053
6148
|
return {
|
|
6054
6149
|
id: graphic.id,
|
|
6055
6150
|
features: [plan.feature],
|
|
6056
|
-
iconResources: [plan.resource]
|
|
6151
|
+
iconResources: [plan.resource],
|
|
6152
|
+
revision
|
|
6057
6153
|
};
|
|
6058
6154
|
}
|
|
6059
|
-
const render = renderMeasureForAdapter(this.adapter, graphic,
|
|
6155
|
+
const render = renderMeasureForAdapter(this.adapter, graphic, { graphicsStyle: this.graphicsStyle });
|
|
6060
6156
|
return {
|
|
6061
6157
|
id: graphic.id,
|
|
6062
6158
|
features: render.features,
|
|
6063
|
-
iconResources: []
|
|
6159
|
+
iconResources: [],
|
|
6160
|
+
revision
|
|
6064
6161
|
};
|
|
6065
6162
|
}
|
|
6163
|
+
/**
|
|
6164
|
+
* Drop every cached render. Called whenever an input shared by all graphic
|
|
6165
|
+
* renders changes — the live view state, or (via {@link reconcileGraphicsStack})
|
|
6166
|
+
* the point-symbol capability's `generation`.
|
|
6167
|
+
*/
|
|
6168
|
+
invalidateGraphicGroupCache() {
|
|
6169
|
+
this.graphicGroupCache = /* @__PURE__ */ new WeakMap();
|
|
6170
|
+
}
|
|
6066
6171
|
/** Submit one complete grouped desired state for the host stack plus overrides. */
|
|
6067
6172
|
reconcileGraphicsStack(graphics = this.lastRenderedMeasures) {
|
|
6173
|
+
const currentGeneration = this.pointSymbols?.generation;
|
|
6174
|
+
if (currentGeneration !== this.lastSeenPointSymbolGeneration) {
|
|
6175
|
+
this.invalidateGraphicGroupCache();
|
|
6176
|
+
this.lastSeenPointSymbolGeneration = currentGeneration;
|
|
6177
|
+
}
|
|
6068
6178
|
const groups = this.graphicStack.desiredGroups(graphics, (graphic) => this.renderGraphicGroup(graphic));
|
|
6069
6179
|
this.adapter.setLayerFeatureGroups(this.graphicsLayer, groups);
|
|
6070
6180
|
this.snapGeometryFeatures = groups.flatMap((group) => group.features);
|
|
@@ -6093,6 +6203,7 @@ var TacticalDraw = class {
|
|
|
6093
6203
|
*/
|
|
6094
6204
|
handleViewChange = () => {
|
|
6095
6205
|
if (this.destroyed || !this.hasRendered) return;
|
|
6206
|
+
this.invalidateGraphicGroupCache();
|
|
6096
6207
|
this.renderHighlights();
|
|
6097
6208
|
const excluded = this.graphicsStackExcludedIds();
|
|
6098
6209
|
const visible = excluded.size > 0 ? this.lastRenderedMeasures.filter((m) => !excluded.has(m.id)) : this.lastRenderedMeasures;
|
|
@@ -188,6 +188,15 @@ interface FeatureGroup {
|
|
|
188
188
|
id: string;
|
|
189
189
|
features: readonly Feature[];
|
|
190
190
|
iconResources: readonly IconResource[];
|
|
191
|
+
/**
|
|
192
|
+
* Monotonic stamp identifying this group's rendered content, when the
|
|
193
|
+
* producer supplies one. Two groups with the same `id` and the same
|
|
194
|
+
* `revision` carry byte-identical `features` and `iconResources`, so an
|
|
195
|
+
* adapter may skip any diff or upload work for them. A missing `revision`
|
|
196
|
+
* means "unknown" — adapters must fall back to their own comparison and
|
|
197
|
+
* must never treat two absent stamps as equal.
|
|
198
|
+
*/
|
|
199
|
+
revision?: number;
|
|
191
200
|
}
|
|
192
201
|
/**
|
|
193
202
|
* Click-style hit event delivered by {@link MapAdapter.onPick}.
|
package/dist/testing.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as MapAdapter, f as PixelCoordinate, i as LayerId } from "./map-adapter-
|
|
1
|
+
import { a as MapAdapter, f as PixelCoordinate, i as LayerId } from "./map-adapter-BIlrWk6y.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.2.0-alpha.
|
|
3
|
+
"version": "0.2.0-alpha.38",
|
|
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",
|