@orbat-mapper/tactical-draw 0.10.1 → 0.11.0
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 +25 -0
- package/dist/index.mjs +61 -10
- package/package.json +2 -2
package/dist/index.d.mts
CHANGED
|
@@ -628,6 +628,12 @@ interface InteractionStyle {
|
|
|
628
628
|
midpointHandle?: HandleStyle;
|
|
629
629
|
/** Generator-option handle, such as a flared attack shaft's rear-width grip. */
|
|
630
630
|
optionHandle?: HandleStyle;
|
|
631
|
+
/**
|
|
632
|
+
* Generator-option handle whose option has no stored value, so it shows a
|
|
633
|
+
* derived position (e.g. an interpolated vertex width). Falls back to
|
|
634
|
+
* `optionHandle` when unset.
|
|
635
|
+
*/
|
|
636
|
+
unsetOptionHandle?: HandleStyle;
|
|
631
637
|
/** Visible grab target for a draggable rendered text label. */
|
|
632
638
|
labelHandle?: HandleStyle;
|
|
633
639
|
scaleHandle?: HandleStyle;
|
|
@@ -762,6 +768,13 @@ interface EditSession {
|
|
|
762
768
|
/** `true` after the first working-copy mutation; back to `false` within tolerance. */
|
|
763
769
|
readonly dirty: boolean;
|
|
764
770
|
readonly modes: readonly EditMode[];
|
|
771
|
+
/**
|
|
772
|
+
* Whether reshape mode shows the measure's detail handles: fine-adjustment
|
|
773
|
+
* option handles such as the per-vertex width grips on arrow shafts.
|
|
774
|
+
* Initialised from `EditOptions.detailHandles` and mutated by
|
|
775
|
+
* [[setDetailHandles]].
|
|
776
|
+
*/
|
|
777
|
+
readonly detailHandles: boolean;
|
|
765
778
|
/**
|
|
766
779
|
* Live size anchor for the measure under edit (ADR-0020). `"ground"` bakes a
|
|
767
780
|
* pixel size to meters on commit; `"screen"` keeps it zoom-aware. Initialised
|
|
@@ -790,6 +803,11 @@ interface EditSession {
|
|
|
790
803
|
* is rejected with `TypeError`.
|
|
791
804
|
*/
|
|
792
805
|
setModes(modes: readonly EditMode[]): void;
|
|
806
|
+
/**
|
|
807
|
+
* Show or hide the detail handles (see [[detailHandles]]). View state only:
|
|
808
|
+
* no change event, no history entry. A silent no-op after settlement.
|
|
809
|
+
*/
|
|
810
|
+
setDetailHandles(visible: boolean): void;
|
|
793
811
|
/** Atomically replace the live working graphic while preserving id and kind. */
|
|
794
812
|
updateGraphic(updater: (graphic: ControlMeasure) => ControlMeasure): void;
|
|
795
813
|
/** Shallow-merge per-measure generator options into the live working copy. */
|
|
@@ -1509,6 +1527,13 @@ interface EditOptions {
|
|
|
1509
1527
|
* stays screen-anchored throughout the edit.
|
|
1510
1528
|
*/
|
|
1511
1529
|
sizeAnchor?: SizeAnchor;
|
|
1530
|
+
/**
|
|
1531
|
+
* Show the measure's detail handles in reshape mode: fine-adjustment option
|
|
1532
|
+
* handles such as the left and right width grips beside each arrow-shaft
|
|
1533
|
+
* vertex. Default `false`, which keeps the handle set uncluttered; hosts
|
|
1534
|
+
* typically bind a toggle to [[EditSession.setDetailHandles]].
|
|
1535
|
+
*/
|
|
1536
|
+
detailHandles?: boolean;
|
|
1512
1537
|
}
|
|
1513
1538
|
/**
|
|
1514
1539
|
* Options for `TacticalDraw.editMany` — the multi-select group transform
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DEFAULT_LABEL_HEIGHT_CSS_PIXELS, DEFAULT_SYMBOL_COLOR, FILL_PATTERN_DEFINITIONS, NON_SOLID_FILL_PATTERNS, applyBoxTransformOptions, canonicalTextAmplifierKey, cloneControlMeasure, controlMeasureIdFromFeature, dragControlMeasureOptionHandle, foldsBoxTransformOptions, freezeOrientationOptions, getControlMeasureMetadata, getControlMeasureOptionHandles, getDefaultOptions, normalizeTextAmplifiers, renderControlMeasure, resolveAmplifierPlacement, resolveSizePair, resolveStyleHints, resolveSymbolColor, roundToFixed, switchSizePairUnit } from "@orbat-mapper/control-measures";
|
|
1
|
+
import { DEFAULT_LABEL_HEIGHT_CSS_PIXELS, DEFAULT_SYMBOL_COLOR, FILL_PATTERN_DEFINITIONS, NON_SOLID_FILL_PATTERNS, applyBoxTransformOptions, canonicalTextAmplifierKey, cloneControlMeasure, controlMeasureIdFromFeature, dragControlMeasureOptionHandle, foldsBoxTransformOptions, freezeOrientationOptions, getControlMeasureMetadata, getControlMeasureOptionHandles, getDefaultOptions, normalizeTextAmplifiers, remapVertexAlignedOptions, renderControlMeasure, resetControlMeasureOptionHandle, resolveAmplifierPlacement, resolveSizePair, resolveStyleHints, resolveSymbolColor, roundToFixed, switchSizePairUnit } from "@orbat-mapper/control-measures";
|
|
2
2
|
function pickPointerType(originalEvent) {
|
|
3
3
|
if (typeof PointerEvent !== "undefined" && originalEvent instanceof PointerEvent) {
|
|
4
4
|
if (originalEvent.pointerType === "touch") return "touch";
|
|
@@ -3175,7 +3175,8 @@ function handleFeatureId(prefix, kind, index) {
|
|
|
3175
3175
|
case "rotate": return `${prefix}:handle-rotate`;
|
|
3176
3176
|
}
|
|
3177
3177
|
}
|
|
3178
|
-
function handleStyleFor(kind, interactionStyle) {
|
|
3178
|
+
function handleStyleFor(kind, interactionStyle, unset = false) {
|
|
3179
|
+
if (kind === "option" && unset && interactionStyle?.unsetOptionHandle) return interactionStyle.unsetOptionHandle;
|
|
3179
3180
|
switch (kind) {
|
|
3180
3181
|
case "vertex": return interactionStyle?.vertexHandle;
|
|
3181
3182
|
case "midpoint": return interactionStyle?.midpointHandle;
|
|
@@ -3195,7 +3196,7 @@ function handleStyleFor(kind, interactionStyle) {
|
|
|
3195
3196
|
*/
|
|
3196
3197
|
function toFeatures(handles, featureIdPrefix, interactionStyle) {
|
|
3197
3198
|
return handles.map((handle) => {
|
|
3198
|
-
const style = handleStyleFor(handle.kind, interactionStyle);
|
|
3199
|
+
const style = handleStyleFor(handle.kind, interactionStyle, handle.unset);
|
|
3199
3200
|
return {
|
|
3200
3201
|
type: "Feature",
|
|
3201
3202
|
id: handleFeatureId(featureIdPrefix, handle.kind, handle.index),
|
|
@@ -4300,6 +4301,7 @@ function runEdit(opts) {
|
|
|
4300
4301
|
symbolColors
|
|
4301
4302
|
};
|
|
4302
4303
|
let sizeAnchor = opts.sizeAnchor ?? defaultSizeAnchor(measure);
|
|
4304
|
+
let detailHandles = opts.detailHandles ?? false;
|
|
4303
4305
|
const metadata = getControlMeasureMetadata(measure.kind);
|
|
4304
4306
|
const rule = metadata.rule;
|
|
4305
4307
|
const midpointHandlesEnabled = rule?.canonicalPointCount === void 0;
|
|
@@ -4561,14 +4563,15 @@ function runEdit(opts) {
|
|
|
4561
4563
|
closedRing
|
|
4562
4564
|
});
|
|
4563
4565
|
if (modes.includes("reshape")) {
|
|
4564
|
-
const optionHandles = getControlMeasureOptionHandles(measure.kind, workingPoints, workingOptions);
|
|
4566
|
+
const optionHandles = getControlMeasureOptionHandles(measure.kind, workingPoints, workingOptions, { detail: detailHandles });
|
|
4565
4567
|
for (let index = 0; index < optionHandles.length; index++) {
|
|
4566
4568
|
const handle = optionHandles[index];
|
|
4567
4569
|
handles.push({
|
|
4568
4570
|
kind: "option",
|
|
4569
4571
|
index,
|
|
4570
4572
|
optionId: handle.id,
|
|
4571
|
-
position: [...handle.position]
|
|
4573
|
+
position: [...handle.position],
|
|
4574
|
+
...handle.unset ? { unset: true } : {}
|
|
4572
4575
|
});
|
|
4573
4576
|
}
|
|
4574
4577
|
}
|
|
@@ -5012,6 +5015,14 @@ function runEdit(opts) {
|
|
|
5012
5015
|
if (ctx.settled) return;
|
|
5013
5016
|
ctx.fail(new TacticalDrawAbortError("session"));
|
|
5014
5017
|
},
|
|
5018
|
+
get detailHandles() {
|
|
5019
|
+
return detailHandles;
|
|
5020
|
+
},
|
|
5021
|
+
setDetailHandles(visible) {
|
|
5022
|
+
if (ctx.settled || visible === detailHandles) return;
|
|
5023
|
+
detailHandles = visible;
|
|
5024
|
+
renderHandlesLayer();
|
|
5025
|
+
},
|
|
5015
5026
|
setModes(next) {
|
|
5016
5027
|
if (ctx.settled) return;
|
|
5017
5028
|
const normalised = normaliseModes(next);
|
|
@@ -5158,11 +5169,30 @@ function runEdit(opts) {
|
|
|
5158
5169
|
if (ctx.settled) return;
|
|
5159
5170
|
const previous = workingPoints.map((p) => [...p]);
|
|
5160
5171
|
const spliced = previous.filter((_, i) => i !== index);
|
|
5161
|
-
|
|
5172
|
+
const next = rule ? rule.transform({
|
|
5162
5173
|
previous,
|
|
5163
5174
|
next: spliced,
|
|
5164
5175
|
activePointIndex: index
|
|
5165
|
-
}) : spliced
|
|
5176
|
+
}) : spliced;
|
|
5177
|
+
const preGestureOptions = workingOptions;
|
|
5178
|
+
followVertexChange({
|
|
5179
|
+
type: "delete",
|
|
5180
|
+
index
|
|
5181
|
+
});
|
|
5182
|
+
commitGesture(next, preGestureOptions);
|
|
5183
|
+
}
|
|
5184
|
+
/**
|
|
5185
|
+
* Keep the measure's vertex-aligned options (e.g. `vertexLeftWidthRatios`) in
|
|
5186
|
+
* step with a vertex insert or delete, replacing `workingOptions`
|
|
5187
|
+
* with the patched copy. The caller commits with the pre-change options
|
|
5188
|
+
* snapshot so one change event and one undo step cover points and options.
|
|
5189
|
+
*/
|
|
5190
|
+
function followVertexChange(change) {
|
|
5191
|
+
const patch = remapVertexAlignedOptions(measure.kind, workingOptions, change);
|
|
5192
|
+
if (Object.keys(patch).length > 0) workingOptions = {
|
|
5193
|
+
...workingOptions ?? {},
|
|
5194
|
+
...patch
|
|
5195
|
+
};
|
|
5166
5196
|
}
|
|
5167
5197
|
const onDriverDown = (pixel, info) => {
|
|
5168
5198
|
if (ctx.settled || dragInFlight) return;
|
|
@@ -5186,10 +5216,18 @@ function runEdit(opts) {
|
|
|
5186
5216
|
shell.suppressNextClickAway();
|
|
5187
5217
|
return;
|
|
5188
5218
|
}
|
|
5219
|
+
if (hit.kind === "option" && hit.optionId && info?.altKey) {
|
|
5220
|
+
const patch = resetControlMeasureOptionHandle(measure.kind, workingPoints, workingOptions, hit.optionId);
|
|
5221
|
+
if (patch) {
|
|
5222
|
+
setOptions(patch, "gesture", "Reset option handle");
|
|
5223
|
+
shell.suppressNextClickAway();
|
|
5224
|
+
return;
|
|
5225
|
+
}
|
|
5226
|
+
}
|
|
5189
5227
|
if (hit.kind === "option" && hit.optionId) {
|
|
5190
5228
|
activeOptionDrag = {
|
|
5191
5229
|
handleId: hit.optionId,
|
|
5192
|
-
preDragOptions:
|
|
5230
|
+
preDragOptions: workingOptions,
|
|
5193
5231
|
preDragNonGeometryDirty: nonGeometryDirty
|
|
5194
5232
|
};
|
|
5195
5233
|
emitTransient("start", workingPoints, null);
|
|
@@ -5208,10 +5246,15 @@ function runEdit(opts) {
|
|
|
5208
5246
|
activeDrag = {
|
|
5209
5247
|
fsm,
|
|
5210
5248
|
preDragPoints,
|
|
5249
|
+
preDragOptions: workingOptions,
|
|
5211
5250
|
controlPointIndex: hit.kind === "midpoint" ? hit.index + 1 : hit.index
|
|
5212
5251
|
};
|
|
5213
5252
|
emitTransient("start", fsm.kind === "midpoint" ? initialPoints : preDragPoints, activeDrag.controlPointIndex);
|
|
5214
5253
|
if (fsm.kind === "midpoint") {
|
|
5254
|
+
followVertexChange({
|
|
5255
|
+
type: "insert",
|
|
5256
|
+
index: hit.index + 1
|
|
5257
|
+
});
|
|
5215
5258
|
workingPoints.splice(0, workingPoints.length, ...initialPoints);
|
|
5216
5259
|
renderWorkingGraphic();
|
|
5217
5260
|
renderHandlesLayer();
|
|
@@ -5386,13 +5429,13 @@ function runEdit(opts) {
|
|
|
5386
5429
|
driver.setDragPanEnabled(true);
|
|
5387
5430
|
return;
|
|
5388
5431
|
}
|
|
5389
|
-
const { fsm, preDragPoints, controlPointIndex } = activeDrag;
|
|
5432
|
+
const { fsm, preDragPoints, preDragOptions, controlPointIndex } = activeDrag;
|
|
5390
5433
|
activeDrag = null;
|
|
5391
5434
|
driver.setDragPanEnabled(true);
|
|
5392
5435
|
const next = fsm.end(snappedPixel(pixel, info, "reshape", controlPointIndex), { constrainAngles: info?.shiftKey });
|
|
5393
5436
|
emitTransient("end", next, controlPointIndex);
|
|
5394
5437
|
replaceWorkingPoints(preDragPoints);
|
|
5395
|
-
commitGesture(next);
|
|
5438
|
+
commitGesture(next, preDragOptions);
|
|
5396
5439
|
return;
|
|
5397
5440
|
}
|
|
5398
5441
|
if (activeBoxDrag) {
|
|
@@ -8492,6 +8535,7 @@ var TacticalDraw = class {
|
|
|
8492
8535
|
handleIdPrefix,
|
|
8493
8536
|
guide: options?.guide,
|
|
8494
8537
|
sizeAnchor: options?.sizeAnchor,
|
|
8538
|
+
detailHandles: options?.detailHandles,
|
|
8495
8539
|
settleToGraphics,
|
|
8496
8540
|
publishWorkingGraphic: (graphic, features) => this.publishWorkingGraphics([{
|
|
8497
8541
|
id: graphic.id,
|
|
@@ -8973,6 +9017,12 @@ const BUILT_IN_INTERACTION_STYLE = {
|
|
|
8973
9017
|
strokeColor: "#ffffff",
|
|
8974
9018
|
strokeWidth: 2
|
|
8975
9019
|
},
|
|
9020
|
+
unsetOptionHandle: {
|
|
9021
|
+
pointRadius: 5,
|
|
9022
|
+
fillColor: "#ffffff",
|
|
9023
|
+
strokeColor: "#f59e0b",
|
|
9024
|
+
strokeWidth: 2
|
|
9025
|
+
},
|
|
8976
9026
|
labelHandle: {
|
|
8977
9027
|
pointRadius: 6,
|
|
8978
9028
|
fillColor: "#7c3aed",
|
|
@@ -8996,6 +9046,7 @@ const HANDLE_SLOTS = [
|
|
|
8996
9046
|
"vertexHandle",
|
|
8997
9047
|
"midpointHandle",
|
|
8998
9048
|
"optionHandle",
|
|
9049
|
+
"unsetOptionHandle",
|
|
8999
9050
|
"labelHandle",
|
|
9000
9051
|
"scaleHandle",
|
|
9001
9052
|
"rotateHandle"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orbat-mapper/tactical-draw",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
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",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"access": "public"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@orbat-mapper/control-measures": "^0.
|
|
42
|
+
"@orbat-mapper/control-measures": "^0.29.0"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
|
45
45
|
"vitest": ">=4.0.0"
|