@fieldnotes/core 0.61.0 → 0.63.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -450,7 +450,7 @@ declare class HistoryStack {
450
450
  }
451
451
 
452
452
  type HtmlExportRenderer = (element: HtmlElement) => CanvasImageSource | null | Promise<CanvasImageSource | null>;
453
- type HtmlExportErrorReason = 'unsupported' | 'timeout' | 'render' | 'encode';
453
+ type HtmlExportErrorReason = 'unsupported' | 'timeout' | 'render' | 'encode' | 'missing-painter' | 'painter-threw' | 'degenerate-size';
454
454
  interface HtmlExportError {
455
455
  elementId: string;
456
456
  htmlType?: string;
@@ -466,6 +466,51 @@ interface HtmlExportOptions {
466
466
  onHtmlError?: (error: HtmlExportError) => void;
467
467
  }
468
468
 
469
+ interface HtmlPaintContext {
470
+ /** Translated to the element origin, rotated about its centre, clipped, save()d.
471
+ * Layer opacity is NOT applied here — each surface owns that boundary. */
472
+ ctx: CanvasRenderingContext2D;
473
+ element: Readonly<HtmlElement>;
474
+ size: Readonly<Size>;
475
+ /** CSS pixels per world unit for THIS surface (screen | minimap | export). */
476
+ zoom: number;
477
+ }
478
+ type HtmlPainter = (paint: HtmlPaintContext) => void;
479
+ type HtmlRouting = 'dom' | 'canvas' | 'missing';
480
+ declare class HtmlPainterMissingError extends Error {
481
+ readonly elementId: string;
482
+ readonly htmlType: string | undefined;
483
+ constructor(elementId: string, htmlType: string | undefined);
484
+ }
485
+ declare class HtmlPainterRegistry {
486
+ private readonly painters;
487
+ private readonly declared;
488
+ private readonly listeners;
489
+ private _version;
490
+ private canvasTypesCache;
491
+ get version(): number;
492
+ /**
493
+ * Memoized: this is the hottest read in the feature — `isDomElement` consults it several
494
+ * times per element per frame, and every html-element store update re-reconciles every
495
+ * html element. The cache is dropped in `bump()`, which is called on exactly the
496
+ * transitions that can change the membership of this set (first `expect` of a type, last
497
+ * release of a type, any `register`, and any unregister that empties a type's stack).
498
+ *
499
+ * The returned `Set` is the LIVE memoized instance, not a copy: callers must not mutate
500
+ * it, and a caller that holds it across a `bump()` holds a stale snapshot. Nothing in the
501
+ * codebase does either — `withHtmlDefaults` passes it straight through to the exporters
502
+ * unless the caller supplied `expectedCanvasTypes`, in which case it builds a fresh union
503
+ * — and holding it across a change was already a stale snapshot before memoization.
504
+ */
505
+ get canvasTypes(): ReadonlySet<string>;
506
+ expect(htmlTypes: Iterable<string>): () => void;
507
+ register(htmlType: string, painter: HtmlPainter): () => void;
508
+ getActivePainter(htmlType: string): HtmlPainter | undefined;
509
+ onChange(listener: () => void): () => void;
510
+ private bump;
511
+ }
512
+ declare function resolveHtmlRouting(el: Readonly<HtmlElement>, registry: HtmlPainterRegistry | null, expectedCanvasTypes?: ReadonlySet<string>): HtmlRouting;
513
+
469
514
  interface ExportImageOptions extends ExportResourceOptions, HtmlExportOptions {
470
515
  scale?: number;
471
516
  padding?: number;
@@ -497,6 +542,20 @@ interface ExportImageOptions extends ExportResourceOptions, HtmlExportOptions {
497
542
  * caps (never above the requested scale).
498
543
  */
499
544
  scaleMode?: 'exact' | 'fit';
545
+ /** Registry of canvas-backed html painters, keyed by `htmlType`. When absent (or when
546
+ * an element's `htmlType` isn't claimed), html elements fall back to the legacy
547
+ * DOM-raster path (`renderHtml`). */
548
+ htmlPainters?: HtmlPainterRegistry;
549
+ /** `htmlType`s that must route to canvas even before a painter for them is
550
+ * registered — lets a host declare intent up front (mirrors `HtmlPainterRegistry.expect`). */
551
+ expectedCanvasTypes?: ReadonlySet<string>;
552
+ /**
553
+ * When true, a canvas-routed html element with no active painter throws
554
+ * `HtmlPainterMissingError` instead of reporting `onHtmlError` and continuing.
555
+ * DOM-routed html elements are never affected — a missing `renderHtml` stays
556
+ * a non-fatal `'unsupported'` diagnostic regardless of this flag.
557
+ */
558
+ strictMissingCanvasHtml?: boolean;
500
559
  }
501
560
  type ExportAssetErrorReason = 'load' | 'timeout' | 'encode';
502
561
  interface ExportAssetError {
@@ -522,9 +581,32 @@ interface ExportSvgOptions extends ExportResourceOptions, HtmlExportOptions {
522
581
  background?: string;
523
582
  filter?: (el: CanvasElement) => boolean;
524
583
  rasterScale?: number;
584
+ /** Registry of canvas-backed html painters, keyed by `htmlType`. When absent (or when
585
+ * an element's `htmlType` isn't claimed), html elements fall back to the legacy
586
+ * DOM-raster path (`renderHtml`). */
587
+ htmlPainters?: HtmlPainterRegistry;
588
+ /** `htmlType`s that must route to canvas even before a painter for them is
589
+ * registered — lets a host declare intent up front (mirrors `HtmlPainterRegistry.expect`). */
590
+ expectedCanvasTypes?: ReadonlySet<string>;
591
+ /**
592
+ * When true, a canvas-routed html element with no active painter throws
593
+ * `HtmlPainterMissingError` instead of reporting `onHtmlError` and continuing.
594
+ * DOM-routed html elements are never affected — a missing `renderHtml` stays
595
+ * a non-fatal `'unsupported'` diagnostic regardless of this flag.
596
+ */
597
+ strictMissingCanvasHtml?: boolean;
525
598
  }
526
599
  declare function exportSvg(store: ElementStore, options?: ExportSvgOptions, layerManager?: LayerManager): Promise<string>;
527
600
 
601
+ type HtmlRenderTarget = 'screen' | 'minimap' | 'export';
602
+ interface HtmlPaintDiagnostic {
603
+ kind: 'missing-painter' | 'painter-threw' | 'degenerate-size';
604
+ elementId: string;
605
+ htmlType?: string;
606
+ target: HtmlRenderTarget;
607
+ error?: unknown;
608
+ }
609
+
528
610
  interface RenderStatsSnapshot {
529
611
  fps: number;
530
612
  avgFrameMs: number;
@@ -575,6 +657,30 @@ interface GridInfo {
575
657
  cellRadius: number;
576
658
  }
577
659
 
660
+ interface ElementActivationEvent {
661
+ element: Readonly<CanvasElement>;
662
+ /** Pointerup location converted through the pointerup camera snapshot. */
663
+ world: Point;
664
+ /**
665
+ * Raw `PointerEvent.pointerType`. Usually `'mouse' | 'touch' | 'pen'`, but the
666
+ * spec permits vendor-defined and empty strings, so this is NOT narrowed and
667
+ * NOT normalised.
668
+ */
669
+ pointerType: string;
670
+ gesture: 'single' | 'double';
671
+ }
672
+ interface ActivationOptions {
673
+ gesture: 'single' | 'double';
674
+ /** Additional host filter, applied on top of the core canvas-routing gate. */
675
+ isActivatable?: (el: Readonly<CanvasElement>) => boolean;
676
+ /** Host-owned camera animators the viewport cannot see. */
677
+ isCameraBusy?: () => boolean;
678
+ /** Maximum pointer travel between down and up. Default `8` (PingInput parity). */
679
+ slopPx?: number;
680
+ /** Maximum gap between the halves of a double tap. Default `300`. */
681
+ doubleDelayMs?: number;
682
+ }
683
+
578
684
  interface ViewportOptions {
579
685
  camera?: CameraOptions;
580
686
  background?: BackgroundOptions;
@@ -605,6 +711,12 @@ interface ViewportOptions {
605
711
  /** Show an overview minimap (bottom-right) with tap/drag-to-navigate. Default `false`. */
606
712
  minimap?: boolean;
607
713
  }
714
+ interface HitTestOptions {
715
+ /** Skip elements on locked layers. Default `true` (selection semantics). */
716
+ respectLayerLock?: boolean;
717
+ /** Applied inside the candidate walk; the topmost passing element wins. */
718
+ match?: (element: CanvasElement) => boolean;
719
+ }
608
720
  declare class Viewport {
609
721
  private readonly container;
610
722
  readonly camera: Camera;
@@ -643,6 +755,14 @@ declare class Viewport {
643
755
  private contextMenu;
644
756
  private minimap;
645
757
  private readonly htmlRenderers;
758
+ private readonly htmlPainters;
759
+ private readonly htmlDiagnosticListeners;
760
+ private readonly htmlDiagnostics;
761
+ private readonly resolveRouting;
762
+ private readonly unsubHtmlPainters;
763
+ private activation;
764
+ private activationGeneration;
765
+ private readonly activationListeners;
646
766
  private readonly resizeListeners;
647
767
  private readonly selectionListeners;
648
768
  private detachSelectionSource;
@@ -658,6 +778,15 @@ declare class Viewport {
658
778
  fitToContent(padding?: number): void;
659
779
  /** World-space rectangle currently visible through the canvas. */
660
780
  getVisibleRect(): Bounds;
781
+ /**
782
+ * Topmost element at a world point, using the same geometry selection uses
783
+ * (rotation-aware, grid excluded, real stroke/line hit paths).
784
+ *
785
+ * `match` participates in the topmost-first walk rather than filtering the
786
+ * result, so a non-matching element on top does not swallow the hit.
787
+ * Invisible layers are never returned, in any mode.
788
+ */
789
+ getElementAt(world: Point, options?: HitTestOptions): CanvasElement | null;
661
790
  /**
662
791
  * Size in CSS pixels of the canvas that `getVisibleRect()` measures.
663
792
  * Exposed because `canvasEl` is private: consumers can only reach the
@@ -690,6 +819,15 @@ declare class Viewport {
690
819
  registerOverlay(draw: OverlayRenderer): () => void;
691
820
  exportState(): CanvasState;
692
821
  exportJSON(): string;
822
+ /**
823
+ * Injects this viewport's own html painter registry into export options so a host
824
+ * that registered painters via `registerHtmlPainter`/`expectCanvasHtmlTypes` gets
825
+ * markers in exports without passing anything. An explicitly passed `htmlPainters`
826
+ * REPLACES the viewport's registry rather than merging with it. `expectedCanvasTypes`
827
+ * is always UNIONED with the resolved registry's own declarations — a caller's set
828
+ * can only add expectations, never shrink the registry's own.
829
+ */
830
+ private withHtmlDefaults;
693
831
  exportImage(options?: ExportImageOptions): Promise<Blob | null>;
694
832
  exportSVG(options?: ExportSvgOptions): Promise<string>;
695
833
  loadState(state: CanvasState): void;
@@ -741,6 +879,74 @@ declare class Viewport {
741
879
  removeLayer(id: string): void;
742
880
  registerHtmlRenderer(htmlType: string, factory: (el: HtmlElement) => HTMLElement): void;
743
881
  updateHtmlElement(id: string, newContent: HTMLElement): void;
882
+ /**
883
+ * Declares htmlTypes that route to canvas painters even before a painter for
884
+ * them registers, so the element renderer never treats them as DOM-backed
885
+ * (avoiding a DOM-mount flash while a host is still loading its painter).
886
+ * Returns an idempotent release; each `expect` call is independently reference
887
+ * counted by the registry.
888
+ */
889
+ expectCanvasHtmlTypes(htmlTypes: Iterable<string>): () => void;
890
+ /**
891
+ * Direct access to the viewport's live html-painter registry — the same
892
+ * instance the viewport itself uses to route canvas-backed html elements.
893
+ * Beyond `register`/`expectCanvasHtmlTypes` (already exposed above), this
894
+ * hands out `getActivePainter`, `canvasTypes`, `onChange`, and `version`,
895
+ * so a surface such as the minimap that needs to read routing state or
896
+ * react to registry changes can do so without the viewport re-deriving or
897
+ * proxying each capability individually.
898
+ */
899
+ getHtmlPainters(): HtmlPainterRegistry;
900
+ /**
901
+ * Registers the canvas painter for `htmlType`. Later registrations for the
902
+ * same type shadow earlier ones (LIFO); unregistering restores the previous
903
+ * entry. Existing elements of this type reconcile synchronously — DOM nodes
904
+ * detach and the render loop repaints on the next frame.
905
+ */
906
+ registerHtmlPainter(htmlType: string, painter: HtmlPainter): () => void;
907
+ /**
908
+ * Subscribes to diagnostics emitted while painting canvas-routed html
909
+ * elements (missing painter, painter threw, degenerate size). Deduped per
910
+ * element/target/kind against the current registry and element versions, so
911
+ * a fail -> repair -> fail-again sequence reports twice rather than being
912
+ * suppressed forever. Returns an idempotent unsubscribe.
913
+ */
914
+ onHtmlPaintDiagnostic(listener: (d: HtmlPaintDiagnostic) => void): () => void;
915
+ /**
916
+ * Enables (or replaces, or with `null` disables) pointer activation of
917
+ * canvas-painted elements — the bridge for elements that are drawn rather than
918
+ * mounted and so cannot receive DOM events. **Default off**, so every existing
919
+ * consumer behaves identically.
920
+ *
921
+ * The controller is a passive observer: listeners are `{ passive: true }` and
922
+ * it never calls `preventDefault`, `stopPropagation`, or takes pointer capture.
923
+ * Changing or disabling activation resets all active and pending gestures.
924
+ * Throws `RangeError` for a non-finite/negative `slopPx` or a non-positive
925
+ * `doubleDelayMs`, leaving any existing activation untouched.
926
+ *
927
+ * The returned disposer clears **only its own generation**, so a stale
928
+ * Strict-Mode cleanup cannot tear down a newer registration.
929
+ */
930
+ setActivation(options: ActivationOptions | null): () => void;
931
+ /**
932
+ * Subscribes to element activations. Persistent and independent of
933
+ * `setActivation`: subscribing before activation is enabled, or across a
934
+ * replacement, keeps working. Emission iterates a snapshot with per-listener
935
+ * try/catch. Returns an idempotent unsubscribe.
936
+ */
937
+ onElementActivate(listener: (e: ElementActivationEvent) => void): () => void;
938
+ private emitActivation;
939
+ /**
940
+ * Fires whenever the html painter registry's active-painter set changes
941
+ * (declare, register, or their release). Reconciliation is synchronous —
942
+ * routing flips (and any DOM detach/remount) happen before this returns —
943
+ * while the actual repaint of newly canvas-routed elements is deferred to
944
+ * the next render frame via markAllLayersDirty + requestRender. Does NOT
945
+ * touch a minimap: Viewport does not own a MinimapController, and the
946
+ * built-in wrapper / React <Minimap /> each subscribe to the registry
947
+ * directly.
948
+ */
949
+ private onHtmlRegistryChanged;
744
950
  addGrid(input: {
745
951
  gridType?: 'square' | 'hex';
746
952
  hexOrientation?: 'pointy' | 'flat';
@@ -799,6 +1005,187 @@ declare class Viewport {
799
1005
  private observeResize;
800
1006
  }
801
1007
 
1008
+ /**
1009
+ * A viewport-size-independent camera view: the world rectangle to frame.
1010
+ * Restored by contain-fit, so the same view frames the same world content on
1011
+ * any screen size or aspect — a DM's saved zone looks right on a phone and a
1012
+ * TV. Center+zoom would NOT have this property.
1013
+ */
1014
+ interface CameraView {
1015
+ x: number;
1016
+ y: number;
1017
+ w: number;
1018
+ h: number;
1019
+ }
1020
+ /** Captures the currently visible world rect. */
1021
+ declare function captureCameraView(viewport: {
1022
+ getVisibleRect(): {
1023
+ x: number;
1024
+ y: number;
1025
+ w: number;
1026
+ h: number;
1027
+ };
1028
+ }): CameraView;
1029
+ /**
1030
+ * Unclamped contain-fit zoom: the largest zoom at which the whole rect fits.
1031
+ * Contain, never crop — a view whose aspect differs from the canvas shows
1032
+ * extra world content on the short axis.
1033
+ */
1034
+ declare function fitZoomForView(view: CameraView, canvasW: number, canvasH: number): number;
1035
+ /** Camera origin that centers `view` on the canvas at an already-decided zoom. */
1036
+ declare function cameraOriginForView(view: CameraView, zoom: number, canvasW: number, canvasH: number): Point;
1037
+ /**
1038
+ * Writes `view` to `camera`. No-ops on a zero canvas dimension (mount and
1039
+ * visibility races are legitimate); throws on an invalid view or on negative
1040
+ * or non-finite dimensions.
1041
+ */
1042
+ declare function applyCameraView(camera: Camera, view: CameraView, canvasW: number, canvasH: number): void;
1043
+
1044
+ /** A scheduler and its matching canceller. Inseparable by construction. */
1045
+ interface FrameScheduler {
1046
+ requestFrame: (cb: () => void) => number;
1047
+ cancelFrame: (id: number) => void;
1048
+ }
1049
+ interface CameraAnimatorOptions {
1050
+ /** REQUIRED. `element` is used for input listeners only, never measurement. */
1051
+ getCanvasSize: () => {
1052
+ w: number;
1053
+ h: number;
1054
+ };
1055
+ durationMs?: number;
1056
+ easing?: (t: number) => number;
1057
+ interactive?: boolean;
1058
+ frames?: FrameScheduler;
1059
+ now?: () => number;
1060
+ }
1061
+ type CameraAnimationEndReason = 'complete' | 'cancelled' | 'superseded';
1062
+ /**
1063
+ * Animates a camera to a `CameraView`. Standalone controller in the
1064
+ * `PingInput`/`MinimapController` shape: the host owns construction and
1065
+ * disposal, and every timing dependency is injectable for deterministic tests.
1066
+ */
1067
+ declare class CameraAnimator {
1068
+ private readonly camera;
1069
+ private readonly getCanvasSize;
1070
+ private readonly frames;
1071
+ private readonly now;
1072
+ private readonly durationMs;
1073
+ private readonly easing;
1074
+ private rafId;
1075
+ private from;
1076
+ private to;
1077
+ private startedAt;
1078
+ private endListeners;
1079
+ /**
1080
+ * Monotonic operation counter. `animateTo`/`jumpTo` claim a generation
1081
+ * before emitting 'superseded'; if an onEnd listener starts a newer
1082
+ * operation during that emit, the outer call sees a bumped counter and
1083
+ * bails instead of overwriting the nested animation's state. Without this,
1084
+ * the nested animation would run to completion having never reported an end
1085
+ * reason, breaking the exactly-one guarantee the spec makes.
1086
+ */
1087
+ private generation;
1088
+ private lastWrite;
1089
+ private disposed;
1090
+ private detachListeners;
1091
+ constructor(element: HTMLElement, camera: Camera, options: CameraAnimatorOptions);
1092
+ get animating(): boolean;
1093
+ onEnd(listener: (reason: CameraAnimationEndReason) => void): () => void;
1094
+ animateTo(view: CameraView): void;
1095
+ jumpTo(view: CameraView): void;
1096
+ cancel(): void;
1097
+ /**
1098
+ * Terminal. Order is load-bearing: the flag is set BEFORE any listener runs,
1099
+ * because an onEnd listener can call animateTo during the disposal callback.
1100
+ * With the flag set last, that call would start a real animation which the
1101
+ * listener clear then silently discards — a second animation with no end
1102
+ * reason, breaking the exactly-one guarantee.
1103
+ */
1104
+ dispose(): void;
1105
+ /**
1106
+ * Steps 1-3 of the public-call contract. Returns null when the caller must
1107
+ * stop, having already handled termination.
1108
+ *
1109
+ * The disposed check precedes validation deliberately: ordering it after
1110
+ * would make `disposed.animateTo(invalidView)` both required to throw and
1111
+ * required to stay silent. Disposal wins — a terminal animator is inert for
1112
+ * every input, and post-disposal calls are exactly the racy teardown paths
1113
+ * where a throw is least useful.
1114
+ */
1115
+ private validateAndMeasure;
1116
+ private step;
1117
+ private recordWrite;
1118
+ private foreignWrite;
1119
+ /** Terminates an in-flight animation with `reason`. No-op when idle. */
1120
+ private end;
1121
+ private clearFrame;
1122
+ private emit;
1123
+ }
1124
+
1125
+ /** World-space rect of a tracked element, plus the host key it matched under. */
1126
+ interface ElementRect {
1127
+ id: string;
1128
+ /** Opaque host key echoed back from `match`. Core never interprets it. */
1129
+ key: string;
1130
+ x: number;
1131
+ y: number;
1132
+ w: number;
1133
+ h: number;
1134
+ /** Radians, clockwise, about the rect centre. 0 when the element has none. */
1135
+ rotation: number;
1136
+ }
1137
+ /** Returns an opaque key to track the element, or `null` to skip it. */
1138
+ type ElementRectMatch = (element: CanvasElement) => string | null;
1139
+ type ElementRectMatchError = (error: unknown, element: CanvasElement) => void;
1140
+ /**
1141
+ * The tracker's per-frame computation, exported so consumers that need a
1142
+ * snapshot without a live tracker (e.g. a React `getSnapshot` before
1143
+ * subscription) cannot drift from these rules.
1144
+ */
1145
+ declare function computeElementRects(store: ElementStore, match: ElementRectMatch, onError?: ElementRectMatchError): ElementRect[];
1146
+ /** Field-for-field comparison; `key` participates so identity changes emit. */
1147
+ declare function elementRectsEqual(a: readonly ElementRect[], b: readonly ElementRect[]): boolean;
1148
+ /** Narrow structural host: the tracker needs the element store and nothing else. */
1149
+ interface RectTrackerHost {
1150
+ store: ElementStore;
1151
+ }
1152
+ interface ElementRectTrackerOptions {
1153
+ match: ElementRectMatch;
1154
+ /** Inseparable request/cancel pair. Defaults to global rAF. */
1155
+ frames?: FrameScheduler;
1156
+ onError?: ElementRectMatchError;
1157
+ }
1158
+ /**
1159
+ * Tracks the world rects of a host-matched subset of elements.
1160
+ *
1161
+ * Deliberately store-only: it never subscribes to the camera, so pan and zoom
1162
+ * emit nothing and hosts that position content under a single camera transform
1163
+ * (the SDK's own domLayer technique) do no per-frame work.
1164
+ */
1165
+ declare class ElementRectTracker {
1166
+ private readonly store;
1167
+ private readonly frames;
1168
+ private readonly onError?;
1169
+ private readonly listeners;
1170
+ private readonly unsubscribe;
1171
+ private match;
1172
+ private rects;
1173
+ private frameId;
1174
+ private disposed;
1175
+ constructor(host: RectTrackerHost, options: ElementRectTrackerOptions);
1176
+ onChange(listener: (rects: readonly ElementRect[]) => void): () => void;
1177
+ getRects(): readonly ElementRect[];
1178
+ /**
1179
+ * Replaces the matcher and forces a rescan — including when handed the same
1180
+ * reference, because callers legitimately pass one stable wrapper whose
1181
+ * behavior changes (see the React hook).
1182
+ */
1183
+ setMatch(match: ElementRectMatch): void;
1184
+ dispose(): void;
1185
+ private schedule;
1186
+ private flush;
1187
+ }
1188
+
802
1189
  interface LaserToolOptions {
803
1190
  name?: string;
804
1191
  color?: string;
@@ -1368,6 +1755,7 @@ declare class MinimapController {
1368
1755
  private readonly requestFrame;
1369
1756
  private readonly cancelFrame;
1370
1757
  private readonly renderer;
1758
+ private readonly htmlPainters;
1371
1759
  private scene;
1372
1760
  private frameId;
1373
1761
  private debounceTimer;
@@ -1377,6 +1765,19 @@ declare class MinimapController {
1377
1765
  constructor(viewport: Viewport, canvas: HTMLCanvasElement, options?: MinimapControllerOptions);
1378
1766
  setSize(width: number, height: number): void;
1379
1767
  requestDraw(): void;
1768
+ /**
1769
+ * Invalidates the cached scene bitmap in response to html-painter registry
1770
+ * changes (a painter registering/unregistering, or an `expect` declaration
1771
+ * changing) and schedules the same debounced rebuild `markSceneDirty` uses
1772
+ * for every other invalidation source. Unlike those other sources — which
1773
+ * deliberately keep compositing the OLD bitmap until the rebuild lands, so
1774
+ * camera motion and content edits never stall on a render — painter
1775
+ * availability has no "old bitmap is still valid" reading: an element that
1776
+ * was falling back to a neutral fillRect (no painter yet) or a stale
1777
+ * painter's output is not a safe thing to keep showing, so the cached
1778
+ * bitmap is dropped immediately instead of composited a further time.
1779
+ */
1780
+ invalidateScene(): void;
1380
1781
  dispose(): void;
1381
1782
  private clearDebounce;
1382
1783
  private dpr;
@@ -1396,123 +1797,6 @@ declare class MinimapController {
1396
1797
  private onPointerEnd;
1397
1798
  }
1398
1799
 
1399
- /**
1400
- * A viewport-size-independent camera view: the world rectangle to frame.
1401
- * Restored by contain-fit, so the same view frames the same world content on
1402
- * any screen size or aspect — a DM's saved zone looks right on a phone and a
1403
- * TV. Center+zoom would NOT have this property.
1404
- */
1405
- interface CameraView {
1406
- x: number;
1407
- y: number;
1408
- w: number;
1409
- h: number;
1410
- }
1411
- /** Captures the currently visible world rect. */
1412
- declare function captureCameraView(viewport: {
1413
- getVisibleRect(): {
1414
- x: number;
1415
- y: number;
1416
- w: number;
1417
- h: number;
1418
- };
1419
- }): CameraView;
1420
- /**
1421
- * Unclamped contain-fit zoom: the largest zoom at which the whole rect fits.
1422
- * Contain, never crop — a view whose aspect differs from the canvas shows
1423
- * extra world content on the short axis.
1424
- */
1425
- declare function fitZoomForView(view: CameraView, canvasW: number, canvasH: number): number;
1426
- /** Camera origin that centers `view` on the canvas at an already-decided zoom. */
1427
- declare function cameraOriginForView(view: CameraView, zoom: number, canvasW: number, canvasH: number): Point;
1428
- /**
1429
- * Writes `view` to `camera`. No-ops on a zero canvas dimension (mount and
1430
- * visibility races are legitimate); throws on an invalid view or on negative
1431
- * or non-finite dimensions.
1432
- */
1433
- declare function applyCameraView(camera: Camera, view: CameraView, canvasW: number, canvasH: number): void;
1434
-
1435
- /** A scheduler and its matching canceller. Inseparable by construction. */
1436
- interface FrameScheduler {
1437
- requestFrame: (cb: () => void) => number;
1438
- cancelFrame: (id: number) => void;
1439
- }
1440
- interface CameraAnimatorOptions {
1441
- /** REQUIRED. `element` is used for input listeners only, never measurement. */
1442
- getCanvasSize: () => {
1443
- w: number;
1444
- h: number;
1445
- };
1446
- durationMs?: number;
1447
- easing?: (t: number) => number;
1448
- interactive?: boolean;
1449
- frames?: FrameScheduler;
1450
- now?: () => number;
1451
- }
1452
- type CameraAnimationEndReason = 'complete' | 'cancelled' | 'superseded';
1453
- /**
1454
- * Animates a camera to a `CameraView`. Standalone controller in the
1455
- * `PingInput`/`MinimapController` shape: the host owns construction and
1456
- * disposal, and every timing dependency is injectable for deterministic tests.
1457
- */
1458
- declare class CameraAnimator {
1459
- private readonly camera;
1460
- private readonly getCanvasSize;
1461
- private readonly frames;
1462
- private readonly now;
1463
- private readonly durationMs;
1464
- private readonly easing;
1465
- private rafId;
1466
- private from;
1467
- private to;
1468
- private startedAt;
1469
- private endListeners;
1470
- /**
1471
- * Monotonic operation counter. `animateTo`/`jumpTo` claim a generation
1472
- * before emitting 'superseded'; if an onEnd listener starts a newer
1473
- * operation during that emit, the outer call sees a bumped counter and
1474
- * bails instead of overwriting the nested animation's state. Without this,
1475
- * the nested animation would run to completion having never reported an end
1476
- * reason, breaking the exactly-one guarantee the spec makes.
1477
- */
1478
- private generation;
1479
- private lastWrite;
1480
- private disposed;
1481
- private detachListeners;
1482
- constructor(element: HTMLElement, camera: Camera, options: CameraAnimatorOptions);
1483
- get animating(): boolean;
1484
- onEnd(listener: (reason: CameraAnimationEndReason) => void): () => void;
1485
- animateTo(view: CameraView): void;
1486
- jumpTo(view: CameraView): void;
1487
- cancel(): void;
1488
- /**
1489
- * Terminal. Order is load-bearing: the flag is set BEFORE any listener runs,
1490
- * because an onEnd listener can call animateTo during the disposal callback.
1491
- * With the flag set last, that call would start a real animation which the
1492
- * listener clear then silently discards — a second animation with no end
1493
- * reason, breaking the exactly-one guarantee.
1494
- */
1495
- dispose(): void;
1496
- /**
1497
- * Steps 1-3 of the public-call contract. Returns null when the caller must
1498
- * stop, having already handled termination.
1499
- *
1500
- * The disposed check precedes validation deliberately: ordering it after
1501
- * would make `disposed.animateTo(invalidView)` both required to throw and
1502
- * required to stay silent. Disposal wins — a terminal animator is inert for
1503
- * every input, and post-disposal calls are exactly the racy teardown paths
1504
- * where a throw is least useful.
1505
- */
1506
- private validateAndMeasure;
1507
- private step;
1508
- private recordWrite;
1509
- private foreignWrite;
1510
- /** Terminates an in-flight animation with `reason`. No-op when idle. */
1511
- private end;
1512
- private clearFrame;
1513
- private emit;
1514
- }
1515
-
1516
1800
  /**
1517
1801
  * The wire shape of a focus-request presence payload. Focus is ephemeral by
1518
1802
  * contract: presence frames only — never elements, undo history, persisted
@@ -1649,6 +1933,7 @@ interface HtmlInput extends BaseDefaults {
1649
1933
  interactive?: boolean;
1650
1934
  htmlType?: string;
1651
1935
  data?: Record<string, unknown>;
1936
+ rotation?: number;
1652
1937
  }
1653
1938
  interface TextInput extends BaseDefaults {
1654
1939
  position: Point;
@@ -1996,6 +2281,6 @@ declare class TemplateTool implements Tool {
1996
2281
  private notifyOptionsChange;
1997
2282
  }
1998
2283
 
1999
- declare const VERSION = "0.61.0";
2284
+ declare const VERSION = "0.63.0";
2000
2285
 
2001
- export { type ActiveFormats, type AlignEdge, type ArrowElement, type ArrowStrokeStyle, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, type BackgroundOptions, type BackgroundPattern, type Binding, type Bounds, Camera, type CameraAnimationEndReason, CameraAnimator, type CameraAnimatorOptions, type CameraChangeInfo, type CameraOptions, type CameraView, type CanvasElement, type CanvasState, type Command, DEFAULT_NOTE_FONT_SIZE, type DistributeAxis, type ElementChangeMeta, ElementStore, type ElementStyle, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, type ExportAssetError, type ExportAssetErrorReason, type ExportImageOptions, type ExportResourceOptions, type ExportSvgOptions, FOCUS_PRESENCE_KIND, type FocusAudience, type FocusPresence, type FocusRole, type FontSizePreset, type FrameScheduler, type GridElement, type GridInfo, HandTool, type HexOrientation, HistoryStack, type HistoryStackOptions, type HtmlElement, type HtmlExportError, type HtmlExportErrorReason, type HtmlExportOptions, type HtmlExportRenderer, type ImageElement, ImageTool, type ImageToolOptions, IndexedDBAdapter, type IndexedDBAdapterOptions, LASER_TRAIL_PRESENCE_KIND, LaserTool, type LaserToolOptions, type LaserTrailEmission, type LaserTrailPresence, type Layer, LayerManager, LocalStorageAdapter, MEASURE_PRESENCE_KIND, type MeasureEmission, type MeasurePresence, MeasureTool, type MeasureToolOptions, type Measurement, MemoryAdapter, MinimapController, type MinimapControllerOptions, type NoteElement, NoteTool, type NoteToolOptions, type OverlayRenderer, PING_PRESENCE_KIND, PencilTool, type PencilToolOptions, type PingEmission, PingInput, type PingInputHost, type PingInputOptions, type PingPresence, PingTool, type PingToolOptions, type Point, type PointerState, RemoteFocusReceiver, type RemoteFocusReceiverHost, type RemoteFocusReceiverOptions, RemoteLaserOverlay, type RemoteLaserOverlayHost, type RemoteLaserOverlayOptions, RemoteMeasureOverlay, type RemoteMeasureOverlayHost, type RemoteMeasureOverlayOptions, RemotePingOverlay, type RemotePingOverlayHost, type RemotePingOverlayOptions, type RenderStatsSnapshot, type RotateDirection, SelectTool, type SelectionStyleDetails, type ShapeElement, type ShapeKind, ShapeTool, type ShapeToolOptions, type ShortcutBindings, type ShortcutOptions, type ShortcutsApi, type Size, type StorageAdapter, type StrokeElement, type StrokePoint, type TemplateElement, type TemplateRenderStyle, type TemplateShape, TemplateTool, type TemplateToolOptions, type TextElement, TextTool, type TextToolOptions, type Tool, type ToolContext, ToolManager, type ToolName, VERSION, Viewport, type ViewportOptions, applyCameraView, boundsIntersect, cameraOriginForView, captureCameraView, createArrow, createGrid, createHtmlElement, createImage, createNote, createShape, createStroke, createTemplate, createText, drawHexPath, exportImage, exportSvg, fitZoomForView, getActiveFormats, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, getElementBounds, getElementStyle, getElementsBoundingBox, getHexCellsInCone, getHexCellsInLine, getHexCellsInRadius, getHexCellsInRectangle, getHexCellsInSquare, getHexDistance, isFocusPresence, isLaserTrailPresence, isMeasurePresence, isNearBezier, isPingPresence, setFontSize, smartSnap, snapPoint, snapToHexCenter, styleToPatch, toFocusPresence, toLaserTrailPresence, toMeasurePresence, toPingPresence, toggleBold, toggleItalic, toggleStrikethrough, toggleUnderline };
2286
+ export { type ActivationOptions, type ActiveFormats, type AlignEdge, type ArrowElement, type ArrowStrokeStyle, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, type BackgroundOptions, type BackgroundPattern, type Binding, type Bounds, Camera, type CameraAnimationEndReason, CameraAnimator, type CameraAnimatorOptions, type CameraChangeInfo, type CameraOptions, type CameraView, type CanvasElement, type CanvasState, type Command, DEFAULT_NOTE_FONT_SIZE, type DistributeAxis, type ElementActivationEvent, type ElementChangeMeta, type ElementRect, type ElementRectMatch, type ElementRectMatchError, ElementRectTracker, type ElementRectTrackerOptions, ElementStore, type ElementStyle, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, type ExportAssetError, type ExportAssetErrorReason, type ExportImageOptions, type ExportResourceOptions, type ExportSvgOptions, FOCUS_PRESENCE_KIND, type FocusAudience, type FocusPresence, type FocusRole, type FontSizePreset, type FrameScheduler, type GridElement, type GridInfo, HandTool, type HexOrientation, HistoryStack, type HistoryStackOptions, type HitTestOptions, type HtmlElement, type HtmlExportError, type HtmlExportErrorReason, type HtmlExportOptions, type HtmlExportRenderer, type HtmlPaintContext, type HtmlPaintDiagnostic, type HtmlPainter, HtmlPainterMissingError, HtmlPainterRegistry, type HtmlRenderTarget, type HtmlRouting, type ImageElement, ImageTool, type ImageToolOptions, IndexedDBAdapter, type IndexedDBAdapterOptions, LASER_TRAIL_PRESENCE_KIND, LaserTool, type LaserToolOptions, type LaserTrailEmission, type LaserTrailPresence, type Layer, LayerManager, LocalStorageAdapter, MEASURE_PRESENCE_KIND, type MeasureEmission, type MeasurePresence, MeasureTool, type MeasureToolOptions, type Measurement, MemoryAdapter, MinimapController, type MinimapControllerOptions, type NoteElement, NoteTool, type NoteToolOptions, type OverlayRenderer, PING_PRESENCE_KIND, PencilTool, type PencilToolOptions, type PingEmission, PingInput, type PingInputHost, type PingInputOptions, type PingPresence, PingTool, type PingToolOptions, type Point, type PointerState, type RectTrackerHost, RemoteFocusReceiver, type RemoteFocusReceiverHost, type RemoteFocusReceiverOptions, RemoteLaserOverlay, type RemoteLaserOverlayHost, type RemoteLaserOverlayOptions, RemoteMeasureOverlay, type RemoteMeasureOverlayHost, type RemoteMeasureOverlayOptions, RemotePingOverlay, type RemotePingOverlayHost, type RemotePingOverlayOptions, type RenderStatsSnapshot, type RotateDirection, SelectTool, type SelectionStyleDetails, type ShapeElement, type ShapeKind, ShapeTool, type ShapeToolOptions, type ShortcutBindings, type ShortcutOptions, type ShortcutsApi, type Size, type StorageAdapter, type StrokeElement, type StrokePoint, type TemplateElement, type TemplateRenderStyle, type TemplateShape, TemplateTool, type TemplateToolOptions, type TextElement, TextTool, type TextToolOptions, type Tool, type ToolContext, ToolManager, type ToolName, VERSION, Viewport, type ViewportOptions, applyCameraView, boundsIntersect, cameraOriginForView, captureCameraView, computeElementRects, createArrow, createGrid, createHtmlElement, createImage, createNote, createShape, createStroke, createTemplate, createText, drawHexPath, elementRectsEqual, exportImage, exportSvg, fitZoomForView, getActiveFormats, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, getElementBounds, getElementStyle, getElementsBoundingBox, getHexCellsInCone, getHexCellsInLine, getHexCellsInRadius, getHexCellsInRectangle, getHexCellsInSquare, getHexDistance, isFocusPresence, isLaserTrailPresence, isMeasurePresence, isNearBezier, isPingPresence, resolveHtmlRouting, setFontSize, smartSnap, snapPoint, snapToHexCenter, styleToPatch, toFocusPresence, toLaserTrailPresence, toMeasurePresence, toPingPresence, toggleBold, toggleItalic, toggleStrikethrough, toggleUnderline };