@orbat-mapper/tactical-draw 0.2.0-alpha.11 → 0.2.0-alpha.13
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 +28 -6
- package/dist/index.mjs +124 -32
- package/package.json +2 -2
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ControlMeasure, ControlMeasureKind, ControlMeasureSnapshot, ControlMeasureStyle } from "@orbat-mapper/control-measures";
|
|
1
|
+
import { ControlMeasure, ControlMeasureKind, ControlMeasureSnapshot, ControlMeasureStyle, TextAmplifiers } from "@orbat-mapper/control-measures";
|
|
2
2
|
import { Feature, Position } from "geojson";
|
|
3
3
|
|
|
4
4
|
//#region src/gestures/types.d.ts
|
|
@@ -54,6 +54,13 @@ interface StyleOptions {
|
|
|
54
54
|
pointRadius?: number;
|
|
55
55
|
text?: TextStyleOptions;
|
|
56
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Text (label) style for a layer. When `labelProperty` is set, adapters also
|
|
59
|
+
* read per-feature label properties: `rotation` (radians, rendered as
|
|
60
|
+
* `π − rotation`), `textSizePixels`/`textSizeZoom`/`textSizeResolution`
|
|
61
|
+
* (ADR-0020), and `textAnchor` (`"start"` = text begins at the label point,
|
|
62
|
+
* `"end"` = text ends there; absent = centered).
|
|
63
|
+
*/
|
|
57
64
|
interface TextStyleOptions {
|
|
58
65
|
text?: string;
|
|
59
66
|
labelProperty?: string;
|
|
@@ -527,6 +534,13 @@ interface EditSession {
|
|
|
527
534
|
readonly options: ControlMeasure["options"];
|
|
528
535
|
/** Live working style. Copy on read. */
|
|
529
536
|
readonly style: ControlMeasureStyle | undefined;
|
|
537
|
+
/**
|
|
538
|
+
* Live working text amplifiers (MIL-STD-2525E Appendix L Table L-II). Copy
|
|
539
|
+
* on read. `undefined` when the edit-start measure carried none and none
|
|
540
|
+
* have been set since. Keys are stored as given — canonicalisation to
|
|
541
|
+
* doctrinal field codes happens in the core render seam, not here.
|
|
542
|
+
*/
|
|
543
|
+
readonly textAmplifiers: TextAmplifiers | undefined;
|
|
530
544
|
/** `true` after the first working-copy mutation; back to `false` within tolerance. */
|
|
531
545
|
readonly dirty: boolean;
|
|
532
546
|
readonly modes: readonly EditMode[];
|
|
@@ -564,6 +578,13 @@ interface EditSession {
|
|
|
564
578
|
setSizeAnchor(anchor: SizeAnchor): void;
|
|
565
579
|
/** Shallow-merge per-measure style into the live working copy. */
|
|
566
580
|
setStyle(partial: ControlMeasureStyle): void;
|
|
581
|
+
/**
|
|
582
|
+
* Shallow-merge into the live working text amplifiers. A value of
|
|
583
|
+
* `undefined` or an empty/whitespace-only string deletes that key; the
|
|
584
|
+
* working record collapses back to `undefined` once it holds no keys. A
|
|
585
|
+
* silent no-op after the session has settled (like [[setOptions]]).
|
|
586
|
+
*/
|
|
587
|
+
setTextAmplifiers(partial: Record<string, string | undefined>): void;
|
|
567
588
|
/**
|
|
568
589
|
* Subscribe to working-copy changes. The listener receives an
|
|
569
590
|
* [[EditChangeEvent]] for each completed gesture (drag end, insert/delete
|
|
@@ -1146,11 +1167,12 @@ declare class TacticalDraw {
|
|
|
1146
1167
|
private renderMeasuresToGraphics;
|
|
1147
1168
|
/**
|
|
1148
1169
|
* Re-render the graphics layer on a view (zoom) change so pixel-denominated
|
|
1149
|
-
* measures rescale live. Skipped entirely
|
|
1150
|
-
*
|
|
1151
|
-
*
|
|
1152
|
-
*
|
|
1153
|
-
*
|
|
1170
|
+
* and not-yet-committed label-sized measures rescale live. Skipped entirely
|
|
1171
|
+
* when nothing rendered still reads live view state — meter geometry with a
|
|
1172
|
+
* captured label size is zoom-independent, so a rebuild would be wasted
|
|
1173
|
+
* layer writes. During an active edit the edited measure lives on the
|
|
1174
|
+
* preview layer (lifted from graphics), so it is excluded here exactly as
|
|
1175
|
+
* in `render()`; the preview's own view-change subscription rescales it.
|
|
1154
1176
|
*/
|
|
1155
1177
|
private readonly handleViewChange;
|
|
1156
1178
|
/**
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { cloneControlMeasure, controlMeasureIdFromFeature, freezeOrientationOptions, getControlMeasureMetadata, renderControlMeasure, resolveStyleHints, roundToFixed } from "@orbat-mapper/control-measures";
|
|
1
|
+
import { canonicalTextAmplifierKey, cloneControlMeasure, controlMeasureIdFromFeature, freezeOrientationOptions, getControlMeasureMetadata, renderControlMeasure, resolveStyleHints, roundToFixed } from "@orbat-mapper/control-measures";
|
|
2
2
|
//#region src/base-map-adapter.ts
|
|
3
3
|
/**
|
|
4
4
|
* Abstract base class shared by all `MapAdapter` implementations.
|
|
@@ -563,6 +563,21 @@ function measureUsesAdapterResolution(measure) {
|
|
|
563
563
|
return measure.options?.[spec.pixels] !== void 0;
|
|
564
564
|
}
|
|
565
565
|
/**
|
|
566
|
+
* Whether rendering `measure` through [[renderMeasureForAdapter]] reads live
|
|
567
|
+
* adapter view state — either a pixel-denominated size
|
|
568
|
+
* ([[measureUsesAdapterResolution]]) or draw-time label sizing that
|
|
569
|
+
* [[withCommittedAdapterMeasureOptions]] would still capture (a
|
|
570
|
+
* `capturesLabelSize` kind not yet carrying `labelSizePixels`). The
|
|
571
|
+
* view-change rerender gates key off this so such measures rescale live;
|
|
572
|
+
* committed label-sized measures already carry `labelSizePixels`, stay
|
|
573
|
+
* zoom-independent, and keep skipping the rebuild.
|
|
574
|
+
*/
|
|
575
|
+
function measureRendersFromViewState(measure) {
|
|
576
|
+
if (measureUsesAdapterResolution(measure)) return true;
|
|
577
|
+
if (!getControlMeasureMetadata(measure.kind).capturesLabelSize) return false;
|
|
578
|
+
return typeof measure.options?.labelSizePixels !== "number";
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
566
581
|
* The [[SizeAnchor]] a measure currently encodes (ADR-0020): `screen` when it
|
|
567
582
|
* carries its kind's pixel-denominated size option, `ground` otherwise. Keys off
|
|
568
583
|
* the same `PIXEL_SIZE_OPTIONS` table as the bake, so a consumer deciding how to
|
|
@@ -617,9 +632,11 @@ function withAdapterMeasureOptions(measure, adapter) {
|
|
|
617
632
|
};
|
|
618
633
|
}
|
|
619
634
|
/**
|
|
620
|
-
* Capture draw-time label sizing options
|
|
621
|
-
*
|
|
622
|
-
*
|
|
635
|
+
* Capture draw-time label sizing options. These are stable feature style
|
|
636
|
+
* inputs; adapters scale from them without TacticalDraw re-rendering on every
|
|
637
|
+
* zoom. No-op if the measure already carries `labelSizePixels` (e.g. already
|
|
638
|
+
* committed), so it's safe to call on every render as well as on commit
|
|
639
|
+
* itself — see [[renderMeasureForAdapter]].
|
|
623
640
|
*/
|
|
624
641
|
function withCommittedAdapterMeasureOptions(measure, adapter) {
|
|
625
642
|
if (!getControlMeasureMetadata(measure.kind).capturesLabelSize) return measure;
|
|
@@ -641,10 +658,17 @@ function withCommittedAdapterMeasureOptions(measure, adapter) {
|
|
|
641
658
|
* "turn a measure into features against the live map" — every TacticalDraw
|
|
642
659
|
* layer write (graphics reconcile, draw preview, edit preview, commit snapshot)
|
|
643
660
|
* routes through here so pixel sizing is resolved identically everywhere and no
|
|
644
|
-
* call site can forget the injection.
|
|
661
|
+
* call site can forget the injection. Composes [[withAdapterMeasureOptions]]
|
|
662
|
+
* (pixel size → `metersPerPixel`) with [[withCommittedAdapterMeasureOptions]]
|
|
663
|
+
* (label size defaults for `capturesLabelSize` kinds), so draw/edit previews
|
|
664
|
+
* get the same label clearance a commit would bake in — the input `measure`
|
|
665
|
+
* is never mutated, and callers that need the persisted/working measure to
|
|
666
|
+
* stay free of these render-only options must keep using that separately
|
|
667
|
+
* (draw/edit commit paths call [[withCommittedAdapterMeasureOptions]]
|
|
668
|
+
* directly to produce the value they persist).
|
|
645
669
|
*/
|
|
646
670
|
function renderMeasureForAdapter(adapter, measure, opts) {
|
|
647
|
-
return renderControlMeasure(withAdapterMeasureOptions(measure, adapter), opts);
|
|
671
|
+
return renderControlMeasure(withAdapterMeasureOptions(withCommittedAdapterMeasureOptions(measure, adapter), adapter), opts);
|
|
648
672
|
}
|
|
649
673
|
//#endregion
|
|
650
674
|
//#region src/tactical-draw/run-interaction.ts
|
|
@@ -876,7 +900,7 @@ const PREVIEW_CURSOR$1 = "crosshair";
|
|
|
876
900
|
* kinds need no host UI.
|
|
877
901
|
*/
|
|
878
902
|
function runFixedLengthDraw(options) {
|
|
879
|
-
const { adapter, kind, previewLayer, guideLayer, graphicsStyle, generateId, signal, measureOptions, measureStyle, measureProperties, guide: guideOpt, sizeAnchor, onSettled } = options;
|
|
903
|
+
const { adapter, kind, previewLayer, guideLayer, graphicsStyle, generateId, signal, measureOptions, measureStyle, measureProperties, measureTextAmplifiers, guide: guideOpt, sizeAnchor, onSettled } = options;
|
|
880
904
|
const metadata = getControlMeasureMetadata(kind);
|
|
881
905
|
const rule = metadata.rule;
|
|
882
906
|
const min = metadata.minCoordinates;
|
|
@@ -916,14 +940,14 @@ function runFixedLengthDraw(options) {
|
|
|
916
940
|
return;
|
|
917
941
|
}
|
|
918
942
|
const previewThreshold = rule?.minimumPreviewPoints ?? required;
|
|
919
|
-
const features = buildPreviewFeatures$1(adapter, kind, rule && raw.length >= previewThreshold ? rule.derive(raw) : raw, measureOptions, measureStyle, renderOpts);
|
|
943
|
+
const features = buildPreviewFeatures$1(adapter, kind, rule && raw.length >= previewThreshold ? rule.derive(raw) : raw, measureOptions, measureStyle, measureTextAmplifiers, renderOpts);
|
|
920
944
|
ctx.writeLayer(previewLayer, features);
|
|
921
945
|
const guidePoints = rule?.guidePoints ? rule.guidePoints(raw) : raw;
|
|
922
946
|
const guideFeatures = guideEnabled ? buildGuideFeatures(guidePoints, isAreaKind) : [];
|
|
923
947
|
ctx.writeLayer(guideLayer, guideFeatures);
|
|
924
948
|
}
|
|
925
949
|
function rescaleOnViewChange() {
|
|
926
|
-
if (!
|
|
950
|
+
if (!measureRendersFromViewState({
|
|
927
951
|
kind,
|
|
928
952
|
options: measureOptions
|
|
929
953
|
})) return;
|
|
@@ -937,7 +961,8 @@ function runFixedLengthDraw(options) {
|
|
|
937
961
|
controlPoints: finalPoints,
|
|
938
962
|
...measureOptions !== void 0 ? { options: measureOptions } : {},
|
|
939
963
|
...measureStyle !== void 0 ? { style: measureStyle } : {},
|
|
940
|
-
...measureProperties !== void 0 ? { properties: measureProperties } : {}
|
|
964
|
+
...measureProperties !== void 0 ? { properties: measureProperties } : {},
|
|
965
|
+
...measureTextAmplifiers !== void 0 ? { textAmplifiers: measureTextAmplifiers } : {}
|
|
941
966
|
}, adapter);
|
|
942
967
|
const measure = sizeAnchor === "screen" ? captured : bakePixelSizeToGround(captured, adapter);
|
|
943
968
|
return {
|
|
@@ -970,13 +995,14 @@ function runFixedLengthDraw(options) {
|
|
|
970
995
|
* falls back to a plain Point/LineString outline when the kind's generator
|
|
971
996
|
* cannot produce a shape yet (under-min count).
|
|
972
997
|
*/
|
|
973
|
-
function buildPreviewFeatures$1(adapter, kind, points, measureOptions, measureStyle, renderOpts) {
|
|
998
|
+
function buildPreviewFeatures$1(adapter, kind, points, measureOptions, measureStyle, measureTextAmplifiers, renderOpts) {
|
|
974
999
|
const measure = {
|
|
975
1000
|
id: PREVIEW_CM_ID$1,
|
|
976
1001
|
kind,
|
|
977
1002
|
controlPoints: points,
|
|
978
1003
|
...measureOptions !== void 0 ? { options: measureOptions } : {},
|
|
979
|
-
...measureStyle !== void 0 ? { style: measureStyle } : {}
|
|
1004
|
+
...measureStyle !== void 0 ? { style: measureStyle } : {},
|
|
1005
|
+
...measureTextAmplifiers !== void 0 ? { textAmplifiers: measureTextAmplifiers } : {}
|
|
980
1006
|
};
|
|
981
1007
|
try {
|
|
982
1008
|
return renderMeasureForAdapter(adapter, measure, {
|
|
@@ -1129,7 +1155,7 @@ const PREVIEW_CURSOR = "crosshair";
|
|
|
1129
1155
|
* added point again" as additional gesture-level commit triggers.
|
|
1130
1156
|
*/
|
|
1131
1157
|
function runVariableLengthDraw(options) {
|
|
1132
|
-
const { adapter, kind, previewLayer, guideLayer, graphicsStyle, generateId, signal, onSession, seed, measureOptions, measureStyle, measureProperties, guide: guideOpt, sizeAnchor, onSettled } = options;
|
|
1158
|
+
const { adapter, kind, previewLayer, guideLayer, graphicsStyle, generateId, signal, onSession, seed, measureOptions, measureStyle, measureProperties, measureTextAmplifiers, guide: guideOpt, sizeAnchor, onSettled } = options;
|
|
1133
1159
|
const metadata = getControlMeasureMetadata(kind);
|
|
1134
1160
|
if (typeof metadata.minCoordinates !== "number") throw new Error(`runVariableLengthDraw: kind "${kind}" has no minCoordinates metadata`);
|
|
1135
1161
|
const min = metadata.minCoordinates;
|
|
@@ -1164,7 +1190,8 @@ function runVariableLengthDraw(options) {
|
|
|
1164
1190
|
kind,
|
|
1165
1191
|
controlPoints: initialPoints.map((p) => [...p]),
|
|
1166
1192
|
...measureOptions !== void 0 ? { options: measureOptions } : {},
|
|
1167
|
-
...measureStyle !== void 0 ? { style: measureStyle } : {}
|
|
1193
|
+
...measureStyle !== void 0 ? { style: measureStyle } : {},
|
|
1194
|
+
...measureTextAmplifiers !== void 0 ? { textAmplifiers: measureTextAmplifiers } : {}
|
|
1168
1195
|
}, {
|
|
1169
1196
|
...renderOpts,
|
|
1170
1197
|
validationMode: "throw"
|
|
@@ -1287,13 +1314,13 @@ function runVariableLengthDraw(options) {
|
|
|
1287
1314
|
points.splice(idx, 0, rubberBand);
|
|
1288
1315
|
} else points = [...committed];
|
|
1289
1316
|
if (rule) points = rule.derive(points);
|
|
1290
|
-
const previewFeatures = points.length === 0 ? [] : buildPreviewFeatures(adapter, kind, points, measureOptions, measureStyle, renderOpts);
|
|
1317
|
+
const previewFeatures = points.length === 0 ? [] : buildPreviewFeatures(adapter, kind, points, measureOptions, measureStyle, measureTextAmplifiers, renderOpts);
|
|
1291
1318
|
ctx.writeLayer(previewLayer, previewFeatures);
|
|
1292
1319
|
const guideFeatures = points.length === 0 || !guideEnabled ? [] : buildGuideFeatures(points, isAreaKind, trailingFixed);
|
|
1293
1320
|
ctx.writeLayer(guideLayer, guideFeatures);
|
|
1294
1321
|
}
|
|
1295
1322
|
function rescaleOnViewChange() {
|
|
1296
|
-
if (!
|
|
1323
|
+
if (!measureRendersFromViewState({
|
|
1297
1324
|
kind,
|
|
1298
1325
|
options: measureOptions
|
|
1299
1326
|
})) return;
|
|
@@ -1306,7 +1333,8 @@ function runVariableLengthDraw(options) {
|
|
|
1306
1333
|
controlPoints: committed.map((p) => [...p]),
|
|
1307
1334
|
...measureOptions !== void 0 ? { options: measureOptions } : {},
|
|
1308
1335
|
...measureStyle !== void 0 ? { style: measureStyle } : {},
|
|
1309
|
-
...measureProperties !== void 0 ? { properties: measureProperties } : {}
|
|
1336
|
+
...measureProperties !== void 0 ? { properties: measureProperties } : {},
|
|
1337
|
+
...measureTextAmplifiers !== void 0 ? { textAmplifiers: measureTextAmplifiers } : {}
|
|
1310
1338
|
}, adapter);
|
|
1311
1339
|
const measure = sizeAnchor === "screen" ? built : bakePixelSizeToGround(built, adapter);
|
|
1312
1340
|
return {
|
|
@@ -1357,13 +1385,14 @@ function isValidPosition(value) {
|
|
|
1357
1385
|
}
|
|
1358
1386
|
return true;
|
|
1359
1387
|
}
|
|
1360
|
-
function buildPreviewFeatures(adapter, kind, points, measureOptions, measureStyle, renderOpts) {
|
|
1388
|
+
function buildPreviewFeatures(adapter, kind, points, measureOptions, measureStyle, measureTextAmplifiers, renderOpts) {
|
|
1361
1389
|
const measure = {
|
|
1362
1390
|
id: PREVIEW_CM_ID,
|
|
1363
1391
|
kind,
|
|
1364
1392
|
controlPoints: points,
|
|
1365
1393
|
...measureOptions !== void 0 ? { options: measureOptions } : {},
|
|
1366
|
-
...measureStyle !== void 0 ? { style: measureStyle } : {}
|
|
1394
|
+
...measureStyle !== void 0 ? { style: measureStyle } : {},
|
|
1395
|
+
...measureTextAmplifiers !== void 0 ? { textAmplifiers: measureTextAmplifiers } : {}
|
|
1367
1396
|
};
|
|
1368
1397
|
try {
|
|
1369
1398
|
return renderMeasureForAdapter(adapter, measure, renderOpts).features;
|
|
@@ -1996,6 +2025,7 @@ function runEdit(opts) {
|
|
|
1996
2025
|
const editStartPoints = measure.controlPoints.map((p) => [...p]);
|
|
1997
2026
|
const workingPoints = editStartPoints.map((p) => [...p]);
|
|
1998
2027
|
let workingOptions = { ...measure.options ?? {} };
|
|
2028
|
+
let workingTextAmplifiers = measure.textAmplifiers && Object.keys(measure.textAmplifiers).length > 0 ? { ...measure.textAmplifiers } : void 0;
|
|
1999
2029
|
let modes = requestedModes;
|
|
2000
2030
|
let dirty = false;
|
|
2001
2031
|
let nonGeometryDirty = false;
|
|
@@ -2084,6 +2114,8 @@ function runEdit(opts) {
|
|
|
2084
2114
|
options: workingOptions,
|
|
2085
2115
|
controlPoints: workingPoints.map((p) => [...p])
|
|
2086
2116
|
};
|
|
2117
|
+
if (workingTextAmplifiers) working.textAmplifiers = workingTextAmplifiers;
|
|
2118
|
+
else delete working.textAmplifiers;
|
|
2087
2119
|
let features;
|
|
2088
2120
|
try {
|
|
2089
2121
|
features = renderMeasureForAdapter(adapter, working, renderOpts).features;
|
|
@@ -2099,7 +2131,7 @@ function runEdit(opts) {
|
|
|
2099
2131
|
}
|
|
2100
2132
|
function rescaleOnViewChange() {
|
|
2101
2133
|
if (ctx.settled) return;
|
|
2102
|
-
if (
|
|
2134
|
+
if (measureRendersFromViewState({
|
|
2103
2135
|
kind: measure.kind,
|
|
2104
2136
|
options: workingOptions
|
|
2105
2137
|
})) renderPreview();
|
|
@@ -2135,6 +2167,8 @@ function runEdit(opts) {
|
|
|
2135
2167
|
options,
|
|
2136
2168
|
controlPoints: workingPoints.map((p) => [...p])
|
|
2137
2169
|
};
|
|
2170
|
+
if (workingTextAmplifiers) working.textAmplifiers = workingTextAmplifiers;
|
|
2171
|
+
else delete working.textAmplifiers;
|
|
2138
2172
|
const committed = sizeAnchor === "screen" ? working : bakePixelSizeToGround(working, adapter);
|
|
2139
2173
|
return {
|
|
2140
2174
|
measure: cloneControlMeasure(committed),
|
|
@@ -2186,7 +2220,7 @@ function runEdit(opts) {
|
|
|
2186
2220
|
* - Reject restores the working state to the pre-gesture snapshot and
|
|
2187
2221
|
* recomputes `dirty`. Close/abort settle the session as normal.
|
|
2188
2222
|
*/
|
|
2189
|
-
function emitChange(previousRendered, previousPoints, previousOptions, previousStyle, previousNonGeometryDirty, previousSizeAnchor, currentRendered) {
|
|
2223
|
+
function emitChange(previousRendered, previousPoints, previousOptions, previousStyle, previousNonGeometryDirty, previousSizeAnchor, currentRendered, previousTextAmplifiers) {
|
|
2190
2224
|
if (ctx.settled) return;
|
|
2191
2225
|
let rejectRequested = false;
|
|
2192
2226
|
let closeRequested = false;
|
|
@@ -2218,6 +2252,7 @@ function runEdit(opts) {
|
|
|
2218
2252
|
replaceWorkingOptions(previousOptions);
|
|
2219
2253
|
currentStyle = previousStyle;
|
|
2220
2254
|
sizeAnchor = previousSizeAnchor;
|
|
2255
|
+
workingTextAmplifiers = previousTextAmplifiers;
|
|
2221
2256
|
nonGeometryDirty = previousNonGeometryDirty;
|
|
2222
2257
|
renderPreview();
|
|
2223
2258
|
renderHandlesLayer();
|
|
@@ -2242,6 +2277,7 @@ function runEdit(opts) {
|
|
|
2242
2277
|
const previousOptions = { ...preGestureOptions ?? workingOptions ?? {} };
|
|
2243
2278
|
const previousStyle = currentStyle;
|
|
2244
2279
|
const previousNonGeometryDirty = nonGeometryDirty;
|
|
2280
|
+
const previousTextAmplifiers = workingTextAmplifiers;
|
|
2245
2281
|
const previousRendered = rebuildRenderedWith(previousOptions);
|
|
2246
2282
|
replaceWorkingPoints(nextPoints);
|
|
2247
2283
|
let currentRendered;
|
|
@@ -2259,7 +2295,7 @@ function runEdit(opts) {
|
|
|
2259
2295
|
recomputeDirty();
|
|
2260
2296
|
dragInFlight = false;
|
|
2261
2297
|
drainPendingModes();
|
|
2262
|
-
emitChange(previousRendered, previousPoints, previousOptions, previousStyle, previousNonGeometryDirty, sizeAnchor, currentRendered);
|
|
2298
|
+
emitChange(previousRendered, previousPoints, previousOptions, previousStyle, previousNonGeometryDirty, sizeAnchor, currentRendered, previousTextAmplifiers);
|
|
2263
2299
|
}
|
|
2264
2300
|
function setOptions(partial) {
|
|
2265
2301
|
if (ctx.settled) return;
|
|
@@ -2267,6 +2303,7 @@ function runEdit(opts) {
|
|
|
2267
2303
|
const previousOptions = { ...workingOptions ?? {} };
|
|
2268
2304
|
const previousStyle = currentStyle;
|
|
2269
2305
|
const previousNonGeometryDirty = nonGeometryDirty;
|
|
2306
|
+
const previousTextAmplifiers = workingTextAmplifiers;
|
|
2270
2307
|
const previousRendered = rebuildRendered();
|
|
2271
2308
|
replaceWorkingOptions({
|
|
2272
2309
|
...workingOptions ?? {},
|
|
@@ -2282,7 +2319,7 @@ function runEdit(opts) {
|
|
|
2282
2319
|
renderPreview();
|
|
2283
2320
|
nonGeometryDirty = true;
|
|
2284
2321
|
dirty = true;
|
|
2285
|
-
emitChange(previousRendered, previousPoints, previousOptions, previousStyle, previousNonGeometryDirty, sizeAnchor, currentRendered);
|
|
2322
|
+
emitChange(previousRendered, previousPoints, previousOptions, previousStyle, previousNonGeometryDirty, sizeAnchor, currentRendered, previousTextAmplifiers);
|
|
2286
2323
|
}
|
|
2287
2324
|
function setStyle(partial) {
|
|
2288
2325
|
if (ctx.settled) return;
|
|
@@ -2290,6 +2327,7 @@ function runEdit(opts) {
|
|
|
2290
2327
|
const previousOptions = { ...workingOptions ?? {} };
|
|
2291
2328
|
const previousStyle = currentStyle;
|
|
2292
2329
|
const previousNonGeometryDirty = nonGeometryDirty;
|
|
2330
|
+
const previousTextAmplifiers = workingTextAmplifiers;
|
|
2293
2331
|
const previousRendered = rebuildRendered();
|
|
2294
2332
|
currentStyle = {
|
|
2295
2333
|
...currentStyle ?? {},
|
|
@@ -2305,7 +2343,51 @@ function runEdit(opts) {
|
|
|
2305
2343
|
renderPreview();
|
|
2306
2344
|
nonGeometryDirty = true;
|
|
2307
2345
|
dirty = true;
|
|
2308
|
-
emitChange(previousRendered, previousPoints, previousOptions, previousStyle, previousNonGeometryDirty, sizeAnchor, currentRendered);
|
|
2346
|
+
emitChange(previousRendered, previousPoints, previousOptions, previousStyle, previousNonGeometryDirty, sizeAnchor, currentRendered, previousTextAmplifiers);
|
|
2347
|
+
}
|
|
2348
|
+
/**
|
|
2349
|
+
* Shallow-merge into the live working text amplifiers. Mirrors
|
|
2350
|
+
* [[setOptions]] exactly (dirty tracking, re-render, change emission) —
|
|
2351
|
+
* see that function for the shared shape. A merge value of `undefined` or
|
|
2352
|
+
* an empty/whitespace-only string deletes the key; the record collapses to
|
|
2353
|
+
* `undefined` once empty so `renderPreview`/`rebuildRenderedWith` omit the
|
|
2354
|
+
* key rather than persist `{}`.
|
|
2355
|
+
*
|
|
2356
|
+
* Each patch key resolves to its canonical field key and evicts every
|
|
2357
|
+
* working entry for the same field + suffix — including friendly-alias
|
|
2358
|
+
* twins (`uniqueDesignation` for `T`) — so clearing or replacing via a
|
|
2359
|
+
* canonical key cannot be undone by an alias-backed value surviving in the
|
|
2360
|
+
* working record. Non-empty values are stored under the canonical key;
|
|
2361
|
+
* unknown keys keep literal behavior.
|
|
2362
|
+
*/
|
|
2363
|
+
function setTextAmplifiers(partial) {
|
|
2364
|
+
if (ctx.settled) return;
|
|
2365
|
+
const previousPoints = workingPoints.map((p) => [...p]);
|
|
2366
|
+
const previousOptions = { ...workingOptions ?? {} };
|
|
2367
|
+
const previousStyle = currentStyle;
|
|
2368
|
+
const previousNonGeometryDirty = nonGeometryDirty;
|
|
2369
|
+
const previousTextAmplifiers = workingTextAmplifiers;
|
|
2370
|
+
const previousRendered = rebuildRendered();
|
|
2371
|
+
const merged = { ...workingTextAmplifiers ?? {} };
|
|
2372
|
+
for (const [key, value] of Object.entries(partial)) {
|
|
2373
|
+
const canonicalKey = canonicalTextAmplifierKey(key);
|
|
2374
|
+
if (canonicalKey) {
|
|
2375
|
+
for (const existing of Object.keys(merged)) if (canonicalTextAmplifierKey(existing) === canonicalKey) delete merged[existing];
|
|
2376
|
+
} else delete merged[key];
|
|
2377
|
+
if (value !== void 0 && value.trim() !== "") merged[canonicalKey ?? key] = value;
|
|
2378
|
+
}
|
|
2379
|
+
workingTextAmplifiers = Object.keys(merged).length > 0 ? merged : void 0;
|
|
2380
|
+
let currentRendered;
|
|
2381
|
+
try {
|
|
2382
|
+
currentRendered = rebuildRendered();
|
|
2383
|
+
} catch (err) {
|
|
2384
|
+
workingTextAmplifiers = previousTextAmplifiers;
|
|
2385
|
+
throw err;
|
|
2386
|
+
}
|
|
2387
|
+
renderPreview();
|
|
2388
|
+
nonGeometryDirty = true;
|
|
2389
|
+
dirty = true;
|
|
2390
|
+
emitChange(previousRendered, previousPoints, previousOptions, previousStyle, previousNonGeometryDirty, sizeAnchor, currentRendered, previousTextAmplifiers);
|
|
2309
2391
|
}
|
|
2310
2392
|
/**
|
|
2311
2393
|
* Flip the size anchor for the measure under edit (ADR-0020). Re-renders the
|
|
@@ -2321,12 +2403,13 @@ function runEdit(opts) {
|
|
|
2321
2403
|
const previousStyle = currentStyle;
|
|
2322
2404
|
const previousNonGeometryDirty = nonGeometryDirty;
|
|
2323
2405
|
const previousSizeAnchor = sizeAnchor;
|
|
2406
|
+
const previousTextAmplifiers = workingTextAmplifiers;
|
|
2324
2407
|
const previousRendered = rebuildRendered();
|
|
2325
2408
|
sizeAnchor = next;
|
|
2326
2409
|
const currentRendered = rebuildRendered();
|
|
2327
2410
|
nonGeometryDirty = true;
|
|
2328
2411
|
dirty = true;
|
|
2329
|
-
emitChange(previousRendered, previousPoints, previousOptions, previousStyle, previousNonGeometryDirty, previousSizeAnchor, currentRendered);
|
|
2412
|
+
emitChange(previousRendered, previousPoints, previousOptions, previousStyle, previousNonGeometryDirty, previousSizeAnchor, currentRendered, previousTextAmplifiers);
|
|
2330
2413
|
}
|
|
2331
2414
|
/** Apply a translate gesture by shifting every working point by `delta`. */
|
|
2332
2415
|
function commitTranslate(delta) {
|
|
@@ -2384,6 +2467,9 @@ function runEdit(opts) {
|
|
|
2384
2467
|
get style() {
|
|
2385
2468
|
return currentStyle ? { ...currentStyle } : void 0;
|
|
2386
2469
|
},
|
|
2470
|
+
get textAmplifiers() {
|
|
2471
|
+
return workingTextAmplifiers ? { ...workingTextAmplifiers } : void 0;
|
|
2472
|
+
},
|
|
2387
2473
|
get dirty() {
|
|
2388
2474
|
return dirty;
|
|
2389
2475
|
},
|
|
@@ -2415,6 +2501,9 @@ function runEdit(opts) {
|
|
|
2415
2501
|
setStyle(partial) {
|
|
2416
2502
|
setStyle(partial);
|
|
2417
2503
|
},
|
|
2504
|
+
setTextAmplifiers(partial) {
|
|
2505
|
+
setTextAmplifiers(partial);
|
|
2506
|
+
},
|
|
2418
2507
|
setSizeAnchor(next) {
|
|
2419
2508
|
setSizeAnchor(next);
|
|
2420
2509
|
},
|
|
@@ -2957,7 +3046,7 @@ function runGroupTransform(opts) {
|
|
|
2957
3046
|
}
|
|
2958
3047
|
function rescaleOnViewChange() {
|
|
2959
3048
|
if (ctx.settled) return;
|
|
2960
|
-
if (members.some((m) =>
|
|
3049
|
+
if (members.some((m) => measureRendersFromViewState({
|
|
2961
3050
|
kind: m.measure.kind,
|
|
2962
3051
|
options: m.measure.options
|
|
2963
3052
|
}))) renderAllPreviews();
|
|
@@ -3529,17 +3618,18 @@ var TacticalDraw = class {
|
|
|
3529
3618
|
}
|
|
3530
3619
|
/**
|
|
3531
3620
|
* Re-render the graphics layer on a view (zoom) change so pixel-denominated
|
|
3532
|
-
* measures rescale live. Skipped entirely
|
|
3533
|
-
*
|
|
3534
|
-
*
|
|
3535
|
-
*
|
|
3536
|
-
*
|
|
3621
|
+
* and not-yet-committed label-sized measures rescale live. Skipped entirely
|
|
3622
|
+
* when nothing rendered still reads live view state — meter geometry with a
|
|
3623
|
+
* captured label size is zoom-independent, so a rebuild would be wasted
|
|
3624
|
+
* layer writes. During an active edit the edited measure lives on the
|
|
3625
|
+
* preview layer (lifted from graphics), so it is excluded here exactly as
|
|
3626
|
+
* in `render()`; the preview's own view-change subscription rescales it.
|
|
3537
3627
|
*/
|
|
3538
3628
|
handleViewChange = () => {
|
|
3539
3629
|
if (this.destroyed || !this.hasRendered) return;
|
|
3540
3630
|
const excluded = this.excludedInteractionIds();
|
|
3541
3631
|
const visible = excluded.size > 0 ? this.lastRenderedMeasures.filter((m) => !excluded.has(m.id)) : this.lastRenderedMeasures;
|
|
3542
|
-
if (!visible.some(
|
|
3632
|
+
if (!visible.some(measureRendersFromViewState)) return;
|
|
3543
3633
|
this.renderMeasuresToGraphics(visible);
|
|
3544
3634
|
};
|
|
3545
3635
|
/**
|
|
@@ -3761,6 +3851,7 @@ var TacticalDraw = class {
|
|
|
3761
3851
|
measureOptions: draft.options,
|
|
3762
3852
|
measureStyle: draft.style,
|
|
3763
3853
|
measureProperties: draft.properties,
|
|
3854
|
+
measureTextAmplifiers: draft.textAmplifiers,
|
|
3764
3855
|
guide: options?.guide,
|
|
3765
3856
|
sizeAnchor: options?.sizeAnchor,
|
|
3766
3857
|
onSettled
|
|
@@ -3777,6 +3868,7 @@ var TacticalDraw = class {
|
|
|
3777
3868
|
measureOptions: draft.options,
|
|
3778
3869
|
measureStyle: draft.style,
|
|
3779
3870
|
measureProperties: draft.properties,
|
|
3871
|
+
measureTextAmplifiers: draft.textAmplifiers,
|
|
3780
3872
|
guide: options?.guide,
|
|
3781
3873
|
sizeAnchor: options?.sizeAnchor,
|
|
3782
3874
|
onSettled
|
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.13",
|
|
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.
|
|
41
|
+
"@orbat-mapper/control-measures": "^0.2.0-alpha.10"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@arethetypeswrong/core": "^0.18.3",
|