@orbat-mapper/tactical-draw 0.2.0-alpha.32 → 0.2.0-alpha.33
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 +60 -0
- package/dist/index.mjs +116 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -877,6 +877,15 @@ interface InteractionStyle {
|
|
|
877
877
|
scaleHandle?: HandleStyle;
|
|
878
878
|
rotateHandle?: HandleStyle;
|
|
879
879
|
boxOutline?: GuideStyle;
|
|
880
|
+
/**
|
|
881
|
+
* Line style for the passive, non-interactive selection highlight rendered
|
|
882
|
+
* by {@link TacticalDraw.setHighlightedGraphics}. A dashed, muted variant of
|
|
883
|
+
* `boxOutline` so a locked/highlighted selection reads as visually distinct
|
|
884
|
+
* from an active edit's oriented transform box. Delivered per-feature via
|
|
885
|
+
* `properties.style` on the highlight outline `LineString`, resolved through
|
|
886
|
+
* the same built-in → façade → per-call slot system as every other field.
|
|
887
|
+
*/
|
|
888
|
+
highlightOutline?: GuideStyle;
|
|
880
889
|
/**
|
|
881
890
|
* Pixel padding added on every side of the fitted transform box
|
|
882
891
|
* (`fitTransformBox`, ADR-0017) so the box doesn't tightly hug the rendered
|
|
@@ -1547,6 +1556,12 @@ type DrawPointSymbolDraft = Pick<PointSymbol, "kind" | "sidc"> & Partial<Omit<Po
|
|
|
1547
1556
|
*/
|
|
1548
1557
|
interface TacticalDrawLayerOptions {
|
|
1549
1558
|
graphics?: LayerId;
|
|
1559
|
+
/**
|
|
1560
|
+
* Passive selection-highlight outlines ({@link TacticalDraw.setHighlightedGraphics}).
|
|
1561
|
+
* Resolved immediately above `graphics` so highlight boxes stack over base
|
|
1562
|
+
* graphics but below the preview / guide / handles interaction chrome.
|
|
1563
|
+
*/
|
|
1564
|
+
highlight?: LayerId;
|
|
1550
1565
|
preview?: LayerId;
|
|
1551
1566
|
guide?: LayerId;
|
|
1552
1567
|
handles?: LayerId;
|
|
@@ -1733,6 +1748,7 @@ interface DrawOptions {
|
|
|
1733
1748
|
declare class TacticalDraw {
|
|
1734
1749
|
private readonly adapter;
|
|
1735
1750
|
private readonly graphicsLayer;
|
|
1751
|
+
private readonly highlightLayer;
|
|
1736
1752
|
private readonly previewLayer;
|
|
1737
1753
|
private readonly guideLayer;
|
|
1738
1754
|
private readonly handlesLayer;
|
|
@@ -1794,6 +1810,20 @@ declare class TacticalDraw {
|
|
|
1794
1810
|
*/
|
|
1795
1811
|
private lastRenderedMeasures;
|
|
1796
1812
|
private snapGeometryFeatures;
|
|
1813
|
+
/**
|
|
1814
|
+
* Ids of graphics currently carrying a passive selection-highlight outline
|
|
1815
|
+
* ({@link setHighlightedGraphics}). Declarative set-replace, no session —
|
|
1816
|
+
* re-projected on every view change and re-evaluated at the end of every
|
|
1817
|
+
* `render()` so outlines track geometry, style, and dropped graphics.
|
|
1818
|
+
*/
|
|
1819
|
+
private highlightedIds;
|
|
1820
|
+
/**
|
|
1821
|
+
* Whether the highlight layer currently holds any outline. Lets
|
|
1822
|
+
* `renderHighlights` skip a redundant empty write on every view change /
|
|
1823
|
+
* render while no highlights are set, but still emit the one clearing write
|
|
1824
|
+
* that removes outlines when the set transitions to empty.
|
|
1825
|
+
*/
|
|
1826
|
+
private hasHighlightOutlines;
|
|
1797
1827
|
constructor(adapter: MapAdapter, options?: TacticalDrawOptions);
|
|
1798
1828
|
/** Replace snapping configuration without recreating the map adapter or façade. */
|
|
1799
1829
|
setSnappingOptions(options?: SnappingOptions): void;
|
|
@@ -1858,6 +1888,34 @@ declare class TacticalDraw {
|
|
|
1858
1888
|
* pure style replay).
|
|
1859
1889
|
*/
|
|
1860
1890
|
render(measures: readonly Graphic[]): void;
|
|
1891
|
+
/**
|
|
1892
|
+
* Replace the passive selection-highlight set with `ids`. Declarative and
|
|
1893
|
+
* idempotent (mirrors {@link render}): a non-interactive outline box is drawn
|
|
1894
|
+
* around each still-rendered graphic on the dedicated highlight layer — no
|
|
1895
|
+
* draw / edit / transform session, no promise, no pick subscription. Passing
|
|
1896
|
+
* `[]` clears every highlight. Unknown ids and ids currently lifted into an
|
|
1897
|
+
* active edit / transform session are skipped (the live session's own visuals
|
|
1898
|
+
* supersede the passive outline). Hosts use it for Excalidraw-style
|
|
1899
|
+
* "locked selection" chrome.
|
|
1900
|
+
*/
|
|
1901
|
+
setHighlightedGraphics(ids: readonly string[]): void;
|
|
1902
|
+
/**
|
|
1903
|
+
* Project the current {@link highlightedIds} into one outline `LineString`
|
|
1904
|
+
* per graphic and reconcile them onto the highlight layer. Idempotent and
|
|
1905
|
+
* id-keyed via `adapter.updateLayerFeatures`, so a set-replace removes stale
|
|
1906
|
+
* outlines and an unchanged set is a cheap no-op write. Skips ids that are
|
|
1907
|
+
* unknown (not in the last render) or lifted into an active session, and
|
|
1908
|
+
* bails gracefully when the adapter cannot invert pixels to coordinates
|
|
1909
|
+
* (`getCoordinateFromPixel` is optional in the MapAdapter ABI).
|
|
1910
|
+
*/
|
|
1911
|
+
private renderHighlights;
|
|
1912
|
+
/**
|
|
1913
|
+
* Fit the passive-highlight transform box for one graphic: the point-symbol
|
|
1914
|
+
* footprint for symbols, the snug text footprint for Text graphics, and the
|
|
1915
|
+
* projected-geometry bounding box (at the current bearing) for every other
|
|
1916
|
+
* control measure. Returns `null` when nothing projects this frame.
|
|
1917
|
+
*/
|
|
1918
|
+
private highlightBoxForGraphic;
|
|
1861
1919
|
/**
|
|
1862
1920
|
* Nested-call guard + preempt, shared by `draw` and `edit`. Returns a
|
|
1863
1921
|
* pre-rejected promise when called re-entrantly from inside another
|
|
@@ -2093,6 +2151,7 @@ declare class TacticalDraw {
|
|
|
2093
2151
|
/** @internal — exposed for tests and future controllers. */
|
|
2094
2152
|
get layerIds(): {
|
|
2095
2153
|
graphics: LayerId;
|
|
2154
|
+
highlight: LayerId;
|
|
2096
2155
|
preview: LayerId;
|
|
2097
2156
|
guide: LayerId;
|
|
2098
2157
|
handles: LayerId;
|
|
@@ -2104,6 +2163,7 @@ declare class TacticalDraw {
|
|
|
2104
2163
|
*/
|
|
2105
2164
|
private applyGuideStyle;
|
|
2106
2165
|
private baseGuideStyle;
|
|
2166
|
+
private baseHighlightStyle;
|
|
2107
2167
|
/**
|
|
2108
2168
|
* Resolve handle slots (plus `boxOutline`) across built-in → façade →
|
|
2109
2169
|
* per-call with per-slot, per-property shallow merge. Explicit `undefined`
|
package/dist/index.mjs
CHANGED
|
@@ -5843,6 +5843,7 @@ function runPointSymbolEdit(opts) {
|
|
|
5843
5843
|
var TacticalDraw = class {
|
|
5844
5844
|
adapter;
|
|
5845
5845
|
graphicsLayer;
|
|
5846
|
+
highlightLayer;
|
|
5846
5847
|
previewLayer;
|
|
5847
5848
|
guideLayer;
|
|
5848
5849
|
handlesLayer;
|
|
@@ -5904,6 +5905,20 @@ var TacticalDraw = class {
|
|
|
5904
5905
|
*/
|
|
5905
5906
|
lastRenderedMeasures = [];
|
|
5906
5907
|
snapGeometryFeatures = [];
|
|
5908
|
+
/**
|
|
5909
|
+
* Ids of graphics currently carrying a passive selection-highlight outline
|
|
5910
|
+
* ({@link setHighlightedGraphics}). Declarative set-replace, no session —
|
|
5911
|
+
* re-projected on every view change and re-evaluated at the end of every
|
|
5912
|
+
* `render()` so outlines track geometry, style, and dropped graphics.
|
|
5913
|
+
*/
|
|
5914
|
+
highlightedIds = [];
|
|
5915
|
+
/**
|
|
5916
|
+
* Whether the highlight layer currently holds any outline. Lets
|
|
5917
|
+
* `renderHighlights` skip a redundant empty write on every view change /
|
|
5918
|
+
* render while no highlights are set, but still emit the one clearing write
|
|
5919
|
+
* that removes outlines when the set transitions to empty.
|
|
5920
|
+
*/
|
|
5921
|
+
hasHighlightOutlines = false;
|
|
5907
5922
|
constructor(adapter, options) {
|
|
5908
5923
|
this.adapter = adapter;
|
|
5909
5924
|
this.graphicsStyle = resolveStyleHints(BUILT_IN_GRAPHICS_STYLE, options?.graphicsStyle, void 0);
|
|
@@ -5915,6 +5930,7 @@ var TacticalDraw = class {
|
|
|
5915
5930
|
const owned = /* @__PURE__ */ new Set();
|
|
5916
5931
|
const provided = options?.layers ?? {};
|
|
5917
5932
|
this.graphicsLayer = resolveSlot(adapter, provided.graphics, owned);
|
|
5933
|
+
this.highlightLayer = resolveSlot(adapter, provided.highlight, owned);
|
|
5918
5934
|
this.previewLayer = resolveSlot(adapter, provided.preview, owned);
|
|
5919
5935
|
this.guideLayer = resolveSlot(adapter, provided.guide, owned);
|
|
5920
5936
|
this.handlesLayer = resolveSlot(adapter, provided.handles, owned);
|
|
@@ -5923,6 +5939,7 @@ var TacticalDraw = class {
|
|
|
5923
5939
|
this.adapter.setLayerStyle(this.graphicsLayer, graphicsLayerStyle);
|
|
5924
5940
|
this.adapter.setLayerStyle(this.previewLayer, graphicsLayerStyle);
|
|
5925
5941
|
this.adapter.setLayerStyle(this.guideLayer, guideToStyleOptions(this.baseGuideStyle()));
|
|
5942
|
+
this.adapter.setLayerStyle(this.highlightLayer, guideToStyleOptions(this.baseHighlightStyle()));
|
|
5926
5943
|
this.adapter.onViewChange(this.handleViewChange);
|
|
5927
5944
|
}
|
|
5928
5945
|
/** Replace snapping configuration without recreating the map adapter or façade. */
|
|
@@ -5970,6 +5987,7 @@ var TacticalDraw = class {
|
|
|
5970
5987
|
*/
|
|
5971
5988
|
handleViewChange = () => {
|
|
5972
5989
|
if (this.destroyed || !this.hasRendered) return;
|
|
5990
|
+
this.renderHighlights();
|
|
5973
5991
|
const excluded = this.excludedInteractionIds();
|
|
5974
5992
|
const visible = excluded.size > 0 ? this.lastRenderedMeasures.filter((m) => !excluded.has(m.id)) : this.lastRenderedMeasures;
|
|
5975
5993
|
const nativeGroundIconScaling = this.adapter.scalesGroundPointSymbolsNatively === true;
|
|
@@ -6067,6 +6085,88 @@ var TacticalDraw = class {
|
|
|
6067
6085
|
if (handle.graphicIds.some((id) => !rendered.has(id))) handle.abort("removed");
|
|
6068
6086
|
});
|
|
6069
6087
|
}
|
|
6088
|
+
this.renderHighlights();
|
|
6089
|
+
}
|
|
6090
|
+
/**
|
|
6091
|
+
* Replace the passive selection-highlight set with `ids`. Declarative and
|
|
6092
|
+
* idempotent (mirrors {@link render}): a non-interactive outline box is drawn
|
|
6093
|
+
* around each still-rendered graphic on the dedicated highlight layer — no
|
|
6094
|
+
* draw / edit / transform session, no promise, no pick subscription. Passing
|
|
6095
|
+
* `[]` clears every highlight. Unknown ids and ids currently lifted into an
|
|
6096
|
+
* active edit / transform session are skipped (the live session's own visuals
|
|
6097
|
+
* supersede the passive outline). Hosts use it for Excalidraw-style
|
|
6098
|
+
* "locked selection" chrome.
|
|
6099
|
+
*/
|
|
6100
|
+
setHighlightedGraphics(ids) {
|
|
6101
|
+
this.assertAlive();
|
|
6102
|
+
this.highlightedIds = ids.slice();
|
|
6103
|
+
this.renderHighlights();
|
|
6104
|
+
}
|
|
6105
|
+
/**
|
|
6106
|
+
* Project the current {@link highlightedIds} into one outline `LineString`
|
|
6107
|
+
* per graphic and reconcile them onto the highlight layer. Idempotent and
|
|
6108
|
+
* id-keyed via `adapter.updateLayerFeatures`, so a set-replace removes stale
|
|
6109
|
+
* outlines and an unchanged set is a cheap no-op write. Skips ids that are
|
|
6110
|
+
* unknown (not in the last render) or lifted into an active session, and
|
|
6111
|
+
* bails gracefully when the adapter cannot invert pixels to coordinates
|
|
6112
|
+
* (`getCoordinateFromPixel` is optional in the MapAdapter ABI).
|
|
6113
|
+
*/
|
|
6114
|
+
renderHighlights() {
|
|
6115
|
+
if (this.destroyed) return;
|
|
6116
|
+
if (this.highlightedIds.length === 0 && !this.hasHighlightOutlines) return;
|
|
6117
|
+
const fromPixel = this.adapter.getCoordinateFromPixel?.bind(this.adapter);
|
|
6118
|
+
if (!fromPixel) return;
|
|
6119
|
+
const resolved = this.resolveInteractionStyle(void 0);
|
|
6120
|
+
const style = resolved.highlightOutline;
|
|
6121
|
+
const padding = resolved.boxPadding ?? 0;
|
|
6122
|
+
const excluded = this.excludedInteractionIds();
|
|
6123
|
+
const bearing = this.adapter.getBearing();
|
|
6124
|
+
const features = [];
|
|
6125
|
+
for (const id of this.highlightedIds) {
|
|
6126
|
+
if (excluded.has(id)) continue;
|
|
6127
|
+
const graphic = this.lastRenderedMeasures.find((g) => g.id === id);
|
|
6128
|
+
if (!graphic) continue;
|
|
6129
|
+
const box = this.highlightBoxForGraphic(graphic, bearing, padding);
|
|
6130
|
+
if (!box) continue;
|
|
6131
|
+
let invertFailed = false;
|
|
6132
|
+
const invert = (pixel) => {
|
|
6133
|
+
const coord = fromPixel(pixel);
|
|
6134
|
+
if (!coord) {
|
|
6135
|
+
invertFailed = true;
|
|
6136
|
+
return [pixel[0], pixel[1]];
|
|
6137
|
+
}
|
|
6138
|
+
return coord;
|
|
6139
|
+
};
|
|
6140
|
+
const feature = boxOutlineFeature(box, invert, `__td_highlight__:${id}`, style);
|
|
6141
|
+
if (!invertFailed) features.push(feature);
|
|
6142
|
+
}
|
|
6143
|
+
this.adapter.updateLayerFeatures(this.highlightLayer, features);
|
|
6144
|
+
this.hasHighlightOutlines = features.length > 0;
|
|
6145
|
+
}
|
|
6146
|
+
/**
|
|
6147
|
+
* Fit the passive-highlight transform box for one graphic: the point-symbol
|
|
6148
|
+
* footprint for symbols, the snug text footprint for Text graphics, and the
|
|
6149
|
+
* projected-geometry bounding box (at the current bearing) for every other
|
|
6150
|
+
* control measure. Returns `null` when nothing projects this frame.
|
|
6151
|
+
*/
|
|
6152
|
+
highlightBoxForGraphic(graphic, bearing, padding) {
|
|
6153
|
+
if (isPointSymbol(graphic)) {
|
|
6154
|
+
if (!this.pointSymbols) return null;
|
|
6155
|
+
return pointSymbolFootprint(graphic, this.pointSymbols.render(graphic), this.adapter)?.box ?? null;
|
|
6156
|
+
}
|
|
6157
|
+
const render = renderMeasureForAdapter(this.adapter, graphic, { graphicsStyle: this.graphicsStyle });
|
|
6158
|
+
if (graphic.kind === "text") return fitTextFootprintFromFeatures(render.features, (lonLat) => this.adapter.getPixelFromCoordinate(lonLat), {
|
|
6159
|
+
resolution: this.adapter.getResolution(),
|
|
6160
|
+
bearing
|
|
6161
|
+
});
|
|
6162
|
+
const positions = [];
|
|
6163
|
+
for (const feature of render.features) if (feature.geometry) collectFeaturePositions(feature.geometry, positions);
|
|
6164
|
+
const pixels = [];
|
|
6165
|
+
for (const position of positions) {
|
|
6166
|
+
const pixel = this.adapter.getPixelFromCoordinate(position);
|
|
6167
|
+
if (pixel) pixels.push(pixel);
|
|
6168
|
+
}
|
|
6169
|
+
return fitTransformBox(pixels, bearing, padding);
|
|
6070
6170
|
}
|
|
6071
6171
|
/**
|
|
6072
6172
|
* Nested-call guard + preempt, shared by `draw` and `edit`. Returns a
|
|
@@ -6121,6 +6221,7 @@ var TacticalDraw = class {
|
|
|
6121
6221
|
clearSlot();
|
|
6122
6222
|
this.clearSnapResult();
|
|
6123
6223
|
if (extraSettle) extraSettle();
|
|
6224
|
+
this.renderHighlights();
|
|
6124
6225
|
};
|
|
6125
6226
|
this.activeInteraction.begin();
|
|
6126
6227
|
try {
|
|
@@ -6132,7 +6233,10 @@ var TacticalDraw = class {
|
|
|
6132
6233
|
}
|
|
6133
6234
|
if (!settledDuringCreate) {
|
|
6134
6235
|
this.activeInteraction.set(handle);
|
|
6135
|
-
if (activate && !handle.settled)
|
|
6236
|
+
if (activate && !handle.settled) {
|
|
6237
|
+
deactivate = activate(handle) ?? null;
|
|
6238
|
+
this.renderHighlights();
|
|
6239
|
+
}
|
|
6136
6240
|
}
|
|
6137
6241
|
handle.promise.then(clearSlot, clearSlot);
|
|
6138
6242
|
return handle.promise;
|
|
@@ -6648,6 +6752,7 @@ var TacticalDraw = class {
|
|
|
6648
6752
|
get layerIds() {
|
|
6649
6753
|
return {
|
|
6650
6754
|
graphics: this.graphicsLayer,
|
|
6755
|
+
highlight: this.highlightLayer,
|
|
6651
6756
|
preview: this.previewLayer,
|
|
6652
6757
|
guide: this.guideLayer,
|
|
6653
6758
|
handles: this.handlesLayer
|
|
@@ -6670,6 +6775,9 @@ var TacticalDraw = class {
|
|
|
6670
6775
|
baseGuideStyle() {
|
|
6671
6776
|
return mergeSlot(BUILT_IN_INTERACTION_STYLE.guide, this.defaultInteractionStyle?.guide);
|
|
6672
6777
|
}
|
|
6778
|
+
baseHighlightStyle() {
|
|
6779
|
+
return mergeSlot(BUILT_IN_INTERACTION_STYLE.highlightOutline, this.defaultInteractionStyle?.highlightOutline);
|
|
6780
|
+
}
|
|
6673
6781
|
/**
|
|
6674
6782
|
* Resolve handle slots (plus `boxOutline`) across built-in → façade →
|
|
6675
6783
|
* per-call with per-slot, per-property shallow merge. Explicit `undefined`
|
|
@@ -6697,6 +6805,8 @@ var TacticalDraw = class {
|
|
|
6697
6805
|
}
|
|
6698
6806
|
const boxOutline = mergeSlot(mergeSlot(BUILT_IN_INTERACTION_STYLE.boxOutline, this.defaultInteractionStyle?.boxOutline), perCall?.boxOutline);
|
|
6699
6807
|
if (boxOutline) out.boxOutline = boxOutline;
|
|
6808
|
+
const highlightOutline = mergeSlot(mergeSlot(BUILT_IN_INTERACTION_STYLE.highlightOutline, this.defaultInteractionStyle?.highlightOutline), perCall?.highlightOutline);
|
|
6809
|
+
if (highlightOutline) out.highlightOutline = highlightOutline;
|
|
6700
6810
|
const boxPadding = perCall && "boxPadding" in perCall ? perCall.boxPadding : this.defaultInteractionStyle && "boxPadding" in this.defaultInteractionStyle ? this.defaultInteractionStyle.boxPadding : void 0;
|
|
6701
6811
|
if (typeof boxPadding === "number") out.boxPadding = boxPadding;
|
|
6702
6812
|
return out;
|
|
@@ -6737,6 +6847,11 @@ const BUILT_IN_INTERACTION_STYLE = {
|
|
|
6737
6847
|
strokeColor: "#4f7cff",
|
|
6738
6848
|
strokeWidth: 1
|
|
6739
6849
|
},
|
|
6850
|
+
highlightOutline: {
|
|
6851
|
+
strokeColor: "#8ea3c9",
|
|
6852
|
+
strokeWidth: 1.5,
|
|
6853
|
+
strokeDash: [4, 4]
|
|
6854
|
+
},
|
|
6740
6855
|
vertexHandle: {
|
|
6741
6856
|
pointRadius: 6,
|
|
6742
6857
|
fillColor: "#ffffff",
|
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.33",
|
|
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",
|