@orbat-mapper/tactical-draw 0.2.0-alpha.10 → 0.2.0-alpha.12

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 CHANGED
@@ -1,4 +1,4 @@
1
- import { ControlMeasure, ControlMeasureKind, ControlMeasureSnapshot, ControlMeasureStyle } from "@orbat-mapper/control-measures";
1
+ import { 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
@@ -54,6 +54,13 @@ interface StyleOptions {
54
54
  pointRadius?: number;
55
55
  text?: TextStyleOptions;
56
56
  }
57
+ /**
58
+ * Text (label) style for a layer. When `labelProperty` is set, adapters also
59
+ * read per-feature label properties: `rotation` (radians, rendered as
60
+ * `π − rotation`), `textSizePixels`/`textSizeZoom`/`textSizeResolution`
61
+ * (ADR-0020), and `textAnchor` (`"start"` = text begins at the label point,
62
+ * `"end"` = text ends there; absent = centered).
63
+ */
57
64
  interface TextStyleOptions {
58
65
  text?: string;
59
66
  labelProperty?: string;
@@ -490,6 +497,24 @@ interface InteractionStyle {
490
497
  */
491
498
  type EditMode = "reshape" | "transform" | "delete";
492
499
  //#endregion
500
+ //#region src/tactical-draw/session-events.d.ts
501
+ /**
502
+ * The reason a session (`TransformSession`, `EditSession`, or `DrawSession`)
503
+ * settled, delivered to that session's `onSettled` handler (#191 / #194 /
504
+ * #197). `"close"` covers every commit path of the owning session —
505
+ * `TransformSession.close()` / `EditSession.close()` / `DrawSession.commit()`,
506
+ * a click-away close, a gesture commit (dblclick, a second click/tap on the
507
+ * finishing vertex, a click on another graphics-layer measure), or
508
+ * `TransformSession.setMembers([])`. `"error"` is the commit-time snapshot
509
+ * build throwing (the outer promise rejects with that error, not a
510
+ * `TacticalDrawAbortError`). The remaining values replay
511
+ * `TacticalDrawAbortError.reason` verbatim: `"escape"`, `"signal"`,
512
+ * `"session"` (`session.abort()` / `event.abort()`), `"preempted"`,
513
+ * `"destroyed"`, and `"removed"` (edit / transform only — a measure dropped
514
+ * from `td.render()`'s measure set).
515
+ */
516
+ type SettleReason = "close" | "error" | TacticalDrawAbortReason;
517
+ //#endregion
493
518
  //#region src/tactical-draw/edit-session.d.ts
494
519
  /**
495
520
  * Surface delivered via `td.edit(measure, opts).onSession` and synchronously
@@ -497,7 +522,8 @@ type EditMode = "reshape" | "transform" | "delete";
497
522
  *
498
523
  * `measure` is the immutable edit-start input. `controlPoints` is live working
499
524
  * state. After the session settles (`close` / `abort`), every method is a
500
- * silent no-op.
525
+ * silent no-op (except [[onSettled]] — late registration still delivers,
526
+ * asynchronously).
501
527
  */
502
528
  interface EditSession {
503
529
  /** Immutable edit-start input. */
@@ -508,6 +534,13 @@ interface EditSession {
508
534
  readonly options: ControlMeasure["options"];
509
535
  /** Live working style. Copy on read. */
510
536
  readonly style: ControlMeasureStyle | undefined;
537
+ /**
538
+ * Live working text amplifiers (MIL-STD-2525E Appendix L Table L-II). Copy
539
+ * on read. `undefined` when the edit-start measure carried none and none
540
+ * have been set since. Keys are stored as given — canonicalisation to
541
+ * doctrinal field codes happens in the core render seam, not here.
542
+ */
543
+ readonly textAmplifiers: TextAmplifiers | undefined;
511
544
  /** `true` after the first working-copy mutation; back to `false` within tolerance. */
512
545
  readonly dirty: boolean;
513
546
  readonly modes: readonly EditMode[];
@@ -545,6 +578,13 @@ interface EditSession {
545
578
  setSizeAnchor(anchor: SizeAnchor): void;
546
579
  /** Shallow-merge per-measure style into the live working copy. */
547
580
  setStyle(partial: ControlMeasureStyle): void;
581
+ /**
582
+ * Shallow-merge into the live working text amplifiers. A value of
583
+ * `undefined` or an empty/whitespace-only string deletes that key; the
584
+ * working record collapses back to `undefined` once it holds no keys. A
585
+ * silent no-op after the session has settled (like [[setOptions]]).
586
+ */
587
+ setTextAmplifiers(partial: Record<string, string | undefined>): void;
548
588
  /**
549
589
  * Subscribe to working-copy changes. The listener receives an
550
590
  * [[EditChangeEvent]] for each completed gesture (drag end, insert/delete
@@ -557,6 +597,47 @@ interface EditSession {
557
597
  * have run.
558
598
  */
559
599
  onChange(listener: (event: EditChangeEvent) => void): () => void;
600
+ /**
601
+ * Subscribe to the committed snapshot — the single delivery channel for the
602
+ * `ControlMeasureSnapshot` this session ever commits (#197, mirroring
603
+ * `TransformSession.onCommit` #191). Fires exactly once, synchronously from
604
+ * within the [[close]] call that produced it (`session.close()`,
605
+ * `event.close()` from [[onChange]], a click-away close, or a click on
606
+ * another graphics-layer measure), with the same reference [[close]]
607
+ * returns and the outer `edit` promise resolves with. Never fires on abort.
608
+ *
609
+ * Listener exceptions are collected; after every listener has run, they are
610
+ * rethrown (one if single, `AggregateError` otherwise) — mirrors
611
+ * [[onChange]]. Returns an unsubscribe function; registering after the
612
+ * session has settled returns a no-op unsubscribe and the listener never
613
+ * fires.
614
+ */
615
+ onCommit(handler: (snapshot: ControlMeasureSnapshot) => void): () => void;
616
+ /**
617
+ * Subscribe to the session's settlement — the lifecycle complement of
618
+ * [[onCommit]]: `onCommit` delivers *what* was committed, `onSettled` says
619
+ * the session is *over* and why (#197, mirroring
620
+ * `TransformSession.onSettled` #194). Fires exactly once, when the session
621
+ * settles by any path, commit or abort, with a `SettleReason`.
622
+ *
623
+ * - On the commit path it fires **after** [[onCommit]]'s emission, so a
624
+ * listener that clears selection state observes a document that already
625
+ * contains the committed snapshot.
626
+ * - On every path it fires **after** the facade has already cleared
627
+ * `td.activeSession` — so fold-then-render works from inside the handler.
628
+ * - `reason` distinguishes commit from abort but is deliberately coarse —
629
+ * no payload; the snapshot was already delivered through [[onCommit]].
630
+ *
631
+ * Registering after the session has already settled is **not** a no-op
632
+ * (unlike [[onChange]] / [[onCommit]]): the handler is invoked
633
+ * asynchronously (on a microtask) exactly once with the settle reason;
634
+ * unsubscribing before that microtask runs cancels the delivery.
635
+ *
636
+ * Listener exceptions are collected; after every listener has run, they are
637
+ * rethrown (one if single, `AggregateError` otherwise) — mirrors
638
+ * [[onChange]] / [[onCommit]]. Returns an unsubscribe function.
639
+ */
640
+ onSettled(handler: (reason: SettleReason) => void): () => void;
560
641
  }
561
642
  /**
562
643
  * Delivered to `EditSession.onChange` listeners once per completed gesture.
@@ -586,17 +667,6 @@ interface EditChangeEvent {
586
667
  }
587
668
  //#endregion
588
669
  //#region src/tactical-draw/transform-session.d.ts
589
- /**
590
- * The reason a `TransformSession` settled, delivered to [[TransformSession.onSettled]].
591
- * `"close"` covers every commit path — `close()`, `setMembers([])`, a
592
- * click-away close, or `TransformChangeEvent.close()`. `"error"` is the
593
- * close-time snapshot build throwing (the `editMany` promise rejected with
594
- * that error, not a `TacticalDrawAbortError`). The remaining values mirror
595
- * `TacticalDrawAbortError.reason` exactly: `"escape"`, `"signal"`, `"session"`
596
- * (`session.abort()` / `event.abort()`), `"preempted"`, `"destroyed"`, and
597
- * `"removed"` (a member dropped from `td.render()`'s measure set).
598
- */
599
- type SettleReason = "close" | "error" | TacticalDrawAbortReason;
600
670
  /**
601
671
  * Surface delivered via `td.editMany(measures, opts).onSession` and
602
672
  * synchronously available from `td.activeTransformSession` while the group
@@ -793,7 +863,8 @@ interface TransformChangeEvent {
793
863
  * kinds. Also available from `td.activeSession` while the draw is live.
794
864
  *
795
865
  * After the draw has settled — either via `commit()` returning `true` or
796
- * `abort()` resolving — every method is a silent no-op.
866
+ * `abort()` resolving — every method is a silent no-op (except [[onSettled]]
867
+ * — late registration still delivers, asynchronously).
797
868
  */
798
869
  interface DrawSession {
799
870
  /** Live snapshot of the committed control points. Copy on read. */
@@ -815,6 +886,45 @@ interface DrawSession {
815
886
  * are collected and rethrown after all listeners run.
816
887
  */
817
888
  onChange(listener: (session: DrawSession) => void): () => void;
889
+ /**
890
+ * Subscribe to the committed snapshot — the single delivery channel for the
891
+ * `ControlMeasureSnapshot` this session ever commits (#197, mirroring
892
+ * `TransformSession.onCommit` #191). Fires exactly once, synchronously from
893
+ * within the commit that produced it (`session.commit()`, dblclick, or a
894
+ * second click/tap on the finishing vertex), with the same reference the
895
+ * outer `draw` promise resolves with — the committed snapshot, not just the
896
+ * measure. Never fires on abort.
897
+ *
898
+ * Listener exceptions are collected; after every listener has run, they are
899
+ * rethrown (one if single, `AggregateError` otherwise) — mirrors
900
+ * [[onChange]]. Returns an unsubscribe function; registering after the
901
+ * session has settled returns a no-op unsubscribe and the listener never
902
+ * fires.
903
+ */
904
+ onCommit(handler: (snapshot: ControlMeasureSnapshot) => void): () => void;
905
+ /**
906
+ * Subscribe to the session's settlement — the lifecycle complement of
907
+ * [[onCommit]]: `onCommit` delivers *what* was committed, `onSettled` says
908
+ * the session is *over* and why (#197, mirroring
909
+ * `TransformSession.onSettled` #194). Fires exactly once, when the session
910
+ * settles by any path, commit or abort, with a `SettleReason`.
911
+ *
912
+ * - On the commit path it fires **after** [[onCommit]]'s emission.
913
+ * - On every path it fires **after** the facade has already cleared
914
+ * `td.activeSession` — so fold-then-render works from inside the handler.
915
+ * - `reason` distinguishes commit from abort but is deliberately coarse —
916
+ * no payload; the snapshot was already delivered through [[onCommit]].
917
+ *
918
+ * Registering after the session has already settled is **not** a no-op
919
+ * (unlike [[onChange]] / [[onCommit]]): the handler is invoked
920
+ * asynchronously (on a microtask) exactly once with the settle reason;
921
+ * unsubscribing before that microtask runs cancels the delivery.
922
+ *
923
+ * Listener exceptions are collected; after every listener has run, they are
924
+ * rethrown (one if single, `AggregateError` otherwise) — mirrors
925
+ * [[onChange]] / [[onCommit]]. Returns an unsubscribe function.
926
+ */
927
+ onSettled(handler: (reason: SettleReason) => void): () => void;
818
928
  }
819
929
  //#endregion
820
930
  //#region src/tactical-draw/tactical-draw.d.ts
@@ -1144,6 +1254,12 @@ declare class TacticalDraw {
1144
1254
  * `opts.onSession` and commit on `session.commit()`, `dblclick`, or a second
1145
1255
  * click on the last added control point. All paths reject with
1146
1256
  * `TacticalDrawAbortError` on signal / Escape / destroy / `session.abort()`.
1257
+ *
1258
+ * For a variable-length draw, `DrawSession.onCommit` is the single delivery
1259
+ * channel for the committed snapshot and `DrawSession.onSettled` is the
1260
+ * lifecycle signal for when the session is over and why (#197) — together
1261
+ * they make the returned promise non-load-bearing for hosts that adopt them.
1262
+ * Fixed-length kinds have no session and settle only through the promise.
1147
1263
  */
1148
1264
  draw<K extends ControlMeasureKind>(draft: DrawMeasureDraft<K>, options?: DrawOptions): Promise<ControlMeasureSnapshot<K>>;
1149
1265
  /**
@@ -1155,6 +1271,11 @@ declare class TacticalDraw {
1155
1271
  * Default click-away (`closeOnClickAway: true`) closes the edit on a click
1156
1272
  * outside the preview / handle features. Hosts that own close policy pass
1157
1273
  * `false`.
1274
+ *
1275
+ * `EditSession.onCommit` is the single delivery channel for the committed
1276
+ * snapshot and `EditSession.onSettled` is the lifecycle signal for when the
1277
+ * session is over and why (#197) — together they make the returned promise
1278
+ * non-load-bearing for hosts that adopt them.
1158
1279
  */
1159
1280
  edit(measure: ControlMeasure, options?: EditOptions): Promise<ControlMeasureSnapshot>;
1160
1281
  /**
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { cloneControlMeasure, controlMeasureIdFromFeature, freezeOrientationOptions, getControlMeasureMetadata, renderControlMeasure, resolveStyleHints, roundToFixed } from "@orbat-mapper/control-measures";
1
+ import { canonicalTextAmplifierKey, cloneControlMeasure, controlMeasureIdFromFeature, freezeOrientationOptions, getControlMeasureMetadata, renderControlMeasure, resolveStyleHints, roundToFixed } from "@orbat-mapper/control-measures";
2
2
  //#region src/base-map-adapter.ts
3
3
  /**
4
4
  * Abstract base class shared by all `MapAdapter` implementations.
@@ -876,7 +876,7 @@ const PREVIEW_CURSOR$1 = "crosshair";
876
876
  * kinds need no host UI.
877
877
  */
878
878
  function runFixedLengthDraw(options) {
879
- const { adapter, kind, previewLayer, guideLayer, graphicsStyle, generateId, signal, measureOptions, measureStyle, measureProperties, guide: guideOpt, sizeAnchor, onSettled } = options;
879
+ const { adapter, kind, previewLayer, guideLayer, graphicsStyle, generateId, signal, measureOptions, measureStyle, measureProperties, measureTextAmplifiers, guide: guideOpt, sizeAnchor, onSettled } = options;
880
880
  const metadata = getControlMeasureMetadata(kind);
881
881
  const rule = metadata.rule;
882
882
  const min = metadata.minCoordinates;
@@ -916,7 +916,7 @@ function runFixedLengthDraw(options) {
916
916
  return;
917
917
  }
918
918
  const previewThreshold = rule?.minimumPreviewPoints ?? required;
919
- const features = buildPreviewFeatures$1(adapter, kind, rule && raw.length >= previewThreshold ? rule.derive(raw) : raw, measureOptions, measureStyle, renderOpts);
919
+ const features = buildPreviewFeatures$1(adapter, kind, rule && raw.length >= previewThreshold ? rule.derive(raw) : raw, measureOptions, measureStyle, measureTextAmplifiers, renderOpts);
920
920
  ctx.writeLayer(previewLayer, features);
921
921
  const guidePoints = rule?.guidePoints ? rule.guidePoints(raw) : raw;
922
922
  const guideFeatures = guideEnabled ? buildGuideFeatures(guidePoints, isAreaKind) : [];
@@ -937,7 +937,8 @@ function runFixedLengthDraw(options) {
937
937
  controlPoints: finalPoints,
938
938
  ...measureOptions !== void 0 ? { options: measureOptions } : {},
939
939
  ...measureStyle !== void 0 ? { style: measureStyle } : {},
940
- ...measureProperties !== void 0 ? { properties: measureProperties } : {}
940
+ ...measureProperties !== void 0 ? { properties: measureProperties } : {},
941
+ ...measureTextAmplifiers !== void 0 ? { textAmplifiers: measureTextAmplifiers } : {}
941
942
  }, adapter);
942
943
  const measure = sizeAnchor === "screen" ? captured : bakePixelSizeToGround(captured, adapter);
943
944
  return {
@@ -970,13 +971,14 @@ function runFixedLengthDraw(options) {
970
971
  * falls back to a plain Point/LineString outline when the kind's generator
971
972
  * cannot produce a shape yet (under-min count).
972
973
  */
973
- function buildPreviewFeatures$1(adapter, kind, points, measureOptions, measureStyle, renderOpts) {
974
+ function buildPreviewFeatures$1(adapter, kind, points, measureOptions, measureStyle, measureTextAmplifiers, renderOpts) {
974
975
  const measure = {
975
976
  id: PREVIEW_CM_ID$1,
976
977
  kind,
977
978
  controlPoints: points,
978
979
  ...measureOptions !== void 0 ? { options: measureOptions } : {},
979
- ...measureStyle !== void 0 ? { style: measureStyle } : {}
980
+ ...measureStyle !== void 0 ? { style: measureStyle } : {},
981
+ ...measureTextAmplifiers !== void 0 ? { textAmplifiers: measureTextAmplifiers } : {}
980
982
  };
981
983
  try {
982
984
  return renderMeasureForAdapter(adapter, measure, {
@@ -1007,6 +1009,117 @@ function outlinePreview$1(points) {
1007
1009
  return features;
1008
1010
  }
1009
1011
  //#endregion
1012
+ //#region src/tactical-draw/session-events.ts
1013
+ /**
1014
+ * Map a settle-time error to its `SettleReason`: a `TacticalDrawAbortError`
1015
+ * replays its own `reason` verbatim; anything else (a `produce` throw from
1016
+ * `ctx.commit`) is the commit-time snapshot build failing, reported as
1017
+ * `"error"`.
1018
+ */
1019
+ function settleReasonFromError(error) {
1020
+ return isTacticalDrawAbortError(error) ? error.reason : "error";
1021
+ }
1022
+ /**
1023
+ * Fan a `runInteraction` settlement out to a session's commit + settle
1024
+ * emitters — the shared body of the `onSettled` wrappers in the edit and draw
1025
+ * controllers (transform emits its close-path settle from `commitClose`
1026
+ * instead, so it does not use this). On the commit path, emit the snapshot
1027
+ * then the `"close"` settle in a try/finally so a throwing `onCommit` listener
1028
+ * cannot starve the settle emission; on the reject path, emit the settle with
1029
+ * the reason derived from the error. Callers run their own facade
1030
+ * slot-clearing (`onSettled()`) before this so a listener observes a cleared
1031
+ * `td.activeSession`.
1032
+ */
1033
+ function settleFromInteraction(settlement, commitEmitter, settleEmitter) {
1034
+ if (settlement.status === "committed") try {
1035
+ commitEmitter.emit(settlement.value);
1036
+ } finally {
1037
+ settleEmitter.emit("close");
1038
+ }
1039
+ else settleEmitter.emit(settleReasonFromError(settlement.error));
1040
+ }
1041
+ /**
1042
+ * Subscribe/emit machinery shared by `TransformSession.onSettled`,
1043
+ * `EditSession.onSettled`, and `DrawSession.onSettled` (#191 / #194 / #197):
1044
+ * a settle-exactly-once latch, synchronous emission to every listener with
1045
+ * exception collection (single rethrow, `AggregateError` otherwise when more
1046
+ * than one listener throws), and a late-registration path that delivers the
1047
+ * latched reason on a microtask exactly once when a handler subscribes after
1048
+ * the session has already settled.
1049
+ *
1050
+ * `listenerErrorsMessage` becomes the `AggregateError` message — pass a
1051
+ * session-specific string (e.g. `"EditSession.onSettled listener errors"`).
1052
+ */
1053
+ function createSettleEmitter(listenerErrorsMessage) {
1054
+ const listeners = /* @__PURE__ */ new Set();
1055
+ /** Set exactly once, by `emit` — also the "has settled" latch and the reason replayed to late registrations. */
1056
+ let settledReason = null;
1057
+ return {
1058
+ emit(reason) {
1059
+ if (settledReason !== null) return;
1060
+ settledReason = reason;
1061
+ const current = [...listeners];
1062
+ listeners.clear();
1063
+ const errors = [];
1064
+ for (const listener of current) try {
1065
+ listener(reason);
1066
+ } catch (err) {
1067
+ errors.push(err);
1068
+ }
1069
+ if (errors.length === 1) throw errors[0];
1070
+ if (errors.length > 1) throw new AggregateError(errors, listenerErrorsMessage);
1071
+ },
1072
+ register(handler) {
1073
+ if (settledReason !== null) {
1074
+ const reason = settledReason;
1075
+ let cancelled = false;
1076
+ queueMicrotask(() => {
1077
+ if (!cancelled) handler(reason);
1078
+ });
1079
+ return () => {
1080
+ cancelled = true;
1081
+ };
1082
+ }
1083
+ listeners.add(handler);
1084
+ return () => {
1085
+ listeners.delete(handler);
1086
+ };
1087
+ }
1088
+ };
1089
+ }
1090
+ /**
1091
+ * Subscribe/emit machinery shared by `TransformSession.onCommit`,
1092
+ * `EditSession.onCommit`, and `DrawSession.onCommit`: a listener set with
1093
+ * synchronous, exception-collecting emission (single rethrow, `AggregateError`
1094
+ * otherwise). Unlike `createSettleEmitter`, `emit` may be called more than
1095
+ * once — a transform's `setMembers` can commit several removal batches before
1096
+ * the close batch — and there is no latch or late-registration path:
1097
+ * registration-time `ctx.settled` guards stay in the controllers, mirroring
1098
+ * `onChange`.
1099
+ */
1100
+ function createCommitEmitter(listenerErrorsMessage) {
1101
+ const listeners = /* @__PURE__ */ new Set();
1102
+ return {
1103
+ emit(value) {
1104
+ const current = [...listeners];
1105
+ const errors = [];
1106
+ for (const listener of current) try {
1107
+ listener(value);
1108
+ } catch (err) {
1109
+ errors.push(err);
1110
+ }
1111
+ if (errors.length === 1) throw errors[0];
1112
+ if (errors.length > 1) throw new AggregateError(errors, listenerErrorsMessage);
1113
+ },
1114
+ register(listener) {
1115
+ listeners.add(listener);
1116
+ return () => {
1117
+ listeners.delete(listener);
1118
+ };
1119
+ }
1120
+ };
1121
+ }
1122
+ //#endregion
1010
1123
  //#region src/tactical-draw/draw-controller-variable.ts
1011
1124
  const PREVIEW_CM_ID = "__td_preview__";
1012
1125
  const PREVIEW_CURSOR = "crosshair";
@@ -1018,7 +1131,7 @@ const PREVIEW_CURSOR = "crosshair";
1018
1131
  * added point again" as additional gesture-level commit triggers.
1019
1132
  */
1020
1133
  function runVariableLengthDraw(options) {
1021
- const { adapter, kind, previewLayer, guideLayer, graphicsStyle, generateId, signal, onSession, seed, measureOptions, measureStyle, measureProperties, guide: guideOpt, sizeAnchor, onSettled } = options;
1134
+ const { adapter, kind, previewLayer, guideLayer, graphicsStyle, generateId, signal, onSession, seed, measureOptions, measureStyle, measureProperties, measureTextAmplifiers, guide: guideOpt, sizeAnchor, onSettled } = options;
1022
1135
  const metadata = getControlMeasureMetadata(kind);
1023
1136
  if (typeof metadata.minCoordinates !== "number") throw new Error(`runVariableLengthDraw: kind "${kind}" has no minCoordinates metadata`);
1024
1137
  const min = metadata.minCoordinates;
@@ -1053,7 +1166,8 @@ function runVariableLengthDraw(options) {
1053
1166
  kind,
1054
1167
  controlPoints: initialPoints.map((p) => [...p]),
1055
1168
  ...measureOptions !== void 0 ? { options: measureOptions } : {},
1056
- ...measureStyle !== void 0 ? { style: measureStyle } : {}
1169
+ ...measureStyle !== void 0 ? { style: measureStyle } : {},
1170
+ ...measureTextAmplifiers !== void 0 ? { textAmplifiers: measureTextAmplifiers } : {}
1057
1171
  }, {
1058
1172
  ...renderOpts,
1059
1173
  validationMode: "throw"
@@ -1064,6 +1178,8 @@ function runVariableLengthDraw(options) {
1064
1178
  let ctx;
1065
1179
  const savedCursor = "";
1066
1180
  const changeListeners = /* @__PURE__ */ new Set();
1181
+ const commitEmitter = createCommitEmitter("DrawSession.onCommit listener errors");
1182
+ const settleEmitter = createSettleEmitter("DrawSession.onSettled listener errors");
1067
1183
  function canCommitNow() {
1068
1184
  const n = committed.length;
1069
1185
  if (typeof max === "number" && n > max) return false;
@@ -1100,6 +1216,13 @@ function runVariableLengthDraw(options) {
1100
1216
  return () => {
1101
1217
  changeListeners.delete(listener);
1102
1218
  };
1219
+ },
1220
+ onCommit(listener) {
1221
+ if (ctx.settled) return () => {};
1222
+ return commitEmitter.register(listener);
1223
+ },
1224
+ onSettled(handler) {
1225
+ return settleEmitter.register(handler);
1103
1226
  }
1104
1227
  };
1105
1228
  function fireChange() {
@@ -1167,7 +1290,7 @@ function runVariableLengthDraw(options) {
1167
1290
  points.splice(idx, 0, rubberBand);
1168
1291
  } else points = [...committed];
1169
1292
  if (rule) points = rule.derive(points);
1170
- const previewFeatures = points.length === 0 ? [] : buildPreviewFeatures(adapter, kind, points, measureOptions, measureStyle, renderOpts);
1293
+ const previewFeatures = points.length === 0 ? [] : buildPreviewFeatures(adapter, kind, points, measureOptions, measureStyle, measureTextAmplifiers, renderOpts);
1171
1294
  ctx.writeLayer(previewLayer, previewFeatures);
1172
1295
  const guideFeatures = points.length === 0 || !guideEnabled ? [] : buildGuideFeatures(points, isAreaKind, trailingFixed);
1173
1296
  ctx.writeLayer(guideLayer, guideFeatures);
@@ -1186,7 +1309,8 @@ function runVariableLengthDraw(options) {
1186
1309
  controlPoints: committed.map((p) => [...p]),
1187
1310
  ...measureOptions !== void 0 ? { options: measureOptions } : {},
1188
1311
  ...measureStyle !== void 0 ? { style: measureStyle } : {},
1189
- ...measureProperties !== void 0 ? { properties: measureProperties } : {}
1312
+ ...measureProperties !== void 0 ? { properties: measureProperties } : {},
1313
+ ...measureTextAmplifiers !== void 0 ? { textAmplifiers: measureTextAmplifiers } : {}
1190
1314
  }, adapter);
1191
1315
  const measure = sizeAnchor === "screen" ? built : bakePixelSizeToGround(built, adapter);
1192
1316
  return {
@@ -1199,7 +1323,10 @@ function runVariableLengthDraw(options) {
1199
1323
  signal,
1200
1324
  layers: [previewLayer, guideLayer],
1201
1325
  onViewChange: rescaleOnViewChange,
1202
- onSettled,
1326
+ onSettled: (settlement) => {
1327
+ if (onSettled) onSettled();
1328
+ settleFromInteraction(settlement, commitEmitter, settleEmitter);
1329
+ },
1203
1330
  attach(c) {
1204
1331
  ctx = c;
1205
1332
  c.onTeardown(() => {
@@ -1234,13 +1361,14 @@ function isValidPosition(value) {
1234
1361
  }
1235
1362
  return true;
1236
1363
  }
1237
- function buildPreviewFeatures(adapter, kind, points, measureOptions, measureStyle, renderOpts) {
1364
+ function buildPreviewFeatures(adapter, kind, points, measureOptions, measureStyle, measureTextAmplifiers, renderOpts) {
1238
1365
  const measure = {
1239
1366
  id: PREVIEW_CM_ID,
1240
1367
  kind,
1241
1368
  controlPoints: points,
1242
1369
  ...measureOptions !== void 0 ? { options: measureOptions } : {},
1243
- ...measureStyle !== void 0 ? { style: measureStyle } : {}
1370
+ ...measureStyle !== void 0 ? { style: measureStyle } : {},
1371
+ ...measureTextAmplifiers !== void 0 ? { textAmplifiers: measureTextAmplifiers } : {}
1244
1372
  };
1245
1373
  try {
1246
1374
  return renderMeasureForAdapter(adapter, measure, renderOpts).features;
@@ -1873,6 +2001,7 @@ function runEdit(opts) {
1873
2001
  const editStartPoints = measure.controlPoints.map((p) => [...p]);
1874
2002
  const workingPoints = editStartPoints.map((p) => [...p]);
1875
2003
  let workingOptions = { ...measure.options ?? {} };
2004
+ let workingTextAmplifiers = measure.textAmplifiers && Object.keys(measure.textAmplifiers).length > 0 ? { ...measure.textAmplifiers } : void 0;
1876
2005
  let modes = requestedModes;
1877
2006
  let dirty = false;
1878
2007
  let nonGeometryDirty = false;
@@ -1897,6 +2026,8 @@ function runEdit(opts) {
1897
2026
  */
1898
2027
  let pendingModes = null;
1899
2028
  const changeListeners = /* @__PURE__ */ new Set();
2029
+ const commitEmitter = createCommitEmitter("EditSession.onCommit listener errors");
2030
+ const settleEmitter = createSettleEmitter("EditSession.onSettled listener errors");
1900
2031
  /**
1901
2032
  * The transform box's rotation, in radians, session-scoped (ADR-0017 decision
1902
2033
  * #2): starts at `0` (axis-aligned) and accumulates every committed rotate
@@ -1959,6 +2090,8 @@ function runEdit(opts) {
1959
2090
  options: workingOptions,
1960
2091
  controlPoints: workingPoints.map((p) => [...p])
1961
2092
  };
2093
+ if (workingTextAmplifiers) working.textAmplifiers = workingTextAmplifiers;
2094
+ else delete working.textAmplifiers;
1962
2095
  let features;
1963
2096
  try {
1964
2097
  features = renderMeasureForAdapter(adapter, working, renderOpts).features;
@@ -2010,6 +2143,8 @@ function runEdit(opts) {
2010
2143
  options,
2011
2144
  controlPoints: workingPoints.map((p) => [...p])
2012
2145
  };
2146
+ if (workingTextAmplifiers) working.textAmplifiers = workingTextAmplifiers;
2147
+ else delete working.textAmplifiers;
2013
2148
  const committed = sizeAnchor === "screen" ? working : bakePixelSizeToGround(working, adapter);
2014
2149
  return {
2015
2150
  measure: cloneControlMeasure(committed),
@@ -2061,7 +2196,7 @@ function runEdit(opts) {
2061
2196
  * - Reject restores the working state to the pre-gesture snapshot and
2062
2197
  * recomputes `dirty`. Close/abort settle the session as normal.
2063
2198
  */
2064
- function emitChange(previousRendered, previousPoints, previousOptions, previousStyle, previousNonGeometryDirty, previousSizeAnchor, currentRendered) {
2199
+ function emitChange(previousRendered, previousPoints, previousOptions, previousStyle, previousNonGeometryDirty, previousSizeAnchor, currentRendered, previousTextAmplifiers) {
2065
2200
  if (ctx.settled) return;
2066
2201
  let rejectRequested = false;
2067
2202
  let closeRequested = false;
@@ -2093,6 +2228,7 @@ function runEdit(opts) {
2093
2228
  replaceWorkingOptions(previousOptions);
2094
2229
  currentStyle = previousStyle;
2095
2230
  sizeAnchor = previousSizeAnchor;
2231
+ workingTextAmplifiers = previousTextAmplifiers;
2096
2232
  nonGeometryDirty = previousNonGeometryDirty;
2097
2233
  renderPreview();
2098
2234
  renderHandlesLayer();
@@ -2117,6 +2253,7 @@ function runEdit(opts) {
2117
2253
  const previousOptions = { ...preGestureOptions ?? workingOptions ?? {} };
2118
2254
  const previousStyle = currentStyle;
2119
2255
  const previousNonGeometryDirty = nonGeometryDirty;
2256
+ const previousTextAmplifiers = workingTextAmplifiers;
2120
2257
  const previousRendered = rebuildRenderedWith(previousOptions);
2121
2258
  replaceWorkingPoints(nextPoints);
2122
2259
  let currentRendered;
@@ -2134,7 +2271,7 @@ function runEdit(opts) {
2134
2271
  recomputeDirty();
2135
2272
  dragInFlight = false;
2136
2273
  drainPendingModes();
2137
- emitChange(previousRendered, previousPoints, previousOptions, previousStyle, previousNonGeometryDirty, sizeAnchor, currentRendered);
2274
+ emitChange(previousRendered, previousPoints, previousOptions, previousStyle, previousNonGeometryDirty, sizeAnchor, currentRendered, previousTextAmplifiers);
2138
2275
  }
2139
2276
  function setOptions(partial) {
2140
2277
  if (ctx.settled) return;
@@ -2142,6 +2279,7 @@ function runEdit(opts) {
2142
2279
  const previousOptions = { ...workingOptions ?? {} };
2143
2280
  const previousStyle = currentStyle;
2144
2281
  const previousNonGeometryDirty = nonGeometryDirty;
2282
+ const previousTextAmplifiers = workingTextAmplifiers;
2145
2283
  const previousRendered = rebuildRendered();
2146
2284
  replaceWorkingOptions({
2147
2285
  ...workingOptions ?? {},
@@ -2157,7 +2295,7 @@ function runEdit(opts) {
2157
2295
  renderPreview();
2158
2296
  nonGeometryDirty = true;
2159
2297
  dirty = true;
2160
- emitChange(previousRendered, previousPoints, previousOptions, previousStyle, previousNonGeometryDirty, sizeAnchor, currentRendered);
2298
+ emitChange(previousRendered, previousPoints, previousOptions, previousStyle, previousNonGeometryDirty, sizeAnchor, currentRendered, previousTextAmplifiers);
2161
2299
  }
2162
2300
  function setStyle(partial) {
2163
2301
  if (ctx.settled) return;
@@ -2165,6 +2303,7 @@ function runEdit(opts) {
2165
2303
  const previousOptions = { ...workingOptions ?? {} };
2166
2304
  const previousStyle = currentStyle;
2167
2305
  const previousNonGeometryDirty = nonGeometryDirty;
2306
+ const previousTextAmplifiers = workingTextAmplifiers;
2168
2307
  const previousRendered = rebuildRendered();
2169
2308
  currentStyle = {
2170
2309
  ...currentStyle ?? {},
@@ -2180,7 +2319,51 @@ function runEdit(opts) {
2180
2319
  renderPreview();
2181
2320
  nonGeometryDirty = true;
2182
2321
  dirty = true;
2183
- emitChange(previousRendered, previousPoints, previousOptions, previousStyle, previousNonGeometryDirty, sizeAnchor, currentRendered);
2322
+ emitChange(previousRendered, previousPoints, previousOptions, previousStyle, previousNonGeometryDirty, sizeAnchor, currentRendered, previousTextAmplifiers);
2323
+ }
2324
+ /**
2325
+ * Shallow-merge into the live working text amplifiers. Mirrors
2326
+ * [[setOptions]] exactly (dirty tracking, re-render, change emission) —
2327
+ * see that function for the shared shape. A merge value of `undefined` or
2328
+ * an empty/whitespace-only string deletes the key; the record collapses to
2329
+ * `undefined` once empty so `renderPreview`/`rebuildRenderedWith` omit the
2330
+ * key rather than persist `{}`.
2331
+ *
2332
+ * Each patch key resolves to its canonical field key and evicts every
2333
+ * working entry for the same field + suffix — including friendly-alias
2334
+ * twins (`uniqueDesignation` for `T`) — so clearing or replacing via a
2335
+ * canonical key cannot be undone by an alias-backed value surviving in the
2336
+ * working record. Non-empty values are stored under the canonical key;
2337
+ * unknown keys keep literal behavior.
2338
+ */
2339
+ function setTextAmplifiers(partial) {
2340
+ if (ctx.settled) return;
2341
+ const previousPoints = workingPoints.map((p) => [...p]);
2342
+ const previousOptions = { ...workingOptions ?? {} };
2343
+ const previousStyle = currentStyle;
2344
+ const previousNonGeometryDirty = nonGeometryDirty;
2345
+ const previousTextAmplifiers = workingTextAmplifiers;
2346
+ const previousRendered = rebuildRendered();
2347
+ const merged = { ...workingTextAmplifiers ?? {} };
2348
+ for (const [key, value] of Object.entries(partial)) {
2349
+ const canonicalKey = canonicalTextAmplifierKey(key);
2350
+ if (canonicalKey) {
2351
+ for (const existing of Object.keys(merged)) if (canonicalTextAmplifierKey(existing) === canonicalKey) delete merged[existing];
2352
+ } else delete merged[key];
2353
+ if (value !== void 0 && value.trim() !== "") merged[canonicalKey ?? key] = value;
2354
+ }
2355
+ workingTextAmplifiers = Object.keys(merged).length > 0 ? merged : void 0;
2356
+ let currentRendered;
2357
+ try {
2358
+ currentRendered = rebuildRendered();
2359
+ } catch (err) {
2360
+ workingTextAmplifiers = previousTextAmplifiers;
2361
+ throw err;
2362
+ }
2363
+ renderPreview();
2364
+ nonGeometryDirty = true;
2365
+ dirty = true;
2366
+ emitChange(previousRendered, previousPoints, previousOptions, previousStyle, previousNonGeometryDirty, sizeAnchor, currentRendered, previousTextAmplifiers);
2184
2367
  }
2185
2368
  /**
2186
2369
  * Flip the size anchor for the measure under edit (ADR-0020). Re-renders the
@@ -2196,12 +2379,13 @@ function runEdit(opts) {
2196
2379
  const previousStyle = currentStyle;
2197
2380
  const previousNonGeometryDirty = nonGeometryDirty;
2198
2381
  const previousSizeAnchor = sizeAnchor;
2382
+ const previousTextAmplifiers = workingTextAmplifiers;
2199
2383
  const previousRendered = rebuildRendered();
2200
2384
  sizeAnchor = next;
2201
2385
  const currentRendered = rebuildRendered();
2202
2386
  nonGeometryDirty = true;
2203
2387
  dirty = true;
2204
- emitChange(previousRendered, previousPoints, previousOptions, previousStyle, previousNonGeometryDirty, previousSizeAnchor, currentRendered);
2388
+ emitChange(previousRendered, previousPoints, previousOptions, previousStyle, previousNonGeometryDirty, previousSizeAnchor, currentRendered, previousTextAmplifiers);
2205
2389
  }
2206
2390
  /** Apply a translate gesture by shifting every working point by `delta`. */
2207
2391
  function commitTranslate(delta) {
@@ -2259,6 +2443,9 @@ function runEdit(opts) {
2259
2443
  get style() {
2260
2444
  return currentStyle ? { ...currentStyle } : void 0;
2261
2445
  },
2446
+ get textAmplifiers() {
2447
+ return workingTextAmplifiers ? { ...workingTextAmplifiers } : void 0;
2448
+ },
2262
2449
  get dirty() {
2263
2450
  return dirty;
2264
2451
  },
@@ -2290,6 +2477,9 @@ function runEdit(opts) {
2290
2477
  setStyle(partial) {
2291
2478
  setStyle(partial);
2292
2479
  },
2480
+ setTextAmplifiers(partial) {
2481
+ setTextAmplifiers(partial);
2482
+ },
2293
2483
  setSizeAnchor(next) {
2294
2484
  setSizeAnchor(next);
2295
2485
  },
@@ -2300,6 +2490,13 @@ function runEdit(opts) {
2300
2490
  changeListeners.delete(listener);
2301
2491
  };
2302
2492
  },
2493
+ onCommit(listener) {
2494
+ if (ctx.settled) return () => {};
2495
+ return commitEmitter.register(listener);
2496
+ },
2497
+ onSettled(handler) {
2498
+ return settleEmitter.register(handler);
2499
+ },
2303
2500
  [COMMIT_GESTURE_FOR_TEST]: (nextPoints) => {
2304
2501
  commitGesture(nextPoints);
2305
2502
  },
@@ -2553,7 +2750,10 @@ function runEdit(opts) {
2553
2750
  handlesLayer
2554
2751
  ],
2555
2752
  onViewChange: rescaleOnViewChange,
2556
- onSettled,
2753
+ onSettled: (settlement) => {
2754
+ if (onSettled) onSettled();
2755
+ settleFromInteraction(settlement, commitEmitter, settleEmitter);
2756
+ },
2557
2757
  attach(c) {
2558
2758
  ctx = c;
2559
2759
  driver = adapter.createEditPointerDriver();
@@ -2746,10 +2946,8 @@ function runGroupTransform(opts) {
2746
2946
  /** Same click-away suppression role as `edit-controller.ts`'s flag of the same name. */
2747
2947
  let suppressClickAway = false;
2748
2948
  const changeListeners = /* @__PURE__ */ new Set();
2749
- const commitListeners = /* @__PURE__ */ new Set();
2750
- const settleListeners = /* @__PURE__ */ new Set();
2751
- /** Set exactly once, by `emitSettled` — also the "has settled been emitted" latch and the reason replayed to late registrations. */
2752
- let settledReason = null;
2949
+ const commitEmitter = createCommitEmitter("TransformSession.onCommit listener errors");
2950
+ const settleEmitter = createSettleEmitter("TransformSession.onSettled listener errors");
2753
2951
  /** Session-scoped box rotation (ADR-0017 decision #2), shared by the whole group. */
2754
2952
  let boxAngle = 0;
2755
2953
  /** The group box fitted over every member's rendered geometry, or `null` when nothing projects this frame. */
@@ -2971,51 +3169,6 @@ function runGroupTransform(opts) {
2971
3169
  if (errors.length > 1) throw new AggregateError(errors, "TransformSession.onChange listener errors");
2972
3170
  }
2973
3171
  /**
2974
- * Deliver a committed exit-snapshot batch to every `onCommit` listener — the
2975
- * single delivery channel for both the `setMembers`-removed batch and the
2976
- * close-time batch (#191). Same exception-collection contract as `emitChange`.
2977
- */
2978
- function emitCommit(snapshots) {
2979
- const errors = [];
2980
- for (const listener of [...commitListeners]) try {
2981
- listener(snapshots);
2982
- } catch (err) {
2983
- errors.push(err);
2984
- }
2985
- if (errors.length === 1) throw errors[0];
2986
- if (errors.length > 1) throw new AggregateError(errors, "TransformSession.onCommit listener errors");
2987
- }
2988
- /**
2989
- * Map a settle-time error to its `SettleReason`: a `TacticalDrawAbortError`
2990
- * replays its own `reason` verbatim; anything else (a `produce` throw from
2991
- * `ctx.commit`) is the close-time snapshot build failing, reported as
2992
- * `"error"`.
2993
- */
2994
- function settleReasonFromError(error) {
2995
- return isTacticalDrawAbortError(error) ? error.reason : "error";
2996
- }
2997
- /**
2998
- * Deliver the session's settlement to every `onSettled` listener — fires
2999
- * exactly once, whichever path settles first wins the reason. Mirrors
3000
- * `emitCommit`'s exception-collection contract (one throw if single,
3001
- * `AggregateError` otherwise), so a throwing listener cannot suppress a
3002
- * later one nor leave `settledReason` unset.
3003
- */
3004
- function emitSettled(reason) {
3005
- if (settledReason !== null) return;
3006
- settledReason = reason;
3007
- const listeners = [...settleListeners];
3008
- settleListeners.clear();
3009
- const errors = [];
3010
- for (const listener of listeners) try {
3011
- listener(reason);
3012
- } catch (err) {
3013
- errors.push(err);
3014
- }
3015
- if (errors.length === 1) throw errors[0];
3016
- if (errors.length > 1) throw new AggregateError(errors, "TransformSession.onSettled listener errors");
3017
- }
3018
- /**
3019
3172
  * Commit via `ctx.commit` and emit the close batch to `onCommit` listeners,
3020
3173
  * then emit the settle to `onSettled` listeners (#194) — in a try/finally so
3021
3174
  * a throwing `onCommit` listener cannot starve the settle emission. The
@@ -3031,9 +3184,9 @@ function runGroupTransform(opts) {
3031
3184
  function commitClose() {
3032
3185
  const snapshots = ctx.commit(rebuildRenderedAll);
3033
3186
  if (snapshots !== void 0) try {
3034
- emitCommit(snapshots);
3187
+ commitEmitter.emit(snapshots);
3035
3188
  } finally {
3036
- emitSettled("close");
3189
+ settleEmitter.emit("close");
3037
3190
  }
3038
3191
  return snapshots;
3039
3192
  }
@@ -3114,7 +3267,7 @@ function runGroupTransform(opts) {
3114
3267
  renderAllPreviews();
3115
3268
  renderHandlesLayer();
3116
3269
  recomputeDirty();
3117
- if (removedSnapshots.length > 0) emitCommit(removedSnapshots);
3270
+ if (removedSnapshots.length > 0) commitEmitter.emit(removedSnapshots);
3118
3271
  return removedSnapshots;
3119
3272
  }
3120
3273
  function close() {
@@ -3153,26 +3306,10 @@ function runGroupTransform(opts) {
3153
3306
  },
3154
3307
  onCommit(listener) {
3155
3308
  if (ctx.settled) return () => {};
3156
- commitListeners.add(listener);
3157
- return () => {
3158
- commitListeners.delete(listener);
3159
- };
3309
+ return commitEmitter.register(listener);
3160
3310
  },
3161
3311
  onSettled(handler) {
3162
- if (settledReason !== null) {
3163
- const reason = settledReason;
3164
- let cancelled = false;
3165
- queueMicrotask(() => {
3166
- if (!cancelled) handler(reason);
3167
- });
3168
- return () => {
3169
- cancelled = true;
3170
- };
3171
- }
3172
- settleListeners.add(handler);
3173
- return () => {
3174
- settleListeners.delete(handler);
3175
- };
3312
+ return settleEmitter.register(handler);
3176
3313
  }
3177
3314
  };
3178
3315
  let activeBoxDrag = null;
@@ -3290,7 +3427,7 @@ function runGroupTransform(opts) {
3290
3427
  onViewChange: rescaleOnViewChange,
3291
3428
  onSettled: (settlement) => {
3292
3429
  if (onSettled) onSettled();
3293
- if (settlement.status === "rejected") emitSettled(settleReasonFromError(settlement.error));
3430
+ if (settlement.status === "rejected") settleEmitter.emit(settleReasonFromError(settlement.error));
3294
3431
  },
3295
3432
  attach(c) {
3296
3433
  ctx = c;
@@ -3655,6 +3792,12 @@ var TacticalDraw = class {
3655
3792
  * `opts.onSession` and commit on `session.commit()`, `dblclick`, or a second
3656
3793
  * click on the last added control point. All paths reject with
3657
3794
  * `TacticalDrawAbortError` on signal / Escape / destroy / `session.abort()`.
3795
+ *
3796
+ * For a variable-length draw, `DrawSession.onCommit` is the single delivery
3797
+ * channel for the committed snapshot and `DrawSession.onSettled` is the
3798
+ * lifecycle signal for when the session is over and why (#197) — together
3799
+ * they make the returned promise non-load-bearing for hosts that adopt them.
3800
+ * Fixed-length kinds have no session and settle only through the promise.
3658
3801
  */
3659
3802
  draw(draft, options) {
3660
3803
  this.assertAlive();
@@ -3683,6 +3826,7 @@ var TacticalDraw = class {
3683
3826
  measureOptions: draft.options,
3684
3827
  measureStyle: draft.style,
3685
3828
  measureProperties: draft.properties,
3829
+ measureTextAmplifiers: draft.textAmplifiers,
3686
3830
  guide: options?.guide,
3687
3831
  sizeAnchor: options?.sizeAnchor,
3688
3832
  onSettled
@@ -3699,6 +3843,7 @@ var TacticalDraw = class {
3699
3843
  measureOptions: draft.options,
3700
3844
  measureStyle: draft.style,
3701
3845
  measureProperties: draft.properties,
3846
+ measureTextAmplifiers: draft.textAmplifiers,
3702
3847
  guide: options?.guide,
3703
3848
  sizeAnchor: options?.sizeAnchor,
3704
3849
  onSettled
@@ -3713,6 +3858,11 @@ var TacticalDraw = class {
3713
3858
  * Default click-away (`closeOnClickAway: true`) closes the edit on a click
3714
3859
  * outside the preview / handle features. Hosts that own close policy pass
3715
3860
  * `false`.
3861
+ *
3862
+ * `EditSession.onCommit` is the single delivery channel for the committed
3863
+ * snapshot and `EditSession.onSettled` is the lifecycle signal for when the
3864
+ * session is over and why (#197) — together they make the returned promise
3865
+ * non-load-bearing for hosts that adopt them.
3716
3866
  */
3717
3867
  edit(measure, options) {
3718
3868
  this.assertAlive();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orbat-mapper/tactical-draw",
3
- "version": "0.2.0-alpha.10",
3
+ "version": "0.2.0-alpha.12",
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.8"
41
+ "@orbat-mapper/control-measures": "^0.2.0-alpha.9"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@arethetypeswrong/core": "^0.18.3",