@orbat-mapper/tactical-draw 0.2.0-alpha.29 → 0.2.0-alpha.30
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 +81 -14
- package/dist/index.mjs +305 -167
- package/package.json +2 -2
package/dist/index.d.mts
CHANGED
|
@@ -739,6 +739,27 @@ interface InteractionStyle {
|
|
|
739
739
|
type EditMode = "reshape" | "transform" | "labeldrag" | "delete";
|
|
740
740
|
//#endregion
|
|
741
741
|
//#region src/tactical-draw/session-events.d.ts
|
|
742
|
+
/** A gesture's phase within a stream of {@link TransientChangeEvent}s. */
|
|
743
|
+
type TransientPhase = "start" | "move" | "end";
|
|
744
|
+
/**
|
|
745
|
+
* An in-flight mutation of a graphic's working geometry during a gesture —
|
|
746
|
+
* streamed continuously while the gesture runs (`EditSession.onTransientChange`
|
|
747
|
+
* / `DrawSession.onTransientChange`), distinct from the once-per-completed-
|
|
748
|
+
* gesture `onChange`. Hosts use it for live feedback, e.g. a radius readout
|
|
749
|
+
* while dragging a circle handle.
|
|
750
|
+
*/
|
|
751
|
+
interface TransientChangeEvent {
|
|
752
|
+
/** Gesture phase. `end` fires exactly once per started gesture — on commit AND on cancel/abort. */
|
|
753
|
+
readonly phase: TransientPhase;
|
|
754
|
+
/** The full working control points at this instant (lon/lat). Fresh array per emission. */
|
|
755
|
+
readonly points: readonly Position[];
|
|
756
|
+
/**
|
|
757
|
+
* Index of the live point in `points` — the dragged vertex in edit, the
|
|
758
|
+
* rubber-band point in draw — or `null` when the whole geometry moves at
|
|
759
|
+
* once (box translate / rotate / scale).
|
|
760
|
+
*/
|
|
761
|
+
readonly pointIndex: number | null;
|
|
762
|
+
}
|
|
742
763
|
/**
|
|
743
764
|
* The reason a session (`TransformSession`, `EditSession`, or `DrawSession`)
|
|
744
765
|
* settled, delivered to that session's `onSettled` handler (#191 / #194 /
|
|
@@ -912,6 +933,28 @@ interface EditSession {
|
|
|
912
933
|
* [[onChange]] / [[onCommit]]. Returns an unsubscribe function.
|
|
913
934
|
*/
|
|
914
935
|
onSettled(handler: (reason: SettleReason) => void): () => void;
|
|
936
|
+
/**
|
|
937
|
+
* Subscribe to the continuous stream of in-flight geometry mutations —
|
|
938
|
+
* vertex/midpoint drags and box scale/rotate/translate gestures — fired
|
|
939
|
+
* throughout the gesture rather than once at its end (contrast
|
|
940
|
+
* [[onChange]]). `start` fires at gesture begin, `move` on every pointer
|
|
941
|
+
* tick, and `end` fires exactly once when the gesture finishes by any
|
|
942
|
+
* path: commit, a click-slop no-op (a box body click that never becomes a
|
|
943
|
+
* drag), or session abort/teardown mid-drag. `end` fires **before** the
|
|
944
|
+
* gesture's [[onChange]] emission (and, on the commit path, before
|
|
945
|
+
* [[onCommit]]/[[onSettled]] too), so a listener observing both channels
|
|
946
|
+
* sees the transient stream close first.
|
|
947
|
+
*
|
|
948
|
+
* Label-placement drags (`labeldrag` mode) do not fire this — they move
|
|
949
|
+
* amplifier placements, not control points.
|
|
950
|
+
*
|
|
951
|
+
* Listener exceptions are collected; after every listener has run, they
|
|
952
|
+
* are rethrown (one if single, `AggregateError` otherwise) — mirrors
|
|
953
|
+
* [[onChange]]. Returns an unsubscribe function; registering after the
|
|
954
|
+
* session has settled returns a no-op unsubscribe and the listener never
|
|
955
|
+
* fires.
|
|
956
|
+
*/
|
|
957
|
+
onTransientChange(listener: (event: TransientChangeEvent) => void): () => void;
|
|
915
958
|
}
|
|
916
959
|
/**
|
|
917
960
|
* Delivered to `EditSession.onChange` listeners once per completed gesture.
|
|
@@ -1264,6 +1307,24 @@ interface DrawSession {
|
|
|
1264
1307
|
* [[onChange]] / [[onCommit]]. Returns an unsubscribe function.
|
|
1265
1308
|
*/
|
|
1266
1309
|
onSettled(handler: (reason: SettleReason) => void): () => void;
|
|
1310
|
+
/**
|
|
1311
|
+
* Subscribe to the continuous stream of rubber-band pointer ticks while the
|
|
1312
|
+
* draw is live — contrast [[onChange]], which fires once per committed
|
|
1313
|
+
* point. `start` fires on the first rubber-band tick, `move` on every
|
|
1314
|
+
* subsequent tick; a click that commits a point does not itself emit — the
|
|
1315
|
+
* next rubber-band tick after it does. `end` fires exactly once, when the
|
|
1316
|
+
* session settles (commit or abort).
|
|
1317
|
+
*
|
|
1318
|
+
* `points` is the committed points with the live rubber-band point spliced
|
|
1319
|
+
* in at its insertion index; `pointIndex` is that index.
|
|
1320
|
+
*
|
|
1321
|
+
* Listener exceptions are collected; after every listener has run, they
|
|
1322
|
+
* are rethrown (one if single, `AggregateError` otherwise) — mirrors
|
|
1323
|
+
* [[onChange]]. Returns an unsubscribe function; registering after the
|
|
1324
|
+
* session has settled returns a no-op unsubscribe and the listener never
|
|
1325
|
+
* fires.
|
|
1326
|
+
*/
|
|
1327
|
+
onTransientChange(listener: (event: TransientChangeEvent) => void): () => void;
|
|
1267
1328
|
}
|
|
1268
1329
|
//#endregion
|
|
1269
1330
|
//#region src/tactical-draw/snapping.d.ts
|
|
@@ -1465,9 +1526,13 @@ type DrawMeasureDraft<K extends ControlMeasureKind = ControlMeasureKind> = {
|
|
|
1465
1526
|
interface DrawOptions {
|
|
1466
1527
|
signal?: AbortSignal;
|
|
1467
1528
|
/**
|
|
1468
|
-
* Called synchronously once when a
|
|
1469
|
-
*
|
|
1470
|
-
*
|
|
1529
|
+
* Called synchronously once when a draw starts, before any pointer event
|
|
1530
|
+
* is processed. The session lets a host wire a Done/Cancel toolbar to the
|
|
1531
|
+
* active draw and subscribe to live control-point / transient-geometry
|
|
1532
|
+
* feedback. Surfaced for both variable- and fixed-length kinds — a
|
|
1533
|
+
* fixed-length session auto-commits on the last required click, so its
|
|
1534
|
+
* main value is `onTransientChange` (e.g. a live radius readout) and
|
|
1535
|
+
* `abort()`.
|
|
1471
1536
|
*/
|
|
1472
1537
|
onSession?: (session: DrawSession) => void;
|
|
1473
1538
|
/**
|
|
@@ -1666,17 +1731,19 @@ declare class TacticalDraw {
|
|
|
1666
1731
|
*/
|
|
1667
1732
|
private trackTransformSession;
|
|
1668
1733
|
/**
|
|
1669
|
-
* Start an interactive draw.
|
|
1670
|
-
*
|
|
1671
|
-
*
|
|
1672
|
-
*
|
|
1673
|
-
*
|
|
1734
|
+
* Start an interactive draw. Both fixed- and variable-length kinds surface
|
|
1735
|
+
* a `DrawSession` via `opts.onSession`. Fixed-length kinds commit
|
|
1736
|
+
* automatically on the last required click; variable-length kinds commit
|
|
1737
|
+
* on `session.commit()`, `dblclick`, or a second click on the last added
|
|
1738
|
+
* control point. All paths reject with `TacticalDrawAbortError` on
|
|
1739
|
+
* signal / Escape / destroy / `session.abort()`.
|
|
1674
1740
|
*
|
|
1675
|
-
*
|
|
1676
|
-
*
|
|
1677
|
-
*
|
|
1678
|
-
*
|
|
1679
|
-
*
|
|
1741
|
+
* `DrawSession.onCommit` is the single delivery channel for the committed
|
|
1742
|
+
* snapshot and `DrawSession.onSettled` is the lifecycle signal for when the
|
|
1743
|
+
* session is over and why (#197) — together they make the returned promise
|
|
1744
|
+
* non-load-bearing for hosts that adopt them. `DrawSession.onTransientChange`
|
|
1745
|
+
* streams live rubber-band geometry while the draw is in flight, for both
|
|
1746
|
+
* kinds of session.
|
|
1680
1747
|
*/
|
|
1681
1748
|
draw(draft: DrawPointSymbolDraft, options?: DrawOptions): Promise<GraphicSnapshot<PointSymbol>>;
|
|
1682
1749
|
draw<K extends ControlMeasureKind>(draft: DrawMeasureDraft<K>, options?: DrawOptions): Promise<GraphicSnapshot<ControlMeasure<K>>>;
|
|
@@ -1964,4 +2031,4 @@ declare function nearestWorldCopyLng(lng: number, refLng: number): number;
|
|
|
1964
2031
|
/** `nearestWorldCopyLng` applied to a lon/lat position. */
|
|
1965
2032
|
declare function nearestWorldCopyPosition(position: Position, refLng: number): Position;
|
|
1966
2033
|
//#endregion
|
|
1967
|
-
export { BaseMapAdapter, type CombinedAbort, DEFAULT_LABEL_SIZE_PIXELS, DEFAULT_TEXT_SIZE_MAX_PIXELS, DEFAULT_TEXT_SIZE_MIN_PIXELS, type DrawMeasureDraft, type DrawOptions, type DrawPointSymbolDraft, type DrawSession, type EditChangeEvent, type EditManyOptions, type EditMode, type EditOptions, type EditPointerDriver, type EditSession, type FeatureOps, type Graphic, type GraphicEditChangeEvent, type GraphicEditSession, type GraphicGeometrySnappingOptions, type GraphicRender, type GraphicSnapshot, type GuideStyle, HATCH_PATTERNS, type HandleStyle, type HatchPattern, type IconResource, type InteractionHit, type InteractionStyle, type LayerId, type MapAdapter, type MapCursor, type MapEvent, type MapEventHandler, type NativeEventEntry, type PickEvent, type PickHandler, type PickOptions, type PixelCoordinate, type PointSymbol, type PointSymbolAnchor, type PointSymbolCapability, PointSymbolCapabilityRequiredError, type PointSymbolColors, type PointSymbolFootprint, type PointSymbolGraphicModifiers, type PointSymbolRender, type PointSymbolSize, type PointSymbolStyle, type PointerCallback, PointerCallbackHub, type PointerEventInfo, type SessionChangeSource, type SessionHistory, type SessionHistoryState, type SettleReason, type SizeAnchor, type SnapCandidate, type SnapCandidateProvider, type SnapCandidateRequest, type SnapInteraction, type SnapResult, type SnappingOptions, type StyleOptions, TOUCH_HIT_TOLERANCE_PX, TacticalDraw, TacticalDrawAbortError, type TacticalDrawAbortReason, TacticalDrawDestroyedError, type TacticalDrawLayerOptions, type TacticalDrawOptions, type TextStyleOptions, type ToPixel, type TransformBox, type TransformChangeEvent, type TransformSession, bindAbortable, clonePointSymbol, coerceHandleStyle, combineWithHostSignal, effectivePointSymbolPixelSize, fillPatternImageDimension, fillPatternImageId, getSizeAnchor, hitTestFeature, hitTestIconFeature, ignoreAbort, isPointSymbol, isTacticalDrawAbortError, makeFillPatternImage, nearestWorldCopyLng, nearestWorldCopyPosition, pickHitTolerance, pointSymbolFootprint, resizePointSymbolSize, resolveGroundTextSizePixels, tryControlMeasureIdFromFeature };
|
|
2034
|
+
export { BaseMapAdapter, type CombinedAbort, DEFAULT_LABEL_SIZE_PIXELS, DEFAULT_TEXT_SIZE_MAX_PIXELS, DEFAULT_TEXT_SIZE_MIN_PIXELS, type DrawMeasureDraft, type DrawOptions, type DrawPointSymbolDraft, type DrawSession, type EditChangeEvent, type EditManyOptions, type EditMode, type EditOptions, type EditPointerDriver, type EditSession, type FeatureOps, type Graphic, type GraphicEditChangeEvent, type GraphicEditSession, type GraphicGeometrySnappingOptions, type GraphicRender, type GraphicSnapshot, type GuideStyle, HATCH_PATTERNS, type HandleStyle, type HatchPattern, type IconResource, type InteractionHit, type InteractionStyle, type LayerId, type MapAdapter, type MapCursor, type MapEvent, type MapEventHandler, type NativeEventEntry, type PickEvent, type PickHandler, type PickOptions, type PixelCoordinate, type PointSymbol, type PointSymbolAnchor, type PointSymbolCapability, PointSymbolCapabilityRequiredError, type PointSymbolColors, type PointSymbolFootprint, type PointSymbolGraphicModifiers, type PointSymbolRender, type PointSymbolSize, type PointSymbolStyle, type PointerCallback, PointerCallbackHub, type PointerEventInfo, type SessionChangeSource, type SessionHistory, type SessionHistoryState, type SettleReason, type SizeAnchor, type SnapCandidate, type SnapCandidateProvider, type SnapCandidateRequest, type SnapInteraction, type SnapResult, type SnappingOptions, type StyleOptions, TOUCH_HIT_TOLERANCE_PX, TacticalDraw, TacticalDrawAbortError, type TacticalDrawAbortReason, TacticalDrawDestroyedError, type TacticalDrawLayerOptions, type TacticalDrawOptions, type TextStyleOptions, type ToPixel, type TransformBox, type TransformChangeEvent, type TransformSession, type TransientChangeEvent, type TransientPhase, bindAbortable, clonePointSymbol, coerceHandleStyle, combineWithHostSignal, effectivePointSymbolPixelSize, fillPatternImageDimension, fillPatternImageId, getSizeAnchor, hitTestFeature, hitTestIconFeature, ignoreAbort, isPointSymbol, isTacticalDrawAbortError, makeFillPatternImage, nearestWorldCopyLng, nearestWorldCopyPosition, pickHitTolerance, pointSymbolFootprint, resizePointSymbolSize, resolveGroundTextSizePixels, tryControlMeasureIdFromFeature };
|
package/dist/index.mjs
CHANGED
|
@@ -1220,156 +1220,6 @@ function createSnapResolver(adapter, getGraphics, getGeometryFeatures, getOption
|
|
|
1220
1220
|
};
|
|
1221
1221
|
}
|
|
1222
1222
|
//#endregion
|
|
1223
|
-
//#region src/tactical-draw/draw-controller.ts
|
|
1224
|
-
const PREVIEW_CM_ID$1 = "__td_preview__";
|
|
1225
|
-
const PREVIEW_CURSOR$1 = "crosshair";
|
|
1226
|
-
/**
|
|
1227
|
-
* Drive an interactive fixed-length draw against a `MapAdapter`.
|
|
1228
|
-
*
|
|
1229
|
-
* Caller is the `TacticalDraw` façade. The controller owns:
|
|
1230
|
-
* - control-point accumulation via `click`
|
|
1231
|
-
* - rubber-band preview via `pointermove`
|
|
1232
|
-
* - Escape cancellation via `keydown`
|
|
1233
|
-
* - cursor capture / restore for the duration of the draw
|
|
1234
|
-
* - host `AbortSignal` plumbing and reason mapping
|
|
1235
|
-
*
|
|
1236
|
-
* Auto-commits when the user clicks the final required point — fixed-length
|
|
1237
|
-
* kinds need no host UI.
|
|
1238
|
-
*/
|
|
1239
|
-
function runFixedLengthDraw(options) {
|
|
1240
|
-
const { adapter, kind, previewLayer, guideLayer, graphicsStyle, generateId, signal, measureOptions, measureStyle, measureProperties, measureTextAmplifiers, guide: guideOpt, sizeAnchor, onSettled, snap } = options;
|
|
1241
|
-
const metadata = getControlMeasureMetadata(kind);
|
|
1242
|
-
const rule = metadata.rule;
|
|
1243
|
-
const min = metadata.minCoordinates;
|
|
1244
|
-
const max = metadata.maxCoordinates;
|
|
1245
|
-
let required;
|
|
1246
|
-
if (rule) required = rule.minimumUserPoints;
|
|
1247
|
-
else if (typeof min === "number" && typeof max === "number" && min === max) required = min;
|
|
1248
|
-
else throw new Error(`runFixedLengthDraw: kind "${kind}" is not fixed-length (min=${min}, max=${max})`);
|
|
1249
|
-
const renderOpts = graphicsStyle ? { graphicsStyle } : void 0;
|
|
1250
|
-
const guideEnabled = (guideOpt ?? true) && !shouldSuppressGuide(metadata);
|
|
1251
|
-
const isAreaKind = metadata.geometry === "area";
|
|
1252
|
-
const committed = [];
|
|
1253
|
-
let rubberBand;
|
|
1254
|
-
let ctx;
|
|
1255
|
-
const savedCursor = "";
|
|
1256
|
-
const handleClick = (event) => {
|
|
1257
|
-
if (ctx.settled) return;
|
|
1258
|
-
committed.push(snappedDrawPosition(snap, event));
|
|
1259
|
-
rubberBand = void 0;
|
|
1260
|
-
if (committed.length >= required) {
|
|
1261
|
-
ctx.commit(buildSnapshot);
|
|
1262
|
-
return;
|
|
1263
|
-
}
|
|
1264
|
-
renderPreview();
|
|
1265
|
-
};
|
|
1266
|
-
const handlePointerMove = (event) => {
|
|
1267
|
-
if (ctx.settled) return;
|
|
1268
|
-
if (committed.length === 0) return;
|
|
1269
|
-
rubberBand = snappedDrawPosition(snap, event);
|
|
1270
|
-
renderPreview();
|
|
1271
|
-
};
|
|
1272
|
-
function renderPreview() {
|
|
1273
|
-
const raw = rubberBand !== void 0 ? [...committed, rubberBand] : [...committed];
|
|
1274
|
-
if (raw.length === 0) {
|
|
1275
|
-
ctx.writeLayer(previewLayer, []);
|
|
1276
|
-
ctx.writeLayer(guideLayer, []);
|
|
1277
|
-
return;
|
|
1278
|
-
}
|
|
1279
|
-
const previewThreshold = rule?.minimumPreviewPoints ?? required;
|
|
1280
|
-
const features = buildPreviewFeatures$1(adapter, kind, rule && raw.length >= previewThreshold ? rule.derive(raw) : raw, measureOptions, measureStyle, measureTextAmplifiers, renderOpts);
|
|
1281
|
-
ctx.writeLayer(previewLayer, features);
|
|
1282
|
-
const guidePoints = rule?.guidePoints ? rule.guidePoints(raw) : raw;
|
|
1283
|
-
const guideFeatures = guideEnabled ? buildGuideFeatures(guidePoints, isAreaKind) : [];
|
|
1284
|
-
ctx.writeLayer(guideLayer, guideFeatures);
|
|
1285
|
-
}
|
|
1286
|
-
function rescaleOnViewChange() {
|
|
1287
|
-
if (!measureRendersFromViewState({
|
|
1288
|
-
kind,
|
|
1289
|
-
options: measureOptions
|
|
1290
|
-
})) return;
|
|
1291
|
-
renderPreview();
|
|
1292
|
-
}
|
|
1293
|
-
function buildSnapshot() {
|
|
1294
|
-
const finalPoints = roundControlPoints(rule ? rule.derive(committed) : committed);
|
|
1295
|
-
const captured = withCommittedAdapterMeasureOptions({
|
|
1296
|
-
id: generateId(),
|
|
1297
|
-
kind,
|
|
1298
|
-
controlPoints: finalPoints,
|
|
1299
|
-
...measureOptions !== void 0 ? { options: measureOptions } : {},
|
|
1300
|
-
...measureStyle !== void 0 ? { style: measureStyle } : {},
|
|
1301
|
-
...measureProperties !== void 0 ? { properties: measureProperties } : {},
|
|
1302
|
-
...measureTextAmplifiers !== void 0 ? { textAmplifiers: measureTextAmplifiers } : {}
|
|
1303
|
-
});
|
|
1304
|
-
const measure = sizeAnchor === "screen" ? captured : bakePixelSizeToGround(captured, adapter);
|
|
1305
|
-
return {
|
|
1306
|
-
graphic: cloneControlMeasure(measure),
|
|
1307
|
-
render: renderMeasureForAdapter(adapter, measure, renderOpts)
|
|
1308
|
-
};
|
|
1309
|
-
}
|
|
1310
|
-
return runInteraction({
|
|
1311
|
-
adapter,
|
|
1312
|
-
signal,
|
|
1313
|
-
layers: [previewLayer, guideLayer],
|
|
1314
|
-
onViewChange: rescaleOnViewChange,
|
|
1315
|
-
onSettled,
|
|
1316
|
-
attach(c) {
|
|
1317
|
-
ctx = c;
|
|
1318
|
-
c.onTeardown(() => {
|
|
1319
|
-
adapter.off("click", handleClick);
|
|
1320
|
-
adapter.off("pointermove", handlePointerMove);
|
|
1321
|
-
adapter.setCursor(savedCursor);
|
|
1322
|
-
});
|
|
1323
|
-
adapter.setCursor(PREVIEW_CURSOR$1);
|
|
1324
|
-
adapter.on("click", handleClick);
|
|
1325
|
-
adapter.on("pointermove", handlePointerMove);
|
|
1326
|
-
}
|
|
1327
|
-
});
|
|
1328
|
-
}
|
|
1329
|
-
/**
|
|
1330
|
-
* Produce preview features for the partial geometry. Tries the full renderer
|
|
1331
|
-
* first so the preview looks like the real symbol once enough points are in;
|
|
1332
|
-
* falls back to a plain Point/LineString outline when the kind's generator
|
|
1333
|
-
* cannot produce a shape yet (under-min count).
|
|
1334
|
-
*/
|
|
1335
|
-
function buildPreviewFeatures$1(adapter, kind, points, measureOptions, measureStyle, measureTextAmplifiers, renderOpts) {
|
|
1336
|
-
const measure = {
|
|
1337
|
-
id: PREVIEW_CM_ID$1,
|
|
1338
|
-
kind,
|
|
1339
|
-
controlPoints: points,
|
|
1340
|
-
...measureOptions !== void 0 ? { options: measureOptions } : {},
|
|
1341
|
-
...measureStyle !== void 0 ? { style: measureStyle } : {},
|
|
1342
|
-
...measureTextAmplifiers !== void 0 ? { textAmplifiers: measureTextAmplifiers } : {}
|
|
1343
|
-
};
|
|
1344
|
-
try {
|
|
1345
|
-
return renderMeasureForAdapter(adapter, measure, {
|
|
1346
|
-
...renderOpts,
|
|
1347
|
-
validationMode: "throw"
|
|
1348
|
-
}).features;
|
|
1349
|
-
} catch {
|
|
1350
|
-
return outlinePreview$1(points);
|
|
1351
|
-
}
|
|
1352
|
-
}
|
|
1353
|
-
function outlinePreview$1(points) {
|
|
1354
|
-
const features = [];
|
|
1355
|
-
for (let i = 0; i < points.length; i++) {
|
|
1356
|
-
const pt = {
|
|
1357
|
-
type: "Feature",
|
|
1358
|
-
id: `${PREVIEW_CM_ID$1}:vertex:${i}`,
|
|
1359
|
-
geometry: {
|
|
1360
|
-
type: "Point",
|
|
1361
|
-
coordinates: points[i]
|
|
1362
|
-
},
|
|
1363
|
-
properties: {
|
|
1364
|
-
part: "vertex",
|
|
1365
|
-
index: i
|
|
1366
|
-
}
|
|
1367
|
-
};
|
|
1368
|
-
features.push(pt);
|
|
1369
|
-
}
|
|
1370
|
-
return features;
|
|
1371
|
-
}
|
|
1372
|
-
//#endregion
|
|
1373
1223
|
//#region src/tactical-draw/session-events.ts
|
|
1374
1224
|
/**
|
|
1375
1225
|
* Map a settle-time error to its `SettleReason`: a `TacticalDrawAbortError`
|
|
@@ -1514,10 +1364,13 @@ function createChangeEmitter(options) {
|
|
|
1514
1364
|
};
|
|
1515
1365
|
}
|
|
1516
1366
|
/**
|
|
1517
|
-
*
|
|
1518
|
-
*
|
|
1519
|
-
*
|
|
1520
|
-
*
|
|
1367
|
+
* Channel-neutral subscribe/emit machinery: a listener set with synchronous,
|
|
1368
|
+
* exception-collecting emission (single rethrow, `AggregateError` otherwise).
|
|
1369
|
+
* Named for its original `onCommit` channel (`TransformSession` /
|
|
1370
|
+
* `EditSession` / `DrawSession`), it also carries the `onTransientChange`
|
|
1371
|
+
* streams and the fixed/variable draw controllers' `onChange` — any
|
|
1372
|
+
* multi-emit channel with no latch. Unlike `createSettleEmitter`, `emit` may
|
|
1373
|
+
* be called more than
|
|
1521
1374
|
* once — a transform's `setGraphics` can commit several removal batches before
|
|
1522
1375
|
* the close batch — and there is no latch or late-registration path:
|
|
1523
1376
|
* registration-time `ctx.settled` guards stay in the controllers, mirroring
|
|
@@ -1546,6 +1399,232 @@ function createCommitEmitter(listenerErrorsMessage) {
|
|
|
1546
1399
|
};
|
|
1547
1400
|
}
|
|
1548
1401
|
//#endregion
|
|
1402
|
+
//#region src/tactical-draw/draw-controller.ts
|
|
1403
|
+
const PREVIEW_CM_ID$1 = "__td_preview__";
|
|
1404
|
+
const PREVIEW_CURSOR$1 = "crosshair";
|
|
1405
|
+
/**
|
|
1406
|
+
* Drive an interactive fixed-length draw against a `MapAdapter`.
|
|
1407
|
+
*
|
|
1408
|
+
* Caller is the `TacticalDraw` façade. The controller owns:
|
|
1409
|
+
* - control-point accumulation via `click`
|
|
1410
|
+
* - rubber-band preview via `pointermove`
|
|
1411
|
+
* - Escape cancellation via `keydown`
|
|
1412
|
+
* - cursor capture / restore for the duration of the draw
|
|
1413
|
+
* - host `AbortSignal` plumbing and reason mapping
|
|
1414
|
+
*
|
|
1415
|
+
* Auto-commits when the user clicks the final required point — fixed-length
|
|
1416
|
+
* kinds need no host UI.
|
|
1417
|
+
*/
|
|
1418
|
+
function runFixedLengthDraw(options) {
|
|
1419
|
+
const { adapter, kind, previewLayer, guideLayer, graphicsStyle, generateId, signal, measureOptions, measureStyle, measureProperties, measureTextAmplifiers, guide: guideOpt, sizeAnchor, onSettled, onSession, snap } = options;
|
|
1420
|
+
const metadata = getControlMeasureMetadata(kind);
|
|
1421
|
+
const rule = metadata.rule;
|
|
1422
|
+
const min = metadata.minCoordinates;
|
|
1423
|
+
const max = metadata.maxCoordinates;
|
|
1424
|
+
let required;
|
|
1425
|
+
if (rule) required = rule.minimumUserPoints;
|
|
1426
|
+
else if (typeof min === "number" && typeof max === "number" && min === max) required = min;
|
|
1427
|
+
else throw new Error(`runFixedLengthDraw: kind "${kind}" is not fixed-length (min=${min}, max=${max})`);
|
|
1428
|
+
const renderOpts = graphicsStyle ? { graphicsStyle } : void 0;
|
|
1429
|
+
const guideEnabled = (guideOpt ?? true) && !shouldSuppressGuide(metadata);
|
|
1430
|
+
const isAreaKind = metadata.geometry === "area";
|
|
1431
|
+
const committed = [];
|
|
1432
|
+
let rubberBand;
|
|
1433
|
+
let ctx;
|
|
1434
|
+
const savedCursor = "";
|
|
1435
|
+
const changeEmitter = createCommitEmitter("DrawSession.onChange listener errors");
|
|
1436
|
+
const commitEmitter = createCommitEmitter("DrawSession.onCommit listener errors");
|
|
1437
|
+
const settleEmitter = createSettleEmitter("DrawSession.onSettled listener errors");
|
|
1438
|
+
const transientEmitter = createCommitEmitter("DrawSession.onTransientChange listener errors");
|
|
1439
|
+
/** Latch: `true` once the first rubber-band tick has fired `"start"`, until `"end"` fires on settle. */
|
|
1440
|
+
let transientStarted = false;
|
|
1441
|
+
function canCommitNow() {
|
|
1442
|
+
return !ctx.settled && committed.length >= required;
|
|
1443
|
+
}
|
|
1444
|
+
const session = {
|
|
1445
|
+
get controlPoints() {
|
|
1446
|
+
return committed.map((p) => [...p]);
|
|
1447
|
+
},
|
|
1448
|
+
get canCommit() {
|
|
1449
|
+
return canCommitNow();
|
|
1450
|
+
},
|
|
1451
|
+
get minControlPoints() {
|
|
1452
|
+
return required;
|
|
1453
|
+
},
|
|
1454
|
+
get maxControlPoints() {
|
|
1455
|
+
return required;
|
|
1456
|
+
},
|
|
1457
|
+
commit() {
|
|
1458
|
+
if (!canCommitNow()) return false;
|
|
1459
|
+
ctx.commit(buildSnapshot);
|
|
1460
|
+
return true;
|
|
1461
|
+
},
|
|
1462
|
+
abort() {
|
|
1463
|
+
if (ctx.settled) return;
|
|
1464
|
+
ctx.fail(new TacticalDrawAbortError("session"));
|
|
1465
|
+
},
|
|
1466
|
+
onChange(listener) {
|
|
1467
|
+
if (ctx.settled) return () => {};
|
|
1468
|
+
return changeEmitter.register(listener);
|
|
1469
|
+
},
|
|
1470
|
+
onCommit(listener) {
|
|
1471
|
+
if (ctx.settled) return () => {};
|
|
1472
|
+
return commitEmitter.register(listener);
|
|
1473
|
+
},
|
|
1474
|
+
onSettled(handler) {
|
|
1475
|
+
return settleEmitter.register(handler);
|
|
1476
|
+
},
|
|
1477
|
+
onTransientChange(listener) {
|
|
1478
|
+
if (ctx.settled) return () => {};
|
|
1479
|
+
return transientEmitter.register(listener);
|
|
1480
|
+
}
|
|
1481
|
+
};
|
|
1482
|
+
function fireChange() {
|
|
1483
|
+
if (ctx.settled) return;
|
|
1484
|
+
changeEmitter.emit(session);
|
|
1485
|
+
}
|
|
1486
|
+
const handleClick = (event) => {
|
|
1487
|
+
if (ctx.settled) return;
|
|
1488
|
+
committed.push(snappedDrawPosition(snap, event));
|
|
1489
|
+
rubberBand = void 0;
|
|
1490
|
+
if (committed.length >= required) {
|
|
1491
|
+
ctx.commit(buildSnapshot);
|
|
1492
|
+
return;
|
|
1493
|
+
}
|
|
1494
|
+
renderPreview();
|
|
1495
|
+
try {
|
|
1496
|
+
fireChange();
|
|
1497
|
+
} catch (err) {
|
|
1498
|
+
ctx.fail(err);
|
|
1499
|
+
}
|
|
1500
|
+
};
|
|
1501
|
+
const handlePointerMove = (event) => {
|
|
1502
|
+
if (ctx.settled) return;
|
|
1503
|
+
if (committed.length === 0) return;
|
|
1504
|
+
rubberBand = snappedDrawPosition(snap, event);
|
|
1505
|
+
renderPreview();
|
|
1506
|
+
const points = [...committed, rubberBand];
|
|
1507
|
+
const phase = transientStarted ? "move" : "start";
|
|
1508
|
+
transientStarted = true;
|
|
1509
|
+
transientEmitter.emit({
|
|
1510
|
+
phase,
|
|
1511
|
+
points,
|
|
1512
|
+
pointIndex: committed.length
|
|
1513
|
+
});
|
|
1514
|
+
};
|
|
1515
|
+
function renderPreview() {
|
|
1516
|
+
const raw = rubberBand !== void 0 ? [...committed, rubberBand] : [...committed];
|
|
1517
|
+
if (raw.length === 0) {
|
|
1518
|
+
ctx.writeLayer(previewLayer, []);
|
|
1519
|
+
ctx.writeLayer(guideLayer, []);
|
|
1520
|
+
return;
|
|
1521
|
+
}
|
|
1522
|
+
const previewThreshold = rule?.minimumPreviewPoints ?? required;
|
|
1523
|
+
const features = buildPreviewFeatures$1(adapter, kind, rule && raw.length >= previewThreshold ? rule.derive(raw) : raw, measureOptions, measureStyle, measureTextAmplifiers, renderOpts);
|
|
1524
|
+
ctx.writeLayer(previewLayer, features);
|
|
1525
|
+
const guidePoints = rule?.guidePoints ? rule.guidePoints(raw) : raw;
|
|
1526
|
+
const guideFeatures = guideEnabled ? buildGuideFeatures(guidePoints, isAreaKind) : [];
|
|
1527
|
+
ctx.writeLayer(guideLayer, guideFeatures);
|
|
1528
|
+
}
|
|
1529
|
+
function rescaleOnViewChange() {
|
|
1530
|
+
if (!measureRendersFromViewState({
|
|
1531
|
+
kind,
|
|
1532
|
+
options: measureOptions
|
|
1533
|
+
})) return;
|
|
1534
|
+
renderPreview();
|
|
1535
|
+
}
|
|
1536
|
+
function buildSnapshot() {
|
|
1537
|
+
const finalPoints = roundControlPoints(rule ? rule.derive(committed) : committed);
|
|
1538
|
+
const captured = withCommittedAdapterMeasureOptions({
|
|
1539
|
+
id: generateId(),
|
|
1540
|
+
kind,
|
|
1541
|
+
controlPoints: finalPoints,
|
|
1542
|
+
...measureOptions !== void 0 ? { options: measureOptions } : {},
|
|
1543
|
+
...measureStyle !== void 0 ? { style: measureStyle } : {},
|
|
1544
|
+
...measureProperties !== void 0 ? { properties: measureProperties } : {},
|
|
1545
|
+
...measureTextAmplifiers !== void 0 ? { textAmplifiers: measureTextAmplifiers } : {}
|
|
1546
|
+
});
|
|
1547
|
+
const measure = sizeAnchor === "screen" ? captured : bakePixelSizeToGround(captured, adapter);
|
|
1548
|
+
return {
|
|
1549
|
+
graphic: cloneControlMeasure(measure),
|
|
1550
|
+
render: renderMeasureForAdapter(adapter, measure, renderOpts)
|
|
1551
|
+
};
|
|
1552
|
+
}
|
|
1553
|
+
return runInteraction({
|
|
1554
|
+
adapter,
|
|
1555
|
+
signal,
|
|
1556
|
+
layers: [previewLayer, guideLayer],
|
|
1557
|
+
onViewChange: rescaleOnViewChange,
|
|
1558
|
+
onSettled: (settlement) => {
|
|
1559
|
+
if (onSettled) onSettled();
|
|
1560
|
+
if (transientStarted) {
|
|
1561
|
+
transientStarted = false;
|
|
1562
|
+
transientEmitter.emit({
|
|
1563
|
+
phase: "end",
|
|
1564
|
+
points: committed.map((p) => [...p]),
|
|
1565
|
+
pointIndex: null
|
|
1566
|
+
});
|
|
1567
|
+
}
|
|
1568
|
+
settleFromInteraction(settlement, commitEmitter, settleEmitter);
|
|
1569
|
+
},
|
|
1570
|
+
attach(c) {
|
|
1571
|
+
ctx = c;
|
|
1572
|
+
c.onTeardown(() => {
|
|
1573
|
+
adapter.off("click", handleClick);
|
|
1574
|
+
adapter.off("pointermove", handlePointerMove);
|
|
1575
|
+
adapter.setCursor(savedCursor);
|
|
1576
|
+
});
|
|
1577
|
+
adapter.setCursor(PREVIEW_CURSOR$1);
|
|
1578
|
+
adapter.on("click", handleClick);
|
|
1579
|
+
adapter.on("pointermove", handlePointerMove);
|
|
1580
|
+
if (onSession) onSession(session);
|
|
1581
|
+
}
|
|
1582
|
+
});
|
|
1583
|
+
}
|
|
1584
|
+
/**
|
|
1585
|
+
* Produce preview features for the partial geometry. Tries the full renderer
|
|
1586
|
+
* first so the preview looks like the real symbol once enough points are in;
|
|
1587
|
+
* falls back to a plain Point/LineString outline when the kind's generator
|
|
1588
|
+
* cannot produce a shape yet (under-min count).
|
|
1589
|
+
*/
|
|
1590
|
+
function buildPreviewFeatures$1(adapter, kind, points, measureOptions, measureStyle, measureTextAmplifiers, renderOpts) {
|
|
1591
|
+
const measure = {
|
|
1592
|
+
id: PREVIEW_CM_ID$1,
|
|
1593
|
+
kind,
|
|
1594
|
+
controlPoints: points,
|
|
1595
|
+
...measureOptions !== void 0 ? { options: measureOptions } : {},
|
|
1596
|
+
...measureStyle !== void 0 ? { style: measureStyle } : {},
|
|
1597
|
+
...measureTextAmplifiers !== void 0 ? { textAmplifiers: measureTextAmplifiers } : {}
|
|
1598
|
+
};
|
|
1599
|
+
try {
|
|
1600
|
+
return renderMeasureForAdapter(adapter, measure, {
|
|
1601
|
+
...renderOpts,
|
|
1602
|
+
validationMode: "throw"
|
|
1603
|
+
}).features;
|
|
1604
|
+
} catch {
|
|
1605
|
+
return outlinePreview$1(points);
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
function outlinePreview$1(points) {
|
|
1609
|
+
const features = [];
|
|
1610
|
+
for (let i = 0; i < points.length; i++) {
|
|
1611
|
+
const pt = {
|
|
1612
|
+
type: "Feature",
|
|
1613
|
+
id: `${PREVIEW_CM_ID$1}:vertex:${i}`,
|
|
1614
|
+
geometry: {
|
|
1615
|
+
type: "Point",
|
|
1616
|
+
coordinates: points[i]
|
|
1617
|
+
},
|
|
1618
|
+
properties: {
|
|
1619
|
+
part: "vertex",
|
|
1620
|
+
index: i
|
|
1621
|
+
}
|
|
1622
|
+
};
|
|
1623
|
+
features.push(pt);
|
|
1624
|
+
}
|
|
1625
|
+
return features;
|
|
1626
|
+
}
|
|
1627
|
+
//#endregion
|
|
1549
1628
|
//#region src/tactical-draw/draw-controller-variable.ts
|
|
1550
1629
|
const PREVIEW_CM_ID = "__td_preview__";
|
|
1551
1630
|
const PREVIEW_CURSOR = "crosshair";
|
|
@@ -1603,9 +1682,12 @@ function runVariableLengthDraw(options) {
|
|
|
1603
1682
|
let rubberBand;
|
|
1604
1683
|
let ctx;
|
|
1605
1684
|
const savedCursor = "";
|
|
1606
|
-
const
|
|
1685
|
+
const changeEmitter = createCommitEmitter("DrawSession.onChange listener errors");
|
|
1607
1686
|
const commitEmitter = createCommitEmitter("DrawSession.onCommit listener errors");
|
|
1608
1687
|
const settleEmitter = createSettleEmitter("DrawSession.onSettled listener errors");
|
|
1688
|
+
const transientEmitter = createCommitEmitter("DrawSession.onTransientChange listener errors");
|
|
1689
|
+
/** Latch: `true` once the first rubber-band tick has fired `"start"`, until `"end"` fires on settle. */
|
|
1690
|
+
let transientStarted = false;
|
|
1609
1691
|
function canCommitNow() {
|
|
1610
1692
|
const n = committed.length;
|
|
1611
1693
|
if (typeof max === "number" && n > max) return false;
|
|
@@ -1638,10 +1720,7 @@ function runVariableLengthDraw(options) {
|
|
|
1638
1720
|
},
|
|
1639
1721
|
onChange(listener) {
|
|
1640
1722
|
if (ctx.settled) return () => {};
|
|
1641
|
-
|
|
1642
|
-
return () => {
|
|
1643
|
-
changeListeners.delete(listener);
|
|
1644
|
-
};
|
|
1723
|
+
return changeEmitter.register(listener);
|
|
1645
1724
|
},
|
|
1646
1725
|
onCommit(listener) {
|
|
1647
1726
|
if (ctx.settled) return () => {};
|
|
@@ -1649,18 +1728,15 @@ function runVariableLengthDraw(options) {
|
|
|
1649
1728
|
},
|
|
1650
1729
|
onSettled(handler) {
|
|
1651
1730
|
return settleEmitter.register(handler);
|
|
1731
|
+
},
|
|
1732
|
+
onTransientChange(listener) {
|
|
1733
|
+
if (ctx.settled) return () => {};
|
|
1734
|
+
return transientEmitter.register(listener);
|
|
1652
1735
|
}
|
|
1653
1736
|
};
|
|
1654
1737
|
function fireChange() {
|
|
1655
1738
|
if (ctx.settled) return;
|
|
1656
|
-
|
|
1657
|
-
for (const listener of [...changeListeners]) try {
|
|
1658
|
-
listener(session);
|
|
1659
|
-
} catch (err) {
|
|
1660
|
-
errors.push(err);
|
|
1661
|
-
}
|
|
1662
|
-
if (errors.length === 1) throw errors[0];
|
|
1663
|
-
if (errors.length > 1) throw new AggregateError(errors, "DrawSession.onChange listener errors");
|
|
1739
|
+
changeEmitter.emit(session);
|
|
1664
1740
|
}
|
|
1665
1741
|
const handleClick = (event) => {
|
|
1666
1742
|
if (ctx.settled) return;
|
|
@@ -1707,6 +1783,16 @@ function runVariableLengthDraw(options) {
|
|
|
1707
1783
|
if (committed.length === 0) return;
|
|
1708
1784
|
rubberBand = snappedDrawPosition(snap, event);
|
|
1709
1785
|
renderPreview();
|
|
1786
|
+
const idx = insertionIndex(committed.length);
|
|
1787
|
+
const points = [...committed];
|
|
1788
|
+
points.splice(idx, 0, rubberBand);
|
|
1789
|
+
const phase = transientStarted ? "move" : "start";
|
|
1790
|
+
transientStarted = true;
|
|
1791
|
+
transientEmitter.emit({
|
|
1792
|
+
phase,
|
|
1793
|
+
points,
|
|
1794
|
+
pointIndex: idx
|
|
1795
|
+
});
|
|
1710
1796
|
};
|
|
1711
1797
|
function renderPreview() {
|
|
1712
1798
|
let points;
|
|
@@ -1751,6 +1837,14 @@ function runVariableLengthDraw(options) {
|
|
|
1751
1837
|
onViewChange: rescaleOnViewChange,
|
|
1752
1838
|
onSettled: (settlement) => {
|
|
1753
1839
|
if (onSettled) onSettled();
|
|
1840
|
+
if (transientStarted) {
|
|
1841
|
+
transientStarted = false;
|
|
1842
|
+
transientEmitter.emit({
|
|
1843
|
+
phase: "end",
|
|
1844
|
+
points: committed.map((p) => [...p]),
|
|
1845
|
+
pointIndex: null
|
|
1846
|
+
});
|
|
1847
|
+
}
|
|
1754
1848
|
settleFromInteraction(settlement, commitEmitter, settleEmitter);
|
|
1755
1849
|
},
|
|
1756
1850
|
attach(c) {
|
|
@@ -3065,6 +3159,35 @@ function runEdit(opts) {
|
|
|
3065
3159
|
});
|
|
3066
3160
|
const commitEmitter = createCommitEmitter("EditSession.onCommit listener errors");
|
|
3067
3161
|
const settleEmitter = createSettleEmitter("EditSession.onSettled listener errors");
|
|
3162
|
+
const transientEmitter = createCommitEmitter("EditSession.onTransientChange listener errors");
|
|
3163
|
+
/**
|
|
3164
|
+
* Latch for the "start"/"end" pairing `emitTransient` enforces: `start`
|
|
3165
|
+
* no-ops (and doesn't re-latch) while a gesture is already open; `end`
|
|
3166
|
+
* no-ops when there is no open gesture. `move` always passes through — the
|
|
3167
|
+
* latch only guards against a duplicate/missing bookend, per the
|
|
3168
|
+
* `onTransientChange` contract (`end` fires exactly once per started
|
|
3169
|
+
* gesture).
|
|
3170
|
+
*/
|
|
3171
|
+
let transientGestureActive = false;
|
|
3172
|
+
/**
|
|
3173
|
+
* Emit one tick of the `EditSession.onTransientChange` stream. `points` is
|
|
3174
|
+
* defensively copied so a listener can't observe (or corrupt) the live
|
|
3175
|
+
* working-point array.
|
|
3176
|
+
*/
|
|
3177
|
+
function emitTransient(phase, points, pointIndex) {
|
|
3178
|
+
if (phase === "start") {
|
|
3179
|
+
if (transientGestureActive) return;
|
|
3180
|
+
transientGestureActive = true;
|
|
3181
|
+
} else if (phase === "end") {
|
|
3182
|
+
if (!transientGestureActive) return;
|
|
3183
|
+
transientGestureActive = false;
|
|
3184
|
+
}
|
|
3185
|
+
transientEmitter.emit({
|
|
3186
|
+
phase,
|
|
3187
|
+
points: points.map((p) => [...p]),
|
|
3188
|
+
pointIndex
|
|
3189
|
+
});
|
|
3190
|
+
}
|
|
3068
3191
|
/**
|
|
3069
3192
|
* The transform box's rotation, in radians, session-scoped (ADR-0017 decision
|
|
3070
3193
|
* #2): starts at `0` (axis-aligned) and accumulates every committed rotate
|
|
@@ -3616,6 +3739,10 @@ function runEdit(opts) {
|
|
|
3616
3739
|
},
|
|
3617
3740
|
onSettled(handler) {
|
|
3618
3741
|
return settleEmitter.register(handler);
|
|
3742
|
+
},
|
|
3743
|
+
onTransientChange(listener) {
|
|
3744
|
+
if (ctx.settled) return () => {};
|
|
3745
|
+
return transientEmitter.register(listener);
|
|
3619
3746
|
}
|
|
3620
3747
|
};
|
|
3621
3748
|
let activeDrag = null;
|
|
@@ -3735,6 +3862,7 @@ function runEdit(opts) {
|
|
|
3735
3862
|
preDragPoints,
|
|
3736
3863
|
controlPointIndex: hit.kind === "midpoint" ? hit.index + 1 : hit.index
|
|
3737
3864
|
};
|
|
3865
|
+
emitTransient("start", fsm.kind === "midpoint" ? initialPoints : preDragPoints, activeDrag.controlPointIndex);
|
|
3738
3866
|
if (fsm.kind === "midpoint") {
|
|
3739
3867
|
workingPoints.splice(0, workingPoints.length, ...initialPoints);
|
|
3740
3868
|
renderPreview();
|
|
@@ -3773,6 +3901,7 @@ function runEdit(opts) {
|
|
|
3773
3901
|
},
|
|
3774
3902
|
preDragOptions
|
|
3775
3903
|
};
|
|
3904
|
+
emitTransient("start", preDragPoints, null);
|
|
3776
3905
|
dragInFlight = true;
|
|
3777
3906
|
driver.setDragPanEnabled(false);
|
|
3778
3907
|
}
|
|
@@ -3786,6 +3915,7 @@ function runEdit(opts) {
|
|
|
3786
3915
|
renderPreview();
|
|
3787
3916
|
renderHandlesLayer();
|
|
3788
3917
|
recomputeDirty();
|
|
3918
|
+
emitTransient("move", snapshot.points, activeDrag.controlPointIndex);
|
|
3789
3919
|
}
|
|
3790
3920
|
function paintBoxDragSnapshot(pixel, info, constrainRotation = false) {
|
|
3791
3921
|
if (!activeBoxDrag) return;
|
|
@@ -3797,6 +3927,7 @@ function runEdit(opts) {
|
|
|
3797
3927
|
renderPreview();
|
|
3798
3928
|
renderHandlesLayer();
|
|
3799
3929
|
recomputeDirty();
|
|
3930
|
+
emitTransient("move", snapshot.points, null);
|
|
3800
3931
|
}
|
|
3801
3932
|
const onDriverMove = (pixel, info) => {
|
|
3802
3933
|
if (ctx.settled) return;
|
|
@@ -3836,6 +3967,7 @@ function runEdit(opts) {
|
|
|
3836
3967
|
}
|
|
3837
3968
|
if (activeDrag) {
|
|
3838
3969
|
if (ctx.settled) {
|
|
3970
|
+
emitTransient("end", workingPoints, activeDrag.controlPointIndex);
|
|
3839
3971
|
activeDrag = null;
|
|
3840
3972
|
dragInFlight = false;
|
|
3841
3973
|
driver.setDragPanEnabled(true);
|
|
@@ -3845,12 +3977,14 @@ function runEdit(opts) {
|
|
|
3845
3977
|
activeDrag = null;
|
|
3846
3978
|
driver.setDragPanEnabled(true);
|
|
3847
3979
|
const next = fsm.end(snappedPixel(pixel, info, "reshape", controlPointIndex));
|
|
3980
|
+
emitTransient("end", next, controlPointIndex);
|
|
3848
3981
|
replaceWorkingPoints(preDragPoints);
|
|
3849
3982
|
commitGesture(next);
|
|
3850
3983
|
return;
|
|
3851
3984
|
}
|
|
3852
3985
|
if (activeBoxDrag) {
|
|
3853
3986
|
if (ctx.settled) {
|
|
3987
|
+
emitTransient("end", workingPoints, null);
|
|
3854
3988
|
activeBoxDrag = null;
|
|
3855
3989
|
dragInFlight = false;
|
|
3856
3990
|
driver.setDragPanEnabled(true);
|
|
@@ -3862,6 +3996,7 @@ function runEdit(opts) {
|
|
|
3862
3996
|
if (fsm.kind === "body" && withinClickSlop(pixel, downPixel)) {
|
|
3863
3997
|
replaceWorkingPoints(preDragPoints);
|
|
3864
3998
|
workingAmplifierPlacements = preDragAmplifierPlacements;
|
|
3999
|
+
emitTransient("end", preDragPoints, null);
|
|
3865
4000
|
renderPreview();
|
|
3866
4001
|
renderHandlesLayer();
|
|
3867
4002
|
recomputeDirty();
|
|
@@ -3878,6 +4013,7 @@ function runEdit(opts) {
|
|
|
3878
4013
|
replaceWorkingPoints(preDragPoints);
|
|
3879
4014
|
workingAmplifierPlacements = preDragAmplifierPlacements;
|
|
3880
4015
|
if (fsm.kind === "rotate") boxAngle += finalSnapshot.box.angle - preDragBox.angle;
|
|
4016
|
+
emitTransient("end", finalSnapshot.points, null);
|
|
3881
4017
|
commitGesture(finalSnapshot.points, preDragOptions, transformAmplifierPlacements(preDragAmplifierPlacements, preDragBox, finalSnapshot.box, driver), preDragBox.angle);
|
|
3882
4018
|
return;
|
|
3883
4019
|
}
|
|
@@ -3911,6 +4047,7 @@ function runEdit(opts) {
|
|
|
3911
4047
|
emitSettle: (settlement) => settleFromInteraction(settlement, commitEmitter, settleEmitter),
|
|
3912
4048
|
extraTeardown: () => {
|
|
3913
4049
|
if (activeDrag !== null || activeBoxDrag !== null || labels.dragging) {
|
|
4050
|
+
emitTransient("end", workingPoints, activeDrag?.controlPointIndex ?? null);
|
|
3914
4051
|
driver.setDragPanEnabled(true);
|
|
3915
4052
|
activeDrag = null;
|
|
3916
4053
|
activeBoxDrag = null;
|
|
@@ -5516,6 +5653,7 @@ var TacticalDraw = class {
|
|
|
5516
5653
|
guide: options?.guide,
|
|
5517
5654
|
sizeAnchor: options?.sizeAnchor,
|
|
5518
5655
|
onSettled,
|
|
5656
|
+
onSession: this.trackSession(options?.onSession),
|
|
5519
5657
|
snap: this.resolveSnap
|
|
5520
5658
|
}) : runVariableLengthDraw({
|
|
5521
5659
|
adapter: this.adapter,
|
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.30",
|
|
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.22"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@arethetypeswrong/core": "^0.18.3",
|