@orbat-mapper/tactical-draw 0.2.0-alpha.2 → 0.2.0-alpha.4

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.
Files changed (2) hide show
  1. package/dist/index.mjs +29 -21
  2. package/package.json +2 -2
package/dist/index.mjs CHANGED
@@ -1006,6 +1006,7 @@ function runVariableLengthDraw(options) {
1006
1006
  const max = metadata.maxCoordinates;
1007
1007
  const rule = metadata.rule;
1008
1008
  const trailingFixed = rule?.trailingFixedSlots ?? 0;
1009
+ const closedRing = rule?.closedRing ?? false;
1009
1010
  const ruleMin = rule?.minimumUserPoints;
1010
1011
  const guideEnabled = (guideOpt ?? true) && !shouldSuppressGuide(metadata);
1011
1012
  const isAreaKind = metadata.geometry === "area";
@@ -1097,20 +1098,20 @@ function runVariableLengthDraw(options) {
1097
1098
  if (ctx.settled) return;
1098
1099
  const pos = roundControlPoint(event.coordinate);
1099
1100
  const lastSpineIdx = committed.length - 1 - trailingFixed;
1101
+ const finishIndices = closedRing && lastSpineIdx > 0 ? [lastSpineIdx, 0] : [lastSpineIdx];
1100
1102
  for (let i = 0; i < committed.length; i++) if (positionsEqual(pos, committed[i])) {
1101
- if (i === lastSpineIdx && canCommitNow()) ctx.commit(buildSnapshot);
1103
+ if (finishIndices.includes(i) && canCommitNow()) ctx.commit(buildSnapshot);
1102
1104
  return;
1103
1105
  }
1104
- if (lastSpineIdx >= 0 && canCommitNow()) {
1105
- const lastPixel = adapter.getPixelFromCoordinate(committed[lastSpineIdx]);
1106
- if (lastPixel) {
1107
- const tol = mapEventHitTolerance(event.originalEvent);
1108
- const dx = event.pixel[0] - lastPixel[0];
1109
- const dy = event.pixel[1] - lastPixel[1];
1110
- if (dx * dx + dy * dy <= tol * tol) {
1111
- ctx.commit(buildSnapshot);
1112
- return;
1113
- }
1106
+ if (lastSpineIdx >= 0 && canCommitNow()) for (const finishIndex of finishIndices) {
1107
+ const finishPixel = adapter.getPixelFromCoordinate(committed[finishIndex]);
1108
+ if (!finishPixel) continue;
1109
+ const tol = mapEventHitTolerance(event.originalEvent);
1110
+ const dx = event.pixel[0] - finishPixel[0];
1111
+ const dy = event.pixel[1] - finishPixel[1];
1112
+ if (dx * dx + dy * dy <= tol * tol) {
1113
+ ctx.commit(buildSnapshot);
1114
+ return;
1114
1115
  }
1115
1116
  }
1116
1117
  if (typeof max === "number" && committed.length >= max) return;
@@ -1351,7 +1352,7 @@ function segmentMidpoint(a, b) {
1351
1352
  * are emitted for it here. Returns `[]` for an empty control-point list.
1352
1353
  */
1353
1354
  function buildHandleSet(controlPoints, modes, options = {}) {
1354
- const { includeMidpoints = true, trailingFixedSlots = 0 } = options;
1355
+ const { includeMidpoints = true, trailingFixedSlots = 0, closedRing = false } = options;
1355
1356
  const handles = [];
1356
1357
  const wantsReshape = modes.includes("reshape");
1357
1358
  if (wantsReshape || modes.includes("delete")) {
@@ -1361,12 +1362,15 @@ function buildHandleSet(controlPoints, modes, options = {}) {
1361
1362
  position: [...controlPoints[i]]
1362
1363
  });
1363
1364
  if (wantsReshape && includeMidpoints) {
1364
- const midpointLimit = controlPoints.length - 1 - trailingFixedSlots;
1365
- for (let i = 0; i < midpointLimit; i++) handles.push({
1366
- kind: "midpoint",
1367
- index: i,
1368
- position: segmentMidpoint(controlPoints[i], controlPoints[i + 1])
1369
- });
1365
+ const edgeCount = closedRing && trailingFixedSlots === 0 && controlPoints.length >= 3 ? controlPoints.length : controlPoints.length - 1 - trailingFixedSlots;
1366
+ for (let i = 0; i < edgeCount; i++) {
1367
+ const end = (i + 1) % controlPoints.length;
1368
+ handles.push({
1369
+ kind: "midpoint",
1370
+ index: i,
1371
+ position: segmentMidpoint(controlPoints[i], controlPoints[end])
1372
+ });
1373
+ }
1370
1374
  }
1371
1375
  }
1372
1376
  if (modes.includes("translate")) {
@@ -1552,7 +1556,8 @@ function beginHandleDrag(args) {
1552
1556
  if (hit.kind === "vertex") return begun("vertex", preDragPoints, dragOnePoint(preDragPoints, hit.index));
1553
1557
  if (hit.kind === "midpoint") {
1554
1558
  const insertIndex = hit.index + 1;
1555
- const mid = segmentMidpoint(preDragPoints[hit.index], preDragPoints[hit.index + 1]);
1559
+ const segmentEndIndex = (hit.index + 1) % preDragPoints.length;
1560
+ const mid = segmentMidpoint(preDragPoints[hit.index], preDragPoints[segmentEndIndex]);
1556
1561
  const insertedBase = [
1557
1562
  ...preDragPoints.slice(0, insertIndex),
1558
1563
  mid,
@@ -1662,6 +1667,7 @@ function runEdit(opts) {
1662
1667
  const rule = metadata.rule;
1663
1668
  const midpointHandlesEnabled = rule?.canonicalPointCount === void 0;
1664
1669
  const trailingFixedSlots = rule?.trailingFixedSlots ?? 0;
1670
+ const closedRing = rule?.closedRing ?? false;
1665
1671
  const guideEnabled = (guideOpt ?? true) && !shouldSuppressGuide(metadata);
1666
1672
  const isAreaKind = metadata.geometry === "area";
1667
1673
  const requestedModes = normaliseModes(initialModes);
@@ -1732,7 +1738,8 @@ function runEdit(opts) {
1732
1738
  if (ctx.settled) return;
1733
1739
  let handles = buildHandleSet(workingPoints, modes, {
1734
1740
  includeMidpoints: midpointHandlesEnabled,
1735
- trailingFixedSlots
1741
+ trailingFixedSlots,
1742
+ closedRing
1736
1743
  });
1737
1744
  if (activeDrag?.fsm.kind === "rotate" && rotateHandleOverride) {
1738
1745
  const override = rotateHandleOverride;
@@ -2083,7 +2090,8 @@ function runEdit(opts) {
2083
2090
  const tol = info?.hitTolerance ?? HANDLE_HIT_TOLERANCE_PX;
2084
2091
  const hit = pickHandle(buildHandleSet(workingPoints, modes, {
2085
2092
  includeMidpoints: midpointHandlesEnabled,
2086
- trailingFixedSlots
2093
+ trailingFixedSlots,
2094
+ closedRing
2087
2095
  }), pixel, tol, (lonLat) => driver.toPixel(lonLat));
2088
2096
  if (!hit) return;
2089
2097
  if (modes.includes("delete")) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orbat-mapper/tactical-draw",
3
- "version": "0.2.0-alpha.2",
3
+ "version": "0.2.0-alpha.4",
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.3"
41
+ "@orbat-mapper/control-measures": "^0.2.0-alpha.5"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@arethetypeswrong/core": "^0.18.3",