@orbat-mapper/tactical-draw 0.2.0-alpha.8 → 0.2.0-alpha.9
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 +72 -9
- package/dist/index.mjs +83 -4
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -648,13 +648,15 @@ interface TransformSession {
|
|
|
648
648
|
* **Return value** — the committed exit snapshots of the members removed *by
|
|
649
649
|
* this call*, built exactly like [[close]]'s snapshots (pixel size baked to
|
|
650
650
|
* ground unless `sizeAnchor` is `"screen"`, then cloned and rendered); an
|
|
651
|
-
* empty array when nothing was removed.
|
|
652
|
-
*
|
|
653
|
-
*
|
|
654
|
-
*
|
|
655
|
-
*
|
|
656
|
-
*
|
|
657
|
-
*
|
|
651
|
+
* empty array when nothing was removed. The outer `editMany` promise resolves
|
|
652
|
+
* with the snapshots of the members in the set **at close time only**.
|
|
653
|
+
* Removed members are NOT written back to the graphics layer by the
|
|
654
|
+
* controller — the host folds the returned snapshots into its document and
|
|
655
|
+
* calls `td.render()`, the same contract as [[close]]. A removed member's
|
|
656
|
+
* exit snapshot is also delivered through [[onCommit]] — the recommended
|
|
657
|
+
* channel, since it covers this return value and the close-time promise
|
|
658
|
+
* resolution through a single subscription. Returns `undefined` (a silent
|
|
659
|
+
* no-op) after the session has settled.
|
|
658
660
|
*
|
|
659
661
|
* `setMembers([])` on a live session delegates to [[close]]: it commits every
|
|
660
662
|
* current member, resolves the promise with their snapshots, and returns that
|
|
@@ -667,8 +669,9 @@ interface TransformSession {
|
|
|
667
669
|
* set at close time) and resolves the outer `editMany` promise with the same
|
|
668
670
|
* value (the same reference — no double-bake of ground-anchored sizes). A
|
|
669
671
|
* member removed mid-session via [[setMembers]] is not included here; its exit
|
|
670
|
-
* snapshot was returned from that `setMembers` call
|
|
671
|
-
* the session has
|
|
672
|
+
* snapshot was returned from that `setMembers` call (and delivered to
|
|
673
|
+
* [[onCommit]] at that time). Returns `undefined` after the session has
|
|
674
|
+
* settled (idempotent no-op).
|
|
672
675
|
*/
|
|
673
676
|
close(): ControlMeasureSnapshot[] | undefined;
|
|
674
677
|
/** Reject the outer `editMany` promise with reason `"session"`. */
|
|
@@ -686,6 +689,33 @@ interface TransformSession {
|
|
|
686
689
|
* have run — mirrors `EditSession.onChange` (issue #49).
|
|
687
690
|
*/
|
|
688
691
|
onChange(listener: (event: TransformChangeEvent) => void): () => void;
|
|
692
|
+
/**
|
|
693
|
+
* Subscribe to committed exit-snapshot batches — the single delivery channel
|
|
694
|
+
* for every `ControlMeasureSnapshot[]` this session ever commits (#191). Fires
|
|
695
|
+
* exactly once per batch:
|
|
696
|
+
*
|
|
697
|
+
* - The members [[setMembers]] removes, but only when that call actually
|
|
698
|
+
* removed at least one member.
|
|
699
|
+
* - The close-time members on any commit path: [[close]], `setMembers([])`,
|
|
700
|
+
* a click-away close, or `TransformChangeEvent.close()`.
|
|
701
|
+
*
|
|
702
|
+
* Never fires on an abort/reject path. Fires synchronously from within the
|
|
703
|
+
* `setMembers`/`close` call that produced the batch, before that call
|
|
704
|
+
* returns, and the array delivered here is the same reference that call
|
|
705
|
+
* returns (and, for the close batch, the same reference the `editMany`
|
|
706
|
+
* promise resolves with). This is the recommended single fold path for exit
|
|
707
|
+
* state — subscribe once in `onSession` and every committed snapshot, from
|
|
708
|
+
* either delivery site, arrives here exactly once; the `setMembers` return
|
|
709
|
+
* value and the promise resolution are unchanged and remain valid for
|
|
710
|
+
* backward compatibility.
|
|
711
|
+
*
|
|
712
|
+
* Listener exceptions are collected; after every listener has run, they are
|
|
713
|
+
* rethrown (one if single, `AggregateError` otherwise) — mirrors
|
|
714
|
+
* [[onChange]]. Returns an unsubscribe function; registering after the
|
|
715
|
+
* session has settled returns a no-op unsubscribe and the listener never
|
|
716
|
+
* fires.
|
|
717
|
+
*/
|
|
718
|
+
onCommit(handler: (snapshots: ControlMeasureSnapshot[]) => void): () => void;
|
|
689
719
|
}
|
|
690
720
|
/**
|
|
691
721
|
* Delivered to `TransformSession.onChange` listeners once per completed group
|
|
@@ -1104,8 +1134,41 @@ declare class TacticalDraw {
|
|
|
1104
1134
|
* `measures.length` must be >= 1; a single-element array behaves like an
|
|
1105
1135
|
* `edit` with only `transform` mode. Throws `TypeError` synchronously (before
|
|
1106
1136
|
* any preemption) for an empty array or duplicate ids.
|
|
1137
|
+
*
|
|
1138
|
+
* For a selection that changes over time (shift-click toggle, marquee
|
|
1139
|
+
* replace), prefer [[syncTransformMembers]] over hand-rolling the
|
|
1140
|
+
* live-session-vs-fresh-`editMany` branch, and subscribe to
|
|
1141
|
+
* `TransformSession.onCommit` as the single fold path for exit snapshots.
|
|
1107
1142
|
*/
|
|
1108
1143
|
editMany(measures: ControlMeasure[], options?: EditManyOptions): Promise<ControlMeasureSnapshot[]>;
|
|
1144
|
+
/**
|
|
1145
|
+
* One-call selection-sync: mutate the live group transform's member set in
|
|
1146
|
+
* place, or start a fresh `editMany` when no transform is live (#191). This
|
|
1147
|
+
* owns the "live session → `setMembers`, else → `editMany`" branch that every
|
|
1148
|
+
* host otherwise hand-rolls against `activeTransformSession`.
|
|
1149
|
+
*
|
|
1150
|
+
* The facade knows session liveness synchronously — `activeTransformSessionRef`
|
|
1151
|
+
* is cleared inside the settle path, before control returns to host code — so
|
|
1152
|
+
* unlike a host's own `activeTransformSession ? setMembers : editMany` check,
|
|
1153
|
+
* this can never observe a stale ref pointing at an already-settled session.
|
|
1154
|
+
* The settled-race fall-through hosts had to hand-roll for that gap is owned
|
|
1155
|
+
* here instead: a `setMembers` call that (implausibly) still returns
|
|
1156
|
+
* `undefined` falls through to a fresh `editMany` rather than silently
|
|
1157
|
+
* dropping the selection.
|
|
1158
|
+
*
|
|
1159
|
+
* `measures: []` closes any live session (committing every member) and is a
|
|
1160
|
+
* no-op while idle. With a live session, `options` is ignored — session-scoped
|
|
1161
|
+
* state (`sizeAnchor`, box angle, close-on-click-away policy) is sticky per
|
|
1162
|
+
* `setMembers`'s contract; `options` applies only when this call starts a
|
|
1163
|
+
* fresh `editMany`. Exit snapshots are delivered via the session's
|
|
1164
|
+
* `onCommit` — subscribe once in `options.onSession`. The underlying
|
|
1165
|
+
* `editMany` promise is owned by the facade; its abort rejections are
|
|
1166
|
+
* swallowed here (non-abort errors surface as unhandled rejections). Throws
|
|
1167
|
+
* `TypeError` synchronously on duplicate ids, from either path. Starting a
|
|
1168
|
+
* fresh transform preempts any other in-flight draw/edit exactly like
|
|
1169
|
+
* `editMany`.
|
|
1170
|
+
*/
|
|
1171
|
+
syncTransformMembers(measures: readonly ControlMeasure[], options?: EditManyOptions): void;
|
|
1109
1172
|
/**
|
|
1110
1173
|
* Subscribe to picks on committed control measures.
|
|
1111
1174
|
*
|
package/dist/index.mjs
CHANGED
|
@@ -2737,6 +2737,7 @@ function runGroupTransform(opts) {
|
|
|
2737
2737
|
/** Same click-away suppression role as `edit-controller.ts`'s flag of the same name. */
|
|
2738
2738
|
let suppressClickAway = false;
|
|
2739
2739
|
const changeListeners = /* @__PURE__ */ new Set();
|
|
2740
|
+
const commitListeners = /* @__PURE__ */ new Set();
|
|
2740
2741
|
/** Session-scoped box rotation (ADR-0017 decision #2), shared by the whole group. */
|
|
2741
2742
|
let boxAngle = 0;
|
|
2742
2743
|
/** The group box fitted over every member's rendered geometry, or `null` when nothing projects this frame. */
|
|
@@ -2944,7 +2945,7 @@ function runGroupTransform(opts) {
|
|
|
2944
2945
|
errors.push(err);
|
|
2945
2946
|
}
|
|
2946
2947
|
if (abortRequested) ctx.fail(new TacticalDrawAbortError("session"));
|
|
2947
|
-
else if (closeRequested)
|
|
2948
|
+
else if (closeRequested) commitClose();
|
|
2948
2949
|
else if (rejectRequested && !ctx.settled && members === membersAtEmission) {
|
|
2949
2950
|
restorePointsByMember(previousPointsByMember);
|
|
2950
2951
|
sizeAnchor = previousSizeAnchor;
|
|
@@ -2957,6 +2958,35 @@ function runGroupTransform(opts) {
|
|
|
2957
2958
|
if (errors.length === 1) throw errors[0];
|
|
2958
2959
|
if (errors.length > 1) throw new AggregateError(errors, "TransformSession.onChange listener errors");
|
|
2959
2960
|
}
|
|
2961
|
+
/**
|
|
2962
|
+
* Deliver a committed exit-snapshot batch to every `onCommit` listener — the
|
|
2963
|
+
* single delivery channel for both the `setMembers`-removed batch and the
|
|
2964
|
+
* close-time batch (#191). Same exception-collection contract as `emitChange`.
|
|
2965
|
+
*/
|
|
2966
|
+
function emitCommit(snapshots) {
|
|
2967
|
+
const errors = [];
|
|
2968
|
+
for (const listener of [...commitListeners]) try {
|
|
2969
|
+
listener(snapshots);
|
|
2970
|
+
} catch (err) {
|
|
2971
|
+
errors.push(err);
|
|
2972
|
+
}
|
|
2973
|
+
if (errors.length === 1) throw errors[0];
|
|
2974
|
+
if (errors.length > 1) throw new AggregateError(errors, "TransformSession.onCommit listener errors");
|
|
2975
|
+
}
|
|
2976
|
+
/**
|
|
2977
|
+
* Commit via `ctx.commit` and emit the close batch to `onCommit` listeners.
|
|
2978
|
+
* The emission happens after the interaction core has settled (teardown and
|
|
2979
|
+
* `onSettled` already ran inside `ctx.commit`), so a listener that calls
|
|
2980
|
+
* `td.render()` sees these members no longer excluded from reconcile — the
|
|
2981
|
+
* fold-then-render pattern works from inside the handler. `undefined` covers
|
|
2982
|
+
* both an already-settled session and a `produce` throw (the promise
|
|
2983
|
+
* rejects); neither emits.
|
|
2984
|
+
*/
|
|
2985
|
+
function commitClose() {
|
|
2986
|
+
const snapshots = ctx.commit(rebuildRenderedAll);
|
|
2987
|
+
if (snapshots !== void 0) emitCommit(snapshots);
|
|
2988
|
+
return snapshots;
|
|
2989
|
+
}
|
|
2960
2990
|
function commitGesture(nextConcatPoints, preGestureOptionsOverrideByMember) {
|
|
2961
2991
|
if (ctx.settled) return;
|
|
2962
2992
|
const previousPointsByMember = clonePointsByMember();
|
|
@@ -3034,10 +3064,11 @@ function runGroupTransform(opts) {
|
|
|
3034
3064
|
renderAllPreviews();
|
|
3035
3065
|
renderHandlesLayer();
|
|
3036
3066
|
recomputeDirty();
|
|
3067
|
+
if (removedSnapshots.length > 0) emitCommit(removedSnapshots);
|
|
3037
3068
|
return removedSnapshots;
|
|
3038
3069
|
}
|
|
3039
3070
|
function close() {
|
|
3040
|
-
return
|
|
3071
|
+
return commitClose();
|
|
3041
3072
|
}
|
|
3042
3073
|
const session = {
|
|
3043
3074
|
get measures() {
|
|
@@ -3069,6 +3100,13 @@ function runGroupTransform(opts) {
|
|
|
3069
3100
|
return () => {
|
|
3070
3101
|
changeListeners.delete(listener);
|
|
3071
3102
|
};
|
|
3103
|
+
},
|
|
3104
|
+
onCommit(listener) {
|
|
3105
|
+
if (ctx.settled) return () => {};
|
|
3106
|
+
commitListeners.add(listener);
|
|
3107
|
+
return () => {
|
|
3108
|
+
commitListeners.delete(listener);
|
|
3109
|
+
};
|
|
3072
3110
|
}
|
|
3073
3111
|
};
|
|
3074
3112
|
let activeBoxDrag = null;
|
|
@@ -3173,11 +3211,11 @@ function runGroupTransform(opts) {
|
|
|
3173
3211
|
if (localPreview || localHandles) return;
|
|
3174
3212
|
const isMember = localGraphics ? members.some((m) => m.measure.id === localGraphics.id) : false;
|
|
3175
3213
|
if (localGraphics && !isMember) {
|
|
3176
|
-
|
|
3214
|
+
commitClose();
|
|
3177
3215
|
if (onClickAwayGraphicsPick) onClickAwayGraphicsPick(localGraphics);
|
|
3178
3216
|
return;
|
|
3179
3217
|
}
|
|
3180
|
-
|
|
3218
|
+
commitClose();
|
|
3181
3219
|
};
|
|
3182
3220
|
const core = runInteraction({
|
|
3183
3221
|
adapter,
|
|
@@ -3665,6 +3703,11 @@ var TacticalDraw = class {
|
|
|
3665
3703
|
* `measures.length` must be >= 1; a single-element array behaves like an
|
|
3666
3704
|
* `edit` with only `transform` mode. Throws `TypeError` synchronously (before
|
|
3667
3705
|
* any preemption) for an empty array or duplicate ids.
|
|
3706
|
+
*
|
|
3707
|
+
* For a selection that changes over time (shift-click toggle, marquee
|
|
3708
|
+
* replace), prefer [[syncTransformMembers]] over hand-rolling the
|
|
3709
|
+
* live-session-vs-fresh-`editMany` branch, and subscribe to
|
|
3710
|
+
* `TransformSession.onCommit` as the single fold path for exit snapshots.
|
|
3668
3711
|
*/
|
|
3669
3712
|
editMany(measures, options) {
|
|
3670
3713
|
this.assertAlive();
|
|
@@ -3713,6 +3756,42 @@ var TacticalDraw = class {
|
|
|
3713
3756
|
}, extraSettle);
|
|
3714
3757
|
}
|
|
3715
3758
|
/**
|
|
3759
|
+
* One-call selection-sync: mutate the live group transform's member set in
|
|
3760
|
+
* place, or start a fresh `editMany` when no transform is live (#191). This
|
|
3761
|
+
* owns the "live session → `setMembers`, else → `editMany`" branch that every
|
|
3762
|
+
* host otherwise hand-rolls against `activeTransformSession`.
|
|
3763
|
+
*
|
|
3764
|
+
* The facade knows session liveness synchronously — `activeTransformSessionRef`
|
|
3765
|
+
* is cleared inside the settle path, before control returns to host code — so
|
|
3766
|
+
* unlike a host's own `activeTransformSession ? setMembers : editMany` check,
|
|
3767
|
+
* this can never observe a stale ref pointing at an already-settled session.
|
|
3768
|
+
* The settled-race fall-through hosts had to hand-roll for that gap is owned
|
|
3769
|
+
* here instead: a `setMembers` call that (implausibly) still returns
|
|
3770
|
+
* `undefined` falls through to a fresh `editMany` rather than silently
|
|
3771
|
+
* dropping the selection.
|
|
3772
|
+
*
|
|
3773
|
+
* `measures: []` closes any live session (committing every member) and is a
|
|
3774
|
+
* no-op while idle. With a live session, `options` is ignored — session-scoped
|
|
3775
|
+
* state (`sizeAnchor`, box angle, close-on-click-away policy) is sticky per
|
|
3776
|
+
* `setMembers`'s contract; `options` applies only when this call starts a
|
|
3777
|
+
* fresh `editMany`. Exit snapshots are delivered via the session's
|
|
3778
|
+
* `onCommit` — subscribe once in `options.onSession`. The underlying
|
|
3779
|
+
* `editMany` promise is owned by the facade; its abort rejections are
|
|
3780
|
+
* swallowed here (non-abort errors surface as unhandled rejections). Throws
|
|
3781
|
+
* `TypeError` synchronously on duplicate ids, from either path. Starting a
|
|
3782
|
+
* fresh transform preempts any other in-flight draw/edit exactly like
|
|
3783
|
+
* `editMany`.
|
|
3784
|
+
*/
|
|
3785
|
+
syncTransformMembers(measures, options) {
|
|
3786
|
+
this.assertAlive();
|
|
3787
|
+
const session = this.activeTransformSessionRef;
|
|
3788
|
+
if (session) {
|
|
3789
|
+
if (session.setMembers(measures) !== void 0) return;
|
|
3790
|
+
}
|
|
3791
|
+
if (measures.length === 0) return;
|
|
3792
|
+
this.editMany([...measures], options).catch(ignoreAbort);
|
|
3793
|
+
}
|
|
3794
|
+
/**
|
|
3716
3795
|
* Subscribe to picks on committed control measures.
|
|
3717
3796
|
*
|
|
3718
3797
|
* The graphics layer is pre-bound, and `event.id` is the originating control
|
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.9",
|
|
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",
|