@orbat-mapper/tactical-draw 0.2.0-alpha.15 → 0.2.0-alpha.17
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 +14 -5
- package/dist/index.mjs +219 -22
- package/package.json +2 -2
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ControlMeasure, ControlMeasureKind, ControlMeasureSnapshot, ControlMeasureStyle, TextAmplifiers } from "@orbat-mapper/control-measures";
|
|
1
|
+
import { AmplifierPlacements, 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
|
|
@@ -461,6 +461,7 @@ interface CombinedAbort {
|
|
|
461
461
|
declare function combineWithHostSignal(hostSignal?: AbortSignal): CombinedAbort;
|
|
462
462
|
//#endregion
|
|
463
463
|
//#region src/tactical-draw/measure-options.d.ts
|
|
464
|
+
declare const DEFAULT_LABEL_SIZE_PIXELS = 14;
|
|
464
465
|
/**
|
|
465
466
|
* Where a control measure's size is anchored (ADR-0020). `ground` (the
|
|
466
467
|
* default) means a meter size fixed to the terrain — static, never re-rendered
|
|
@@ -502,6 +503,8 @@ interface InteractionStyle {
|
|
|
502
503
|
guide?: GuideStyle;
|
|
503
504
|
vertexHandle?: HandleStyle;
|
|
504
505
|
midpointHandle?: HandleStyle;
|
|
506
|
+
/** Visible grab target for a draggable rendered text label. */
|
|
507
|
+
labelHandle?: HandleStyle;
|
|
505
508
|
scaleHandle?: HandleStyle;
|
|
506
509
|
rotateHandle?: HandleStyle;
|
|
507
510
|
boxOutline?: GuideStyle;
|
|
@@ -519,12 +522,13 @@ interface InteractionStyle {
|
|
|
519
522
|
* `reshape` renders vertex/midpoint handles; `transform` renders the oriented
|
|
520
523
|
* selection box (4 corner scale handles + a rotate grip + drag-anywhere
|
|
521
524
|
* body translate) via `transform-box.ts`. The two can be combined freely —
|
|
522
|
-
* single-measure edits show both by default. `
|
|
525
|
+
* single-measure edits show both by default. `labeldrag` enables dragging
|
|
526
|
+
* rendered text labels. `delete` is an explicit
|
|
523
527
|
* destructive mode: it renders the vertex handles (so they can be tapped to
|
|
524
528
|
* remove) but no midpoints or box, and the edit controller deletes the tapped
|
|
525
529
|
* vertex instead of dragging it.
|
|
526
530
|
*/
|
|
527
|
-
type EditMode = "reshape" | "transform" | "delete";
|
|
531
|
+
type EditMode = "reshape" | "transform" | "labeldrag" | "delete";
|
|
528
532
|
//#endregion
|
|
529
533
|
//#region src/tactical-draw/session-events.d.ts
|
|
530
534
|
/**
|
|
@@ -570,6 +574,8 @@ interface EditSession {
|
|
|
570
574
|
* doctrinal field codes happens in the core render seam, not here.
|
|
571
575
|
*/
|
|
572
576
|
readonly textAmplifiers: TextAmplifiers | undefined;
|
|
577
|
+
/** Live user-adjusted anchors for individually rendered text amplifiers. */
|
|
578
|
+
readonly amplifierPlacements: AmplifierPlacements | undefined;
|
|
573
579
|
/** `true` after the first working-copy mutation; back to `false` within tolerance. */
|
|
574
580
|
readonly dirty: boolean;
|
|
575
581
|
readonly modes: readonly EditMode[];
|
|
@@ -614,6 +620,8 @@ interface EditSession {
|
|
|
614
620
|
* silent no-op after the session has settled (like [[setOptions]]).
|
|
615
621
|
*/
|
|
616
622
|
setTextAmplifiers(partial: Record<string, string | undefined>): void;
|
|
623
|
+
/** Merge field-keyed label anchors; `undefined` resets a field to its doctrinal position. */
|
|
624
|
+
setAmplifierPlacements(partial: Record<string, Position | undefined>): void;
|
|
617
625
|
/**
|
|
618
626
|
* Subscribe to working-copy changes. The listener receives an
|
|
619
627
|
* [[EditChangeEvent]] for each completed gesture (drag end, insert/delete
|
|
@@ -1009,7 +1017,8 @@ interface EditOptions {
|
|
|
1009
1017
|
* renders vertex / midpoint handles; `transform` renders the oriented
|
|
1010
1018
|
* selection box — 4 corner scale handles, a rotate grip above the top edge,
|
|
1011
1019
|
* and drag-anywhere-inside-the-box translate (ADR-0017). The two can be
|
|
1012
|
-
* combined freely (the default does). `"
|
|
1020
|
+
* combined freely (the default does). `"labeldrag"` opt-in enables repositioning
|
|
1021
|
+
* rendered text labels. `"delete"` is exclusive: it collapses
|
|
1013
1022
|
* the set to vertex-tap removal only. An unknown mode throws `TypeError`.
|
|
1014
1023
|
*/
|
|
1015
1024
|
modes?: readonly EditMode[];
|
|
@@ -1516,4 +1525,4 @@ declare function nearestWorldCopyLng(lng: number, refLng: number): number;
|
|
|
1516
1525
|
/** `nearestWorldCopyLng` applied to a lon/lat position. */
|
|
1517
1526
|
declare function nearestWorldCopyPosition(position: Position, refLng: number): Position;
|
|
1518
1527
|
//#endregion
|
|
1519
|
-
export { BaseMapAdapter, type CombinedAbort, DEFAULT_TEXT_SIZE_MAX_PIXELS, DEFAULT_TEXT_SIZE_MIN_PIXELS, type DrawMeasureDraft, type DrawOptions, type DrawSession, type EditChangeEvent, type EditManyOptions, type EditMode, type EditOptions, type EditPointerDriver, type EditSession, type FeatureOps, type GuideStyle, type HandleStyle, type InteractionStyle, type LayerId, type MapAdapter, type MapCursor, type MapEvent, type MapEventHandler, type NativeEventEntry, type PickEvent, type PickHandler, type PickOptions, type PixelCoordinate, type PointerCallback, PointerCallbackHub, type PointerEventInfo, type SettleReason, type SizeAnchor, 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, coerceHandleStyle, combineWithHostSignal, getSizeAnchor, hitTestFeature, ignoreAbort, isTacticalDrawAbortError, nearestWorldCopyLng, nearestWorldCopyPosition, pickHitTolerance, resolveGroundTextSizePixels, tryControlMeasureIdFromFeature };
|
|
1528
|
+
export { BaseMapAdapter, type CombinedAbort, DEFAULT_LABEL_SIZE_PIXELS, DEFAULT_TEXT_SIZE_MAX_PIXELS, DEFAULT_TEXT_SIZE_MIN_PIXELS, type DrawMeasureDraft, type DrawOptions, type DrawSession, type EditChangeEvent, type EditManyOptions, type EditMode, type EditOptions, type EditPointerDriver, type EditSession, type FeatureOps, type GuideStyle, type HandleStyle, type InteractionStyle, type LayerId, type MapAdapter, type MapCursor, type MapEvent, type MapEventHandler, type NativeEventEntry, type PickEvent, type PickHandler, type PickOptions, type PixelCoordinate, type PointerCallback, PointerCallbackHub, type PointerEventInfo, type SettleReason, type SizeAnchor, 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, coerceHandleStyle, combineWithHostSignal, getSizeAnchor, hitTestFeature, ignoreAbort, isTacticalDrawAbortError, nearestWorldCopyLng, nearestWorldCopyPosition, pickHitTolerance, resolveGroundTextSizePixels, tryControlMeasureIdFromFeature };
|
package/dist/index.mjs
CHANGED
|
@@ -521,6 +521,9 @@ function combineWithHostSignal(hostSignal) {
|
|
|
521
521
|
dispose
|
|
522
522
|
};
|
|
523
523
|
}
|
|
524
|
+
//#endregion
|
|
525
|
+
//#region src/tactical-draw/measure-options.ts
|
|
526
|
+
const DEFAULT_LABEL_SIZE_PIXELS = 14;
|
|
524
527
|
/**
|
|
525
528
|
* Per-kind pixel-denominated *geometry* size option and the meter option it
|
|
526
529
|
* bakes into — the single source of which geometry option makes a measure
|
|
@@ -2021,6 +2024,31 @@ function boxOutlineFeature(box, fromPixel, featureId, style) {
|
|
|
2021
2024
|
};
|
|
2022
2025
|
}
|
|
2023
2026
|
//#endregion
|
|
2027
|
+
//#region src/tactical-draw/transform-amplifier-placements.ts
|
|
2028
|
+
/**
|
|
2029
|
+
* Carry user-adjusted label anchors through a transform-box gesture. Anchors
|
|
2030
|
+
* are transformed in pixel space alongside the control points so the result
|
|
2031
|
+
* remains visually rigid across map projections.
|
|
2032
|
+
*/
|
|
2033
|
+
function transformAmplifierPlacements(placements, from, to, projection) {
|
|
2034
|
+
if (!placements) return void 0;
|
|
2035
|
+
const scaleX = to.halfWidth / from.halfWidth;
|
|
2036
|
+
const scaleY = to.halfHeight / from.halfHeight;
|
|
2037
|
+
const transformed = {};
|
|
2038
|
+
for (const [field, position] of Object.entries(placements)) {
|
|
2039
|
+
if (position === void 0) continue;
|
|
2040
|
+
const pixel = projection.toPixel(position);
|
|
2041
|
+
if (!pixel) {
|
|
2042
|
+
transformed[field] = [...position];
|
|
2043
|
+
continue;
|
|
2044
|
+
}
|
|
2045
|
+
const [localX, localY] = rotate(pixel[0] - from.center[0], pixel[1] - from.center[1], -from.angle);
|
|
2046
|
+
const [outX, outY] = rotate(localX * scaleX, localY * scaleY, to.angle);
|
|
2047
|
+
transformed[field] = projection.fromPixel([to.center[0] + outX, to.center[1] + outY]);
|
|
2048
|
+
}
|
|
2049
|
+
return transformed;
|
|
2050
|
+
}
|
|
2051
|
+
//#endregion
|
|
2024
2052
|
//#region src/tactical-draw/edit-controller.ts
|
|
2025
2053
|
/**
|
|
2026
2054
|
* Coordinate-space tolerance for `EditSession.dirty`. The working copy is
|
|
@@ -2031,6 +2059,8 @@ function boxOutlineFeature(box, fromPixel, featureId, style) {
|
|
|
2031
2059
|
*/
|
|
2032
2060
|
const DIRTY_EPSILON$1 = 1e-9;
|
|
2033
2061
|
const HANDLE_HIT_TOLERANCE_PX$1 = 12;
|
|
2062
|
+
/** Screen-space clearance between a label's anchor and its visible drag grip. */
|
|
2063
|
+
const LABEL_HANDLE_OFFSET_PX = 14;
|
|
2034
2064
|
/**
|
|
2035
2065
|
* A body drag (drag-anywhere-inside-the-box translate) whose release pixel
|
|
2036
2066
|
* lands within this many pixels of the down pixel is treated as a plain
|
|
@@ -2045,6 +2075,7 @@ const CLICK_SLOP_PX$1 = 3;
|
|
|
2045
2075
|
const ALLOWED_EDIT_MODES = new Set([
|
|
2046
2076
|
"reshape",
|
|
2047
2077
|
"transform",
|
|
2078
|
+
"labeldrag",
|
|
2048
2079
|
"delete"
|
|
2049
2080
|
]);
|
|
2050
2081
|
/**
|
|
@@ -2074,6 +2105,10 @@ function runEdit(opts) {
|
|
|
2074
2105
|
const workingPoints = editStartPoints.map((p) => [...p]);
|
|
2075
2106
|
let workingOptions = { ...measure.options ?? {} };
|
|
2076
2107
|
let workingTextAmplifiers = measure.textAmplifiers && Object.keys(measure.textAmplifiers).length > 0 ? { ...measure.textAmplifiers } : void 0;
|
|
2108
|
+
let workingAmplifierPlacements = measure.amplifierPlacements && Object.keys(measure.amplifierPlacements).length > 0 ? structuredClone(measure.amplifierPlacements) : void 0;
|
|
2109
|
+
function snapshotAmplifierPlacements() {
|
|
2110
|
+
return workingAmplifierPlacements ? structuredClone(workingAmplifierPlacements) : void 0;
|
|
2111
|
+
}
|
|
2077
2112
|
let modes = requestedModes;
|
|
2078
2113
|
let dirty = false;
|
|
2079
2114
|
let nonGeometryDirty = false;
|
|
@@ -2173,6 +2208,8 @@ function runEdit(opts) {
|
|
|
2173
2208
|
};
|
|
2174
2209
|
if (workingTextAmplifiers) working.textAmplifiers = workingTextAmplifiers;
|
|
2175
2210
|
else delete working.textAmplifiers;
|
|
2211
|
+
if (workingAmplifierPlacements) working.amplifierPlacements = workingAmplifierPlacements;
|
|
2212
|
+
else delete working.amplifierPlacements;
|
|
2176
2213
|
let features;
|
|
2177
2214
|
try {
|
|
2178
2215
|
features = renderMeasureForAdapter(adapter, working, renderOpts).features;
|
|
@@ -2202,6 +2239,31 @@ function runEdit(opts) {
|
|
|
2202
2239
|
trailingFixedSlots,
|
|
2203
2240
|
closedRing
|
|
2204
2241
|
}), handleIdPrefix, interactionStyle);
|
|
2242
|
+
if (modes.includes("labeldrag")) {
|
|
2243
|
+
const style = interactionStyle?.labelHandle;
|
|
2244
|
+
let index = 0;
|
|
2245
|
+
for (const feature of lastPreviewFeatures) {
|
|
2246
|
+
const key = feature.properties?.labelPlacementKey;
|
|
2247
|
+
if (typeof key !== "string" || feature.geometry?.type !== "Point") continue;
|
|
2248
|
+
const pixel = driver.toPixel(feature.geometry.coordinates);
|
|
2249
|
+
if (!pixel) continue;
|
|
2250
|
+
features.push({
|
|
2251
|
+
type: "Feature",
|
|
2252
|
+
id: `${handleIdPrefix}:label-handle:${index}`,
|
|
2253
|
+
geometry: {
|
|
2254
|
+
type: "Point",
|
|
2255
|
+
coordinates: driver.fromPixel([pixel[0], pixel[1] - LABEL_HANDLE_OFFSET_PX])
|
|
2256
|
+
},
|
|
2257
|
+
properties: {
|
|
2258
|
+
handle: "label",
|
|
2259
|
+
index,
|
|
2260
|
+
labelPlacementKey: key,
|
|
2261
|
+
...style ? { style: { ...style } } : {}
|
|
2262
|
+
}
|
|
2263
|
+
});
|
|
2264
|
+
index += 1;
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
2205
2267
|
if (modes.includes("transform")) {
|
|
2206
2268
|
const liveBox = activeBoxDrag ? activeBoxDrag.lastSnapshot.box : box;
|
|
2207
2269
|
if (liveBox) {
|
|
@@ -2226,6 +2288,8 @@ function runEdit(opts) {
|
|
|
2226
2288
|
};
|
|
2227
2289
|
if (workingTextAmplifiers) working.textAmplifiers = workingTextAmplifiers;
|
|
2228
2290
|
else delete working.textAmplifiers;
|
|
2291
|
+
if (workingAmplifierPlacements) working.amplifierPlacements = workingAmplifierPlacements;
|
|
2292
|
+
else delete working.amplifierPlacements;
|
|
2229
2293
|
const committed = sizeAnchor === "screen" ? working : bakePixelSizeToGround(working, adapter);
|
|
2230
2294
|
return {
|
|
2231
2295
|
measure: cloneControlMeasure(committed),
|
|
@@ -2277,7 +2341,7 @@ function runEdit(opts) {
|
|
|
2277
2341
|
* - Reject restores the working state to the pre-gesture snapshot and
|
|
2278
2342
|
* recomputes `dirty`. Close/abort settle the session as normal.
|
|
2279
2343
|
*/
|
|
2280
|
-
function emitChange(previousRendered, previousPoints, previousOptions, previousStyle, previousNonGeometryDirty, previousSizeAnchor, currentRendered, previousTextAmplifiers) {
|
|
2344
|
+
function emitChange(previousRendered, previousPoints, previousOptions, previousStyle, previousNonGeometryDirty, previousSizeAnchor, currentRendered, previousTextAmplifiers, previousAmplifierPlacements) {
|
|
2281
2345
|
if (ctx.settled) return;
|
|
2282
2346
|
let rejectRequested = false;
|
|
2283
2347
|
let closeRequested = false;
|
|
@@ -2310,6 +2374,7 @@ function runEdit(opts) {
|
|
|
2310
2374
|
currentStyle = previousStyle;
|
|
2311
2375
|
sizeAnchor = previousSizeAnchor;
|
|
2312
2376
|
workingTextAmplifiers = previousTextAmplifiers;
|
|
2377
|
+
workingAmplifierPlacements = previousAmplifierPlacements;
|
|
2313
2378
|
nonGeometryDirty = previousNonGeometryDirty;
|
|
2314
2379
|
renderPreview();
|
|
2315
2380
|
renderHandlesLayer();
|
|
@@ -2328,20 +2393,23 @@ function runEdit(opts) {
|
|
|
2328
2393
|
* queued during the drag are applied after the listener emission (so the
|
|
2329
2394
|
* post-gesture render reflects the requested mode set).
|
|
2330
2395
|
*/
|
|
2331
|
-
function commitGesture(nextPoints, preGestureOptions) {
|
|
2396
|
+
function commitGesture(nextPoints, preGestureOptions, nextAmplifierPlacements) {
|
|
2332
2397
|
if (ctx.settled) return;
|
|
2333
2398
|
const previousPoints = workingPoints.map((p) => [...p]);
|
|
2334
2399
|
const previousOptions = { ...preGestureOptions ?? workingOptions ?? {} };
|
|
2335
2400
|
const previousStyle = currentStyle;
|
|
2336
2401
|
const previousNonGeometryDirty = nonGeometryDirty;
|
|
2337
2402
|
const previousTextAmplifiers = workingTextAmplifiers;
|
|
2403
|
+
const previousAmplifierPlacements = snapshotAmplifierPlacements();
|
|
2338
2404
|
const previousRendered = rebuildRenderedWith(previousOptions);
|
|
2339
2405
|
replaceWorkingPoints(nextPoints);
|
|
2406
|
+
if (nextAmplifierPlacements !== void 0) workingAmplifierPlacements = structuredClone(nextAmplifierPlacements);
|
|
2340
2407
|
let currentRendered;
|
|
2341
2408
|
try {
|
|
2342
2409
|
currentRendered = rebuildRendered();
|
|
2343
2410
|
} catch (err) {
|
|
2344
2411
|
replaceWorkingPoints(previousPoints);
|
|
2412
|
+
workingAmplifierPlacements = previousAmplifierPlacements;
|
|
2345
2413
|
if (preGestureOptions !== void 0) replaceWorkingOptions(previousOptions);
|
|
2346
2414
|
dragInFlight = false;
|
|
2347
2415
|
drainPendingModes();
|
|
@@ -2352,7 +2420,7 @@ function runEdit(opts) {
|
|
|
2352
2420
|
recomputeDirty();
|
|
2353
2421
|
dragInFlight = false;
|
|
2354
2422
|
drainPendingModes();
|
|
2355
|
-
emitChange(previousRendered, previousPoints, previousOptions, previousStyle, previousNonGeometryDirty, sizeAnchor, currentRendered, previousTextAmplifiers);
|
|
2423
|
+
emitChange(previousRendered, previousPoints, previousOptions, previousStyle, previousNonGeometryDirty, sizeAnchor, currentRendered, previousTextAmplifiers, previousAmplifierPlacements);
|
|
2356
2424
|
}
|
|
2357
2425
|
function setOptions(partial) {
|
|
2358
2426
|
if (ctx.settled) return;
|
|
@@ -2361,6 +2429,7 @@ function runEdit(opts) {
|
|
|
2361
2429
|
const previousStyle = currentStyle;
|
|
2362
2430
|
const previousNonGeometryDirty = nonGeometryDirty;
|
|
2363
2431
|
const previousTextAmplifiers = workingTextAmplifiers;
|
|
2432
|
+
const previousAmplifierPlacements = snapshotAmplifierPlacements();
|
|
2364
2433
|
const previousRendered = rebuildRendered();
|
|
2365
2434
|
replaceWorkingOptions({
|
|
2366
2435
|
...workingOptions ?? {},
|
|
@@ -2376,7 +2445,7 @@ function runEdit(opts) {
|
|
|
2376
2445
|
renderPreview();
|
|
2377
2446
|
nonGeometryDirty = true;
|
|
2378
2447
|
dirty = true;
|
|
2379
|
-
emitChange(previousRendered, previousPoints, previousOptions, previousStyle, previousNonGeometryDirty, sizeAnchor, currentRendered, previousTextAmplifiers);
|
|
2448
|
+
emitChange(previousRendered, previousPoints, previousOptions, previousStyle, previousNonGeometryDirty, sizeAnchor, currentRendered, previousTextAmplifiers, previousAmplifierPlacements);
|
|
2380
2449
|
}
|
|
2381
2450
|
function setStyle(partial) {
|
|
2382
2451
|
if (ctx.settled) return;
|
|
@@ -2385,6 +2454,7 @@ function runEdit(opts) {
|
|
|
2385
2454
|
const previousStyle = currentStyle;
|
|
2386
2455
|
const previousNonGeometryDirty = nonGeometryDirty;
|
|
2387
2456
|
const previousTextAmplifiers = workingTextAmplifiers;
|
|
2457
|
+
const previousAmplifierPlacements = snapshotAmplifierPlacements();
|
|
2388
2458
|
const previousRendered = rebuildRendered();
|
|
2389
2459
|
currentStyle = {
|
|
2390
2460
|
...currentStyle ?? {},
|
|
@@ -2400,7 +2470,7 @@ function runEdit(opts) {
|
|
|
2400
2470
|
renderPreview();
|
|
2401
2471
|
nonGeometryDirty = true;
|
|
2402
2472
|
dirty = true;
|
|
2403
|
-
emitChange(previousRendered, previousPoints, previousOptions, previousStyle, previousNonGeometryDirty, sizeAnchor, currentRendered, previousTextAmplifiers);
|
|
2473
|
+
emitChange(previousRendered, previousPoints, previousOptions, previousStyle, previousNonGeometryDirty, sizeAnchor, currentRendered, previousTextAmplifiers, previousAmplifierPlacements);
|
|
2404
2474
|
}
|
|
2405
2475
|
/**
|
|
2406
2476
|
* Shallow-merge into the live working text amplifiers. Mirrors
|
|
@@ -2424,6 +2494,7 @@ function runEdit(opts) {
|
|
|
2424
2494
|
const previousStyle = currentStyle;
|
|
2425
2495
|
const previousNonGeometryDirty = nonGeometryDirty;
|
|
2426
2496
|
const previousTextAmplifiers = workingTextAmplifiers;
|
|
2497
|
+
const previousAmplifierPlacements = snapshotAmplifierPlacements();
|
|
2427
2498
|
const previousRendered = rebuildRendered();
|
|
2428
2499
|
const merged = { ...workingTextAmplifiers ?? {} };
|
|
2429
2500
|
for (const [key, value] of Object.entries(partial)) {
|
|
@@ -2444,7 +2515,26 @@ function runEdit(opts) {
|
|
|
2444
2515
|
renderPreview();
|
|
2445
2516
|
nonGeometryDirty = true;
|
|
2446
2517
|
dirty = true;
|
|
2447
|
-
emitChange(previousRendered, previousPoints, previousOptions, previousStyle, previousNonGeometryDirty, sizeAnchor, currentRendered, previousTextAmplifiers);
|
|
2518
|
+
emitChange(previousRendered, previousPoints, previousOptions, previousStyle, previousNonGeometryDirty, sizeAnchor, currentRendered, previousTextAmplifiers, previousAmplifierPlacements);
|
|
2519
|
+
}
|
|
2520
|
+
function setAmplifierPlacements(partial) {
|
|
2521
|
+
if (ctx.settled) return;
|
|
2522
|
+
const previousPoints = workingPoints.map((p) => [...p]);
|
|
2523
|
+
const previousOptions = { ...workingOptions ?? {} };
|
|
2524
|
+
const previousStyle = currentStyle;
|
|
2525
|
+
const previousNonGeometryDirty = nonGeometryDirty;
|
|
2526
|
+
const previousTextAmplifiers = workingTextAmplifiers;
|
|
2527
|
+
const previousAmplifierPlacements = snapshotAmplifierPlacements();
|
|
2528
|
+
const previousRendered = rebuildRendered();
|
|
2529
|
+
const next = { ...workingAmplifierPlacements ?? {} };
|
|
2530
|
+
for (const [field, position] of Object.entries(partial)) if (position === void 0) delete next[field];
|
|
2531
|
+
else next[field] = [...position];
|
|
2532
|
+
workingAmplifierPlacements = Object.keys(next).length > 0 ? next : void 0;
|
|
2533
|
+
const currentRendered = rebuildRendered();
|
|
2534
|
+
renderPreview();
|
|
2535
|
+
nonGeometryDirty = true;
|
|
2536
|
+
dirty = true;
|
|
2537
|
+
emitChange(previousRendered, previousPoints, previousOptions, previousStyle, previousNonGeometryDirty, sizeAnchor, currentRendered, previousTextAmplifiers, previousAmplifierPlacements);
|
|
2448
2538
|
}
|
|
2449
2539
|
/**
|
|
2450
2540
|
* Flip the size anchor for the measure under edit (ADR-0020). Re-renders the
|
|
@@ -2461,12 +2551,13 @@ function runEdit(opts) {
|
|
|
2461
2551
|
const previousNonGeometryDirty = nonGeometryDirty;
|
|
2462
2552
|
const previousSizeAnchor = sizeAnchor;
|
|
2463
2553
|
const previousTextAmplifiers = workingTextAmplifiers;
|
|
2554
|
+
const previousAmplifierPlacements = snapshotAmplifierPlacements();
|
|
2464
2555
|
const previousRendered = rebuildRendered();
|
|
2465
2556
|
sizeAnchor = next;
|
|
2466
2557
|
const currentRendered = rebuildRendered();
|
|
2467
2558
|
nonGeometryDirty = true;
|
|
2468
2559
|
dirty = true;
|
|
2469
|
-
emitChange(previousRendered, previousPoints, previousOptions, previousStyle, previousNonGeometryDirty, previousSizeAnchor, currentRendered, previousTextAmplifiers);
|
|
2560
|
+
emitChange(previousRendered, previousPoints, previousOptions, previousStyle, previousNonGeometryDirty, previousSizeAnchor, currentRendered, previousTextAmplifiers, previousAmplifierPlacements);
|
|
2470
2561
|
}
|
|
2471
2562
|
/** Apply a translate gesture by shifting every working point by `delta`. */
|
|
2472
2563
|
function commitTranslate(delta) {
|
|
@@ -2527,6 +2618,9 @@ function runEdit(opts) {
|
|
|
2527
2618
|
get textAmplifiers() {
|
|
2528
2619
|
return workingTextAmplifiers ? { ...workingTextAmplifiers } : void 0;
|
|
2529
2620
|
},
|
|
2621
|
+
get amplifierPlacements() {
|
|
2622
|
+
return snapshotAmplifierPlacements();
|
|
2623
|
+
},
|
|
2530
2624
|
get dirty() {
|
|
2531
2625
|
return dirty;
|
|
2532
2626
|
},
|
|
@@ -2561,6 +2655,9 @@ function runEdit(opts) {
|
|
|
2561
2655
|
setTextAmplifiers(partial) {
|
|
2562
2656
|
setTextAmplifiers(partial);
|
|
2563
2657
|
},
|
|
2658
|
+
setAmplifierPlacements(partial) {
|
|
2659
|
+
setAmplifierPlacements(partial);
|
|
2660
|
+
},
|
|
2564
2661
|
setSizeAnchor(next) {
|
|
2565
2662
|
setSizeAnchor(next);
|
|
2566
2663
|
},
|
|
@@ -2598,6 +2695,31 @@ function runEdit(opts) {
|
|
|
2598
2695
|
}
|
|
2599
2696
|
};
|
|
2600
2697
|
let activeDrag = null;
|
|
2698
|
+
let activeAmplifierDrag = null;
|
|
2699
|
+
function pickLabel(pixel, tolerance) {
|
|
2700
|
+
for (const feature of lastPreviewFeatures) {
|
|
2701
|
+
const key = feature.properties?.labelPlacementKey;
|
|
2702
|
+
if (typeof key !== "string" || feature.geometry?.type !== "Point") continue;
|
|
2703
|
+
const projected = driver.toPixel(feature.geometry.coordinates);
|
|
2704
|
+
if (!projected) continue;
|
|
2705
|
+
for (const handle of [false, true]) {
|
|
2706
|
+
const targetY = projected[1] - (handle ? LABEL_HANDLE_OFFSET_PX : 0);
|
|
2707
|
+
if (Math.hypot(projected[0] - pixel[0], targetY - pixel[1]) <= tolerance) return {
|
|
2708
|
+
key,
|
|
2709
|
+
handle
|
|
2710
|
+
};
|
|
2711
|
+
}
|
|
2712
|
+
}
|
|
2713
|
+
return null;
|
|
2714
|
+
}
|
|
2715
|
+
/**
|
|
2716
|
+
* The label anchor a pointer pixel maps to: 1:1 for a grab on the label
|
|
2717
|
+
* itself, offset back down by the grip clearance when the drag rides the
|
|
2718
|
+
* visible handle, so the label lands where the anchor (not the grip) points.
|
|
2719
|
+
*/
|
|
2720
|
+
function labelAnchorFromPointer(pixel, handle) {
|
|
2721
|
+
return driver.fromPixel([pixel[0], pixel[1] + (handle ? LABEL_HANDLE_OFFSET_PX : 0)]);
|
|
2722
|
+
}
|
|
2601
2723
|
/**
|
|
2602
2724
|
* A live transform-box gesture (corner scale, box rotate, or body
|
|
2603
2725
|
* translate — ADR-0017). `lastSnapshot` starts as the pre-drag points/box
|
|
@@ -2646,6 +2768,18 @@ function runEdit(opts) {
|
|
|
2646
2768
|
const onDriverDown = (pixel, info) => {
|
|
2647
2769
|
if (ctx.settled || dragInFlight) return;
|
|
2648
2770
|
const tol = info?.hitTolerance ?? HANDLE_HIT_TOLERANCE_PX$1;
|
|
2771
|
+
const labelHit = modes.includes("labeldrag") ? pickLabel(pixel, tol) : null;
|
|
2772
|
+
if (labelHit) {
|
|
2773
|
+
activeAmplifierDrag = {
|
|
2774
|
+
field: labelHit.key,
|
|
2775
|
+
handle: labelHit.handle,
|
|
2776
|
+
previous: snapshotAmplifierPlacements(),
|
|
2777
|
+
downPixel: pixel
|
|
2778
|
+
};
|
|
2779
|
+
dragInFlight = true;
|
|
2780
|
+
driver.setDragPanEnabled(false);
|
|
2781
|
+
return;
|
|
2782
|
+
}
|
|
2649
2783
|
const hit = pickHandle(buildHandleSet(workingPoints, modes, {
|
|
2650
2784
|
includeMidpoints: midpointHandlesEnabled,
|
|
2651
2785
|
trailingFixedSlots,
|
|
@@ -2705,6 +2839,7 @@ function runEdit(opts) {
|
|
|
2705
2839
|
cornerIndex: boxHit.kind === "scale" ? boxHit.index : void 0
|
|
2706
2840
|
}),
|
|
2707
2841
|
preDragPoints,
|
|
2842
|
+
preDragAmplifierPlacements: snapshotAmplifierPlacements(),
|
|
2708
2843
|
preDragBox: box,
|
|
2709
2844
|
downPixel: pixel,
|
|
2710
2845
|
lastSnapshot: {
|
|
@@ -2731,13 +2866,23 @@ function runEdit(opts) {
|
|
|
2731
2866
|
const snapshot = activeBoxDrag.fsm.move(pixel);
|
|
2732
2867
|
activeBoxDrag.lastSnapshot = snapshot;
|
|
2733
2868
|
workingPoints.splice(0, workingPoints.length, ...snapshot.points);
|
|
2869
|
+
workingAmplifierPlacements = transformAmplifierPlacements(activeBoxDrag.preDragAmplifierPlacements, activeBoxDrag.preDragBox, snapshot.box, driver);
|
|
2734
2870
|
renderPreview();
|
|
2735
2871
|
renderHandlesLayer();
|
|
2736
2872
|
recomputeDirty();
|
|
2737
2873
|
}
|
|
2738
2874
|
const onDriverMove = (pixel) => {
|
|
2739
2875
|
if (ctx.settled) return;
|
|
2740
|
-
if (
|
|
2876
|
+
if (activeAmplifierDrag) {
|
|
2877
|
+
const position = labelAnchorFromPointer(pixel, activeAmplifierDrag.handle);
|
|
2878
|
+
if (!position) return;
|
|
2879
|
+
workingAmplifierPlacements = {
|
|
2880
|
+
...workingAmplifierPlacements ?? {},
|
|
2881
|
+
[activeAmplifierDrag.field]: position
|
|
2882
|
+
};
|
|
2883
|
+
renderPreview();
|
|
2884
|
+
renderHandlesLayer();
|
|
2885
|
+
} else if (activeDrag) paintDragSnapshot(pixel);
|
|
2741
2886
|
else if (activeBoxDrag) paintBoxDragSnapshot(pixel);
|
|
2742
2887
|
};
|
|
2743
2888
|
/** Reset drag-in-flight bookkeeping shared by every "gesture ended" path. */
|
|
@@ -2747,6 +2892,19 @@ function runEdit(opts) {
|
|
|
2747
2892
|
drainPendingModes();
|
|
2748
2893
|
}
|
|
2749
2894
|
const onDriverUp = (pixel) => {
|
|
2895
|
+
if (activeAmplifierDrag) {
|
|
2896
|
+
const { field, handle, previous, downPixel } = activeAmplifierDrag;
|
|
2897
|
+
activeAmplifierDrag = null;
|
|
2898
|
+
const position = labelAnchorFromPointer(pixel, handle);
|
|
2899
|
+
workingAmplifierPlacements = previous;
|
|
2900
|
+
endDragBookkeeping();
|
|
2901
|
+
if (position && Math.hypot(pixel[0] - downPixel[0], pixel[1] - downPixel[1]) > CLICK_SLOP_PX$1) setAmplifierPlacements({ [field]: position });
|
|
2902
|
+
else {
|
|
2903
|
+
renderPreview();
|
|
2904
|
+
renderHandlesLayer();
|
|
2905
|
+
}
|
|
2906
|
+
return;
|
|
2907
|
+
}
|
|
2750
2908
|
if (activeDrag) {
|
|
2751
2909
|
if (ctx.settled) {
|
|
2752
2910
|
activeDrag = null;
|
|
@@ -2769,12 +2927,13 @@ function runEdit(opts) {
|
|
|
2769
2927
|
driver.setDragPanEnabled(true);
|
|
2770
2928
|
return;
|
|
2771
2929
|
}
|
|
2772
|
-
const { fsm, preDragPoints, preDragBox, downPixel, preDragOptions } = activeBoxDrag;
|
|
2930
|
+
const { fsm, preDragPoints, preDragAmplifierPlacements, preDragBox, downPixel, preDragOptions } = activeBoxDrag;
|
|
2773
2931
|
activeBoxDrag = null;
|
|
2774
2932
|
driver.setDragPanEnabled(true);
|
|
2775
2933
|
const distance = Math.hypot(pixel[0] - downPixel[0], pixel[1] - downPixel[1]);
|
|
2776
2934
|
if (fsm.kind === "body" && distance <= CLICK_SLOP_PX$1) {
|
|
2777
2935
|
replaceWorkingPoints(preDragPoints);
|
|
2936
|
+
workingAmplifierPlacements = preDragAmplifierPlacements;
|
|
2778
2937
|
renderPreview();
|
|
2779
2938
|
renderHandlesLayer();
|
|
2780
2939
|
recomputeDirty();
|
|
@@ -2784,8 +2943,9 @@ function runEdit(opts) {
|
|
|
2784
2943
|
}
|
|
2785
2944
|
const finalSnapshot = fsm.move(pixel);
|
|
2786
2945
|
replaceWorkingPoints(preDragPoints);
|
|
2946
|
+
workingAmplifierPlacements = preDragAmplifierPlacements;
|
|
2787
2947
|
if (fsm.kind === "rotate") boxAngle += finalSnapshot.box.angle - preDragBox.angle;
|
|
2788
|
-
commitGesture(finalSnapshot.points, preDragOptions);
|
|
2948
|
+
commitGesture(finalSnapshot.points, preDragOptions, transformAmplifierPlacements(preDragAmplifierPlacements, preDragBox, finalSnapshot.box, driver));
|
|
2789
2949
|
return;
|
|
2790
2950
|
}
|
|
2791
2951
|
};
|
|
@@ -2858,10 +3018,11 @@ function runEdit(opts) {
|
|
|
2858
3018
|
unsubDown();
|
|
2859
3019
|
unsubMove();
|
|
2860
3020
|
unsubUp();
|
|
2861
|
-
if (activeDrag !== null || activeBoxDrag !== null) {
|
|
3021
|
+
if (activeDrag !== null || activeBoxDrag !== null || activeAmplifierDrag !== null) {
|
|
2862
3022
|
driver.setDragPanEnabled(true);
|
|
2863
3023
|
activeDrag = null;
|
|
2864
3024
|
activeBoxDrag = null;
|
|
3025
|
+
activeAmplifierDrag = null;
|
|
2865
3026
|
dragInFlight = false;
|
|
2866
3027
|
}
|
|
2867
3028
|
driver.dispose();
|
|
@@ -2972,6 +3133,7 @@ function runGroupTransform(opts) {
|
|
|
2972
3133
|
measure,
|
|
2973
3134
|
editStartPoints,
|
|
2974
3135
|
workingPoints: editStartPoints.map((p) => [...p]),
|
|
3136
|
+
workingAmplifierPlacements: measure.amplifierPlacements ? structuredClone(measure.amplifierPlacements) : void 0,
|
|
2975
3137
|
optionsOverride: null,
|
|
2976
3138
|
box: null,
|
|
2977
3139
|
lastPreviewFeatures: []
|
|
@@ -3003,7 +3165,8 @@ function runGroupTransform(opts) {
|
|
|
3003
3165
|
const working = {
|
|
3004
3166
|
...member.measure,
|
|
3005
3167
|
options: mergeOptions(member.measure.options, override),
|
|
3006
|
-
controlPoints: member.workingPoints.map((p) => [...p])
|
|
3168
|
+
controlPoints: member.workingPoints.map((p) => [...p]),
|
|
3169
|
+
...member.workingAmplifierPlacements ? { amplifierPlacements: member.workingAmplifierPlacements } : {}
|
|
3007
3170
|
};
|
|
3008
3171
|
const committed = sizeAnchor === "screen" ? working : bakePixelSizeToGround(working, adapter);
|
|
3009
3172
|
return {
|
|
@@ -3017,7 +3180,8 @@ function runGroupTransform(opts) {
|
|
|
3017
3180
|
return {
|
|
3018
3181
|
...member.measure,
|
|
3019
3182
|
options: effectiveOptions(i),
|
|
3020
|
-
controlPoints: member.workingPoints.map((p) => [...p])
|
|
3183
|
+
controlPoints: member.workingPoints.map((p) => [...p]),
|
|
3184
|
+
...member.workingAmplifierPlacements ? { amplifierPlacements: member.workingAmplifierPlacements } : {}
|
|
3021
3185
|
};
|
|
3022
3186
|
}
|
|
3023
3187
|
let dirty = false;
|
|
@@ -3184,6 +3348,21 @@ function runGroupTransform(opts) {
|
|
|
3184
3348
|
member.workingPoints.splice(0, member.workingPoints.length, ...snapshot[i]);
|
|
3185
3349
|
});
|
|
3186
3350
|
}
|
|
3351
|
+
function cloneAmplifierPlacementsByMember() {
|
|
3352
|
+
return members.map((member) => member.workingAmplifierPlacements ? structuredClone(member.workingAmplifierPlacements) : void 0);
|
|
3353
|
+
}
|
|
3354
|
+
/**
|
|
3355
|
+
* Adopts `placements[i]` by reference — callers hand over ownership (the
|
|
3356
|
+
* sources are freshly built transforms or snapshot arrays that are dead
|
|
3357
|
+
* after the restore), and working placements are only ever replaced
|
|
3358
|
+
* wholesale, never mutated in place. This runs on every pointermove of a
|
|
3359
|
+
* box drag, so it must not deep-clone.
|
|
3360
|
+
*/
|
|
3361
|
+
function applyAmplifierPlacementsByMember(placements) {
|
|
3362
|
+
members.forEach((member, i) => {
|
|
3363
|
+
member.workingAmplifierPlacements = placements[i];
|
|
3364
|
+
});
|
|
3365
|
+
}
|
|
3187
3366
|
function cloneOptionsOverrideByMember() {
|
|
3188
3367
|
return members.map((m) => m.optionsOverride ? { ...m.optionsOverride } : null);
|
|
3189
3368
|
}
|
|
@@ -3215,7 +3394,7 @@ function runGroupTransform(opts) {
|
|
|
3215
3394
|
* instance to all listeners in order, deferred `reject`/`close`/`abort`
|
|
3216
3395
|
* (close/abort override reject), exception collection with `AggregateError`.
|
|
3217
3396
|
*/
|
|
3218
|
-
function emitChange(previousRendered, previousPointsByMember, previousSizeAnchor, previousNonGeometryDirty, previousOptionsOverrideByMember, currentRendered) {
|
|
3397
|
+
function emitChange(previousRendered, previousPointsByMember, previousAmplifierPlacementsByMember, previousSizeAnchor, previousNonGeometryDirty, previousOptionsOverrideByMember, currentRendered) {
|
|
3219
3398
|
if (ctx.settled) return;
|
|
3220
3399
|
let rejectRequested = false;
|
|
3221
3400
|
let closeRequested = false;
|
|
@@ -3245,6 +3424,7 @@ function runGroupTransform(opts) {
|
|
|
3245
3424
|
else if (closeRequested) commitClose();
|
|
3246
3425
|
else if (rejectRequested && !ctx.settled && members === membersAtEmission) {
|
|
3247
3426
|
restorePointsByMember(previousPointsByMember);
|
|
3427
|
+
applyAmplifierPlacementsByMember(previousAmplifierPlacementsByMember);
|
|
3248
3428
|
sizeAnchor = previousSizeAnchor;
|
|
3249
3429
|
nonGeometryDirty = previousNonGeometryDirty;
|
|
3250
3430
|
applyOptionsOverrides(previousOptionsOverrideByMember);
|
|
@@ -3277,17 +3457,20 @@ function runGroupTransform(opts) {
|
|
|
3277
3457
|
}
|
|
3278
3458
|
return snapshots;
|
|
3279
3459
|
}
|
|
3280
|
-
function commitGesture(nextConcatPoints, preGestureOptionsOverrideByMember) {
|
|
3460
|
+
function commitGesture(nextConcatPoints, preGestureOptionsOverrideByMember, nextAmplifierPlacementsByMember) {
|
|
3281
3461
|
if (ctx.settled) return;
|
|
3282
3462
|
const previousPointsByMember = clonePointsByMember();
|
|
3463
|
+
const previousAmplifierPlacementsByMember = cloneAmplifierPlacementsByMember();
|
|
3283
3464
|
const previousOptionsOverrideByMember = preGestureOptionsOverrideByMember ?? cloneOptionsOverrideByMember();
|
|
3284
3465
|
const previousRendered = rebuildRenderedAllWith(previousOptionsOverrideByMember);
|
|
3285
3466
|
applyConcatenatedPoints(nextConcatPoints);
|
|
3467
|
+
if (nextAmplifierPlacementsByMember) applyAmplifierPlacementsByMember(nextAmplifierPlacementsByMember);
|
|
3286
3468
|
let currentRendered;
|
|
3287
3469
|
try {
|
|
3288
3470
|
currentRendered = rebuildRenderedAll();
|
|
3289
3471
|
} catch (err) {
|
|
3290
3472
|
restorePointsByMember(previousPointsByMember);
|
|
3473
|
+
applyAmplifierPlacementsByMember(previousAmplifierPlacementsByMember);
|
|
3291
3474
|
dragInFlight = false;
|
|
3292
3475
|
throw err;
|
|
3293
3476
|
}
|
|
@@ -3295,7 +3478,7 @@ function runGroupTransform(opts) {
|
|
|
3295
3478
|
renderHandlesLayer();
|
|
3296
3479
|
recomputeDirty();
|
|
3297
3480
|
dragInFlight = false;
|
|
3298
|
-
emitChange(previousRendered, previousPointsByMember, sizeAnchor, nonGeometryDirty, previousOptionsOverrideByMember, currentRendered);
|
|
3481
|
+
emitChange(previousRendered, previousPointsByMember, previousAmplifierPlacementsByMember, sizeAnchor, nonGeometryDirty, previousOptionsOverrideByMember, currentRendered);
|
|
3299
3482
|
}
|
|
3300
3483
|
function setSizeAnchor(next) {
|
|
3301
3484
|
if (ctx.settled || sizeAnchor === next) return;
|
|
@@ -3303,12 +3486,13 @@ function runGroupTransform(opts) {
|
|
|
3303
3486
|
const previousSizeAnchor = sizeAnchor;
|
|
3304
3487
|
const previousNonGeometryDirty = nonGeometryDirty;
|
|
3305
3488
|
const previousOptionsOverrideByMember = cloneOptionsOverrideByMember();
|
|
3489
|
+
const previousAmplifierPlacementsByMember = cloneAmplifierPlacementsByMember();
|
|
3306
3490
|
const previousRendered = rebuildRenderedAll();
|
|
3307
3491
|
sizeAnchor = next;
|
|
3308
3492
|
const currentRendered = rebuildRenderedAll();
|
|
3309
3493
|
nonGeometryDirty = true;
|
|
3310
3494
|
dirty = true;
|
|
3311
|
-
emitChange(previousRendered, previousPointsByMember, previousSizeAnchor, previousNonGeometryDirty, previousOptionsOverrideByMember, currentRendered);
|
|
3495
|
+
emitChange(previousRendered, previousPointsByMember, previousAmplifierPlacementsByMember, previousSizeAnchor, previousNonGeometryDirty, previousOptionsOverrideByMember, currentRendered);
|
|
3312
3496
|
}
|
|
3313
3497
|
/**
|
|
3314
3498
|
* Cancel an in-flight box drag without committing it: restore the pre-drag
|
|
@@ -3320,10 +3504,11 @@ function runGroupTransform(opts) {
|
|
|
3320
3504
|
*/
|
|
3321
3505
|
function cancelActiveBoxDrag() {
|
|
3322
3506
|
if (!activeBoxDrag) return;
|
|
3323
|
-
const { preDragPoints, preDragOptionsOverrideByMember } = activeBoxDrag;
|
|
3507
|
+
const { preDragPoints, preDragAmplifierPlacementsByMember, preDragOptionsOverrideByMember } = activeBoxDrag;
|
|
3324
3508
|
activeBoxDrag = null;
|
|
3325
3509
|
driver.setDragPanEnabled(true);
|
|
3326
3510
|
applyConcatenatedPoints(preDragPoints);
|
|
3511
|
+
applyAmplifierPlacementsByMember(preDragAmplifierPlacementsByMember);
|
|
3327
3512
|
applyOptionsOverrides(preDragOptionsOverrideByMember);
|
|
3328
3513
|
dragInFlight = false;
|
|
3329
3514
|
}
|
|
@@ -3413,6 +3598,7 @@ function runGroupTransform(opts) {
|
|
|
3413
3598
|
renderHandlesLayer();
|
|
3414
3599
|
}
|
|
3415
3600
|
const preDragPoints = concatWorkingPoints();
|
|
3601
|
+
const preDragAmplifierPlacementsByMember = cloneAmplifierPlacementsByMember();
|
|
3416
3602
|
activeBoxDrag = {
|
|
3417
3603
|
fsm: beginBoxDrag({
|
|
3418
3604
|
kind: boxHit.kind,
|
|
@@ -3429,7 +3615,8 @@ function runGroupTransform(opts) {
|
|
|
3429
3615
|
points: preDragPoints,
|
|
3430
3616
|
box: groupBox
|
|
3431
3617
|
},
|
|
3432
|
-
preDragOptionsOverrideByMember
|
|
3618
|
+
preDragOptionsOverrideByMember,
|
|
3619
|
+
preDragAmplifierPlacementsByMember
|
|
3433
3620
|
};
|
|
3434
3621
|
dragInFlight = true;
|
|
3435
3622
|
driver.setDragPanEnabled(false);
|
|
@@ -3439,6 +3626,7 @@ function runGroupTransform(opts) {
|
|
|
3439
3626
|
const snapshot = activeBoxDrag.fsm.move(pixel);
|
|
3440
3627
|
activeBoxDrag.lastSnapshot = snapshot;
|
|
3441
3628
|
applyConcatenatedPoints(snapshot.points);
|
|
3629
|
+
applyAmplifierPlacementsByMember(activeBoxDrag.preDragAmplifierPlacementsByMember.map((placements) => transformAmplifierPlacements(placements, activeBoxDrag.preDragBox, snapshot.box, driver)));
|
|
3442
3630
|
renderAllPreviews();
|
|
3443
3631
|
renderHandlesLayer();
|
|
3444
3632
|
recomputeDirty();
|
|
@@ -3455,12 +3643,13 @@ function runGroupTransform(opts) {
|
|
|
3455
3643
|
driver.setDragPanEnabled(true);
|
|
3456
3644
|
return;
|
|
3457
3645
|
}
|
|
3458
|
-
const { fsm, preDragPoints, preDragBox, downPixel, preDragOptionsOverrideByMember } = activeBoxDrag;
|
|
3646
|
+
const { fsm, preDragPoints, preDragBox, downPixel, preDragOptionsOverrideByMember, preDragAmplifierPlacementsByMember } = activeBoxDrag;
|
|
3459
3647
|
activeBoxDrag = null;
|
|
3460
3648
|
driver.setDragPanEnabled(true);
|
|
3461
3649
|
const distance = Math.hypot(pixel[0] - downPixel[0], pixel[1] - downPixel[1]);
|
|
3462
3650
|
if (fsm.kind === "body" && distance <= CLICK_SLOP_PX) {
|
|
3463
3651
|
applyConcatenatedPoints(preDragPoints);
|
|
3652
|
+
applyAmplifierPlacementsByMember(preDragAmplifierPlacementsByMember);
|
|
3464
3653
|
renderAllPreviews();
|
|
3465
3654
|
renderHandlesLayer();
|
|
3466
3655
|
recomputeDirty();
|
|
@@ -3470,8 +3659,9 @@ function runGroupTransform(opts) {
|
|
|
3470
3659
|
}
|
|
3471
3660
|
const finalSnapshot = fsm.move(pixel);
|
|
3472
3661
|
applyConcatenatedPoints(preDragPoints);
|
|
3662
|
+
applyAmplifierPlacementsByMember(preDragAmplifierPlacementsByMember);
|
|
3473
3663
|
if (fsm.kind === "rotate") boxAngle += finalSnapshot.box.angle - preDragBox.angle;
|
|
3474
|
-
commitGesture(finalSnapshot.points, preDragOptionsOverrideByMember);
|
|
3664
|
+
commitGesture(finalSnapshot.points, preDragOptionsOverrideByMember, preDragAmplifierPlacementsByMember.map((placements) => transformAmplifierPlacements(placements, preDragBox, finalSnapshot.box, driver)));
|
|
3475
3665
|
};
|
|
3476
3666
|
let pickPreview;
|
|
3477
3667
|
let pickHandles;
|
|
@@ -4324,6 +4514,12 @@ const BUILT_IN_INTERACTION_STYLE = {
|
|
|
4324
4514
|
strokeColor: "#6b7280",
|
|
4325
4515
|
strokeWidth: 1.5
|
|
4326
4516
|
},
|
|
4517
|
+
labelHandle: {
|
|
4518
|
+
pointRadius: 6,
|
|
4519
|
+
fillColor: "#7c3aed",
|
|
4520
|
+
strokeColor: "#ffffff",
|
|
4521
|
+
strokeWidth: 2
|
|
4522
|
+
},
|
|
4327
4523
|
scaleHandle: {
|
|
4328
4524
|
pointRadius: 5,
|
|
4329
4525
|
fillColor: "#ffffff",
|
|
@@ -4340,6 +4536,7 @@ const BUILT_IN_INTERACTION_STYLE = {
|
|
|
4340
4536
|
const HANDLE_SLOTS = [
|
|
4341
4537
|
"vertexHandle",
|
|
4342
4538
|
"midpointHandle",
|
|
4539
|
+
"labelHandle",
|
|
4343
4540
|
"scaleHandle",
|
|
4344
4541
|
"rotateHandle"
|
|
4345
4542
|
];
|
|
@@ -4403,4 +4600,4 @@ function nearestWorldCopyPosition(position, refLng) {
|
|
|
4403
4600
|
return [nearestWorldCopyLng(position[0], refLng), ...position.slice(1)];
|
|
4404
4601
|
}
|
|
4405
4602
|
//#endregion
|
|
4406
|
-
export { BaseMapAdapter, DEFAULT_TEXT_SIZE_MAX_PIXELS, DEFAULT_TEXT_SIZE_MIN_PIXELS, PointerCallbackHub, TOUCH_HIT_TOLERANCE_PX, TacticalDraw, TacticalDrawAbortError, TacticalDrawDestroyedError, bindAbortable, coerceHandleStyle, combineWithHostSignal, getSizeAnchor, hitTestFeature, ignoreAbort, isTacticalDrawAbortError, nearestWorldCopyLng, nearestWorldCopyPosition, pickHitTolerance, resolveGroundTextSizePixels, tryControlMeasureIdFromFeature };
|
|
4603
|
+
export { BaseMapAdapter, DEFAULT_LABEL_SIZE_PIXELS, DEFAULT_TEXT_SIZE_MAX_PIXELS, DEFAULT_TEXT_SIZE_MIN_PIXELS, PointerCallbackHub, TOUCH_HIT_TOLERANCE_PX, TacticalDraw, TacticalDrawAbortError, TacticalDrawDestroyedError, bindAbortable, coerceHandleStyle, combineWithHostSignal, getSizeAnchor, hitTestFeature, ignoreAbort, isTacticalDrawAbortError, nearestWorldCopyLng, nearestWorldCopyPosition, pickHitTolerance, resolveGroundTextSizePixels, tryControlMeasureIdFromFeature };
|
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.17",
|
|
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.13"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@arethetypeswrong/core": "^0.18.3",
|