@fieldnotes/core 0.53.0 → 0.55.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
@@ -249,7 +249,7 @@ interface Tool {
249
249
  setOptions?(options: object): void;
250
250
  onOptionsChange?(listener: () => void): () => void;
251
251
  }
252
- type ToolName = 'hand' | 'select' | 'pencil' | 'eraser' | 'arrow' | 'note' | 'image' | 'text' | 'shape' | 'measure' | 'template' | 'laser';
252
+ type ToolName = 'hand' | 'select' | 'pencil' | 'eraser' | 'arrow' | 'note' | 'image' | 'text' | 'shape' | 'measure' | 'template' | 'laser' | 'ping';
253
253
 
254
254
  declare function snapPoint(point: Point, gridSize: number): Point;
255
255
  declare function snapToHexCenter(point: Point, cellSize: number, orientation: HexOrientation): Point;
@@ -510,6 +510,15 @@ interface RenderStatsSnapshot {
510
510
  frameCount: number;
511
511
  }
512
512
 
513
+ /**
514
+ * A world-space draw callback rendered above elements on every frame,
515
+ * regardless of which tool is active. The context arrives with the camera
516
+ * transform applied; implementations must not assume exclusive context state
517
+ * (each renderer is wrapped in save/restore). A throwing renderer is isolated
518
+ * per frame and must not break the render loop.
519
+ */
520
+ type OverlayRenderer = (ctx: CanvasRenderingContext2D) => void;
521
+
513
522
  interface ElementStyle {
514
523
  color?: string;
515
524
  fillColor?: string;
@@ -609,6 +618,15 @@ declare class Viewport {
609
618
  setSmartGuides(enabled: boolean): void;
610
619
  fitToContent(padding?: number): void;
611
620
  requestRender(): void;
621
+ /**
622
+ * Registers a world-space overlay drawn above elements on every frame,
623
+ * regardless of the active tool — the surface for remote presence visuals
624
+ * such as laser trails, cursors, pings, and shared rulers. Overlays draw
625
+ * beneath the active tool's own `renderOverlay` and never touch elements,
626
+ * history, or persisted state. Returns an idempotent unsubscribe that also
627
+ * erases the overlay's last frame.
628
+ */
629
+ registerOverlay(draw: OverlayRenderer): () => void;
612
630
  exportState(): CanvasState;
613
631
  exportJSON(): string;
614
632
  exportImage(options?: ExportImageOptions): Promise<Blob | null>;
@@ -698,6 +716,276 @@ declare class Viewport {
698
716
  private observeResize;
699
717
  }
700
718
 
719
+ interface LaserToolOptions {
720
+ name?: string;
721
+ color?: string;
722
+ width?: number;
723
+ fadeMs?: number;
724
+ }
725
+ /**
726
+ * A coalesced batch of locally drawn trail points, flushed at most once per
727
+ * animation frame — the outgoing side of a shared laser pointer. Emissions
728
+ * carry world coordinates and the tool's current style so a host can forward
729
+ * them as ephemeral presence without duplicating the tool's input handling.
730
+ */
731
+ interface LaserTrailEmission {
732
+ /** World-space points recorded since the previous emission, oldest first. */
733
+ readonly points: readonly Point[];
734
+ readonly color: string;
735
+ readonly width: number;
736
+ readonly fadeMs: number;
737
+ }
738
+ declare class LaserTool implements Tool {
739
+ readonly name: string;
740
+ private color;
741
+ private width;
742
+ private fadeMs;
743
+ private trail;
744
+ private rafId;
745
+ private drawing;
746
+ private optionListeners;
747
+ private trailListeners;
748
+ private pendingEmission;
749
+ constructor(options?: LaserToolOptions);
750
+ private now;
751
+ onActivate(ctx: ToolContext): void;
752
+ onDeactivate(ctx: ToolContext): void;
753
+ getOptions(): LaserToolOptions;
754
+ onOptionsChange(listener: () => void): () => void;
755
+ /**
756
+ * Subscribes to coalesced trail emissions. Points recorded by pointer input
757
+ * are batched and delivered at most once per animation frame, keeping
758
+ * outgoing presence packets small. Listeners must not throw; a throwing
759
+ * listener is isolated so it cannot stall the fade loop.
760
+ */
761
+ onTrail(listener: (emission: LaserTrailEmission) => void): () => void;
762
+ setOptions(options: LaserToolOptions): void;
763
+ onPointerDown(state: PointerState, ctx: ToolContext): void;
764
+ onPointerMove(state: PointerState, ctx: ToolContext): void;
765
+ private recordPoint;
766
+ onPointerUp(_state: PointerState, _ctx: ToolContext): void;
767
+ renderOverlay(ctx: CanvasRenderingContext2D): void;
768
+ private ensureAnimating;
769
+ private flushEmission;
770
+ private tick;
771
+ private notifyOptionsChange;
772
+ }
773
+
774
+ /**
775
+ * The wire shape of a laser-trail presence payload. Presence data is untyped
776
+ * on the wire, so hosts discriminate on `kind`; `isLaserTrailPresence`
777
+ * validates a received payload before it reaches the overlay. Trails are
778
+ * ephemeral by contract: they must travel as presence only — never as
779
+ * elements, undo history, persisted canvas state, or durable operations.
780
+ */
781
+ interface LaserTrailPresence {
782
+ readonly kind: 'laser';
783
+ /** World-space points, oldest first. */
784
+ readonly points: readonly Point[];
785
+ readonly color?: string;
786
+ readonly width?: number;
787
+ readonly fadeMs?: number;
788
+ }
789
+ declare const LASER_TRAIL_PRESENCE_KIND = "laser";
790
+ declare function isLaserTrailPresence(data: unknown): data is LaserTrailPresence;
791
+ /** Builds the presence payload for one local `LaserTool` emission. */
792
+ declare function toLaserTrailPresence(emission: LaserTrailEmission): LaserTrailPresence;
793
+ /**
794
+ * The two viewport capabilities the overlay needs; `Viewport` satisfies it.
795
+ */
796
+ interface RemoteLaserOverlayHost {
797
+ registerOverlay(draw: OverlayRenderer): () => void;
798
+ requestRender(): void;
799
+ }
800
+ interface RemoteLaserOverlayOptions {
801
+ /** Style fallbacks when a payload omits them. */
802
+ color?: string;
803
+ width?: number;
804
+ fadeMs?: number;
805
+ /** Per-sender point cap; oldest points drop first. Default `512`. */
806
+ maxPointsPerSender?: number;
807
+ }
808
+ /**
809
+ * Renders fading laser trails for remote senders through the viewport overlay
810
+ * registration, independent of the viewer's active tool. Points are stamped
811
+ * with local receive time (remote clocks are never trusted), fade like the
812
+ * local `LaserTool`, and a sender's trail disappears immediately on
813
+ * `remove()` — wire that to `presence-leave`/disconnect. The controller never
814
+ * touches elements, history, or persisted state.
815
+ */
816
+ declare class RemoteLaserOverlay {
817
+ private readonly host;
818
+ private readonly color;
819
+ private readonly width;
820
+ private readonly fadeMs;
821
+ private readonly maxPointsPerSender;
822
+ private readonly trails;
823
+ private unregister;
824
+ private rafId;
825
+ private disposed;
826
+ constructor(host: RemoteLaserOverlayHost, options?: RemoteLaserOverlayOptions);
827
+ private now;
828
+ /**
829
+ * Applies a presence payload from `sender` (any opaque per-sender key, e.g.
830
+ * the envelope `from`). Non-laser or malformed payloads are ignored and
831
+ * reported as `false`, so hosts can feed every presence frame through.
832
+ */
833
+ apply(sender: string, data: unknown): boolean;
834
+ /** Removes a sender's trail immediately (presence-leave/disconnect). */
835
+ remove(sender: string): void;
836
+ /** Removes every trail immediately. */
837
+ clear(): void;
838
+ /** Number of senders with a live (unfaded) trail. */
839
+ get activeSenderCount(): number;
840
+ /** Unregisters the overlay and stops the fade loop. Idempotent. */
841
+ dispose(): void;
842
+ private ensureAnimating;
843
+ private tick;
844
+ private renderTrails;
845
+ }
846
+
847
+ interface PingToolOptions {
848
+ name?: string;
849
+ color?: string;
850
+ /** Total pulse animation length in milliseconds. */
851
+ durationMs?: number;
852
+ /** Maximum ripple radius in world units. */
853
+ radius?: number;
854
+ /**
855
+ * Minimum interval between emitted pings. Taps arriving faster are ignored
856
+ * entirely (no local pulse, no emission), so rapid-fire pings cannot starve
857
+ * durable sync traffic.
858
+ */
859
+ minIntervalMs?: number;
860
+ }
861
+ /**
862
+ * One emitted ping — the outgoing side of a shared "look here" marker.
863
+ * Emissions carry the world position and the tool's current style so a host
864
+ * can forward them as ephemeral presence without duplicating input handling.
865
+ */
866
+ interface PingEmission {
867
+ /** World-space ping position. */
868
+ readonly x: number;
869
+ readonly y: number;
870
+ readonly color: string;
871
+ readonly durationMs: number;
872
+ readonly radius: number;
873
+ }
874
+ /**
875
+ * Tap or click to ping a world position: a short expanding-pulse animation is
876
+ * rendered locally and one `PingEmission` is delivered to `onPing` listeners
877
+ * per accepted tap. Pings are ephemeral by contract: they never create
878
+ * elements, enter undo history, or touch persisted canvas state — hosts
879
+ * forward emissions as presence only.
880
+ */
881
+ declare class PingTool implements Tool {
882
+ readonly name: string;
883
+ private color;
884
+ private durationMs;
885
+ private radius;
886
+ private minIntervalMs;
887
+ private pings;
888
+ private lastEmitAt;
889
+ private rafId;
890
+ private optionListeners;
891
+ private pingListeners;
892
+ constructor(options?: PingToolOptions);
893
+ private now;
894
+ onActivate(ctx: ToolContext): void;
895
+ onDeactivate(ctx: ToolContext): void;
896
+ getOptions(): PingToolOptions;
897
+ setOptions(options: PingToolOptions): void;
898
+ onOptionsChange(listener: () => void): () => void;
899
+ /**
900
+ * Subscribes to accepted pings. Listeners must not throw; a throwing
901
+ * listener is isolated so it cannot break the tap handling or other
902
+ * listeners.
903
+ */
904
+ onPing(listener: (emission: PingEmission) => void): () => void;
905
+ onPointerDown(state: PointerState, ctx: ToolContext): void;
906
+ onPointerMove(_state: PointerState, _ctx: ToolContext): void;
907
+ onPointerUp(_state: PointerState, _ctx: ToolContext): void;
908
+ renderOverlay(ctx: CanvasRenderingContext2D): void;
909
+ private ensureAnimating;
910
+ private tick;
911
+ private notifyOptionsChange;
912
+ }
913
+
914
+ /**
915
+ * The wire shape of a map-ping presence payload. Presence data is untyped on
916
+ * the wire, so hosts discriminate on `kind`; `isPingPresence` validates a
917
+ * received payload before it reaches the overlay. Pings are ephemeral by
918
+ * contract: they must travel as presence only — never as elements, undo
919
+ * history, persisted canvas state, or durable operations.
920
+ */
921
+ interface PingPresence {
922
+ readonly kind: 'ping';
923
+ /** World-space ping position. */
924
+ readonly x: number;
925
+ readonly y: number;
926
+ readonly color?: string;
927
+ readonly durationMs?: number;
928
+ readonly radius?: number;
929
+ }
930
+ declare const PING_PRESENCE_KIND = "ping";
931
+ declare function isPingPresence(data: unknown): data is PingPresence;
932
+ /** Builds the presence payload for one local `PingTool` emission. */
933
+ declare function toPingPresence(emission: PingEmission): PingPresence;
934
+ /**
935
+ * The two viewport capabilities the overlay needs; `Viewport` satisfies it.
936
+ */
937
+ interface RemotePingOverlayHost {
938
+ registerOverlay(draw: OverlayRenderer): () => void;
939
+ requestRender(): void;
940
+ }
941
+ interface RemotePingOverlayOptions {
942
+ /** Style fallbacks when a payload omits them. */
943
+ color?: string;
944
+ durationMs?: number;
945
+ radius?: number;
946
+ /** Per-sender live-ping cap; oldest pings drop first. Default `8`. */
947
+ maxPingsPerSender?: number;
948
+ }
949
+ /**
950
+ * Renders expanding-pulse pings for remote senders through the viewport
951
+ * overlay registration, independent of the viewer's active tool. Pings are
952
+ * stamped with local receive time (remote clocks are never trusted),
953
+ * self-expire when their animation ends, and a sender's pings disappear
954
+ * immediately on `remove()` — wire that to `presence-leave`/disconnect. The
955
+ * controller never touches elements, history, or persisted state, and it
956
+ * never moves the viewer's camera: pings are visual only.
957
+ */
958
+ declare class RemotePingOverlay {
959
+ private readonly host;
960
+ private readonly color;
961
+ private readonly durationMs;
962
+ private readonly radius;
963
+ private readonly maxPingsPerSender;
964
+ private readonly pings;
965
+ private unregister;
966
+ private rafId;
967
+ private disposed;
968
+ constructor(host: RemotePingOverlayHost, options?: RemotePingOverlayOptions);
969
+ private now;
970
+ /**
971
+ * Applies a presence payload from `sender` (any opaque per-sender key, e.g.
972
+ * the envelope `from`). Non-ping or malformed payloads are ignored and
973
+ * reported as `false`, so hosts can feed every presence frame through.
974
+ */
975
+ apply(sender: string, data: unknown): boolean;
976
+ /** Removes a sender's pings immediately (presence-leave/disconnect). */
977
+ remove(sender: string): void;
978
+ /** Removes every ping immediately. */
979
+ clear(): void;
980
+ /** Number of senders with a live (unexpired) ping. */
981
+ get activeSenderCount(): number;
982
+ /** Unregisters the overlay and stops the animation loop. Idempotent. */
983
+ dispose(): void;
984
+ private ensureAnimating;
985
+ private tick;
986
+ private renderPings;
987
+ }
988
+
701
989
  interface ActiveFormats {
702
990
  bold: boolean;
703
991
  italic: boolean;
@@ -1136,37 +1424,6 @@ declare class TemplateTool implements Tool {
1136
1424
  private notifyOptionsChange;
1137
1425
  }
1138
1426
 
1139
- interface LaserToolOptions {
1140
- name?: string;
1141
- color?: string;
1142
- width?: number;
1143
- fadeMs?: number;
1144
- }
1145
- declare class LaserTool implements Tool {
1146
- readonly name: string;
1147
- private color;
1148
- private width;
1149
- private fadeMs;
1150
- private trail;
1151
- private rafId;
1152
- private drawing;
1153
- private optionListeners;
1154
- constructor(options?: LaserToolOptions);
1155
- private now;
1156
- onActivate(ctx: ToolContext): void;
1157
- onDeactivate(ctx: ToolContext): void;
1158
- getOptions(): LaserToolOptions;
1159
- onOptionsChange(listener: () => void): () => void;
1160
- setOptions(options: LaserToolOptions): void;
1161
- onPointerDown(state: PointerState, ctx: ToolContext): void;
1162
- onPointerMove(state: PointerState, ctx: ToolContext): void;
1163
- onPointerUp(_state: PointerState, _ctx: ToolContext): void;
1164
- renderOverlay(ctx: CanvasRenderingContext2D): void;
1165
- private ensureAnimating;
1166
- private tick;
1167
- private notifyOptionsChange;
1168
- }
1169
-
1170
- declare const VERSION = "0.53.0";
1427
+ declare const VERSION = "0.55.0";
1171
1428
 
1172
- 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 CameraChangeInfo, type CameraOptions, 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, type FontSizePreset, 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, LaserTool, type LaserToolOptions, type Layer, LayerManager, LocalStorageAdapter, MeasureTool, type MeasureToolOptions, type Measurement, MemoryAdapter, type NoteElement, NoteTool, type NoteToolOptions, PencilTool, type PencilToolOptions, type Point, type PointerState, type RenderStatsSnapshot, type RotateDirection, SelectTool, 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, boundsIntersect, createArrow, createGrid, createHtmlElement, createImage, createNote, createShape, createStroke, createTemplate, createText, drawHexPath, exportImage, exportSvg, getActiveFormats, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, getElementBounds, getElementStyle, getElementsBoundingBox, getHexCellsInCone, getHexCellsInLine, getHexCellsInRadius, getHexCellsInRectangle, getHexCellsInSquare, getHexDistance, isNearBezier, setFontSize, smartSnap, snapPoint, snapToHexCenter, styleToPatch, toggleBold, toggleItalic, toggleStrikethrough, toggleUnderline };
1429
+ 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 CameraChangeInfo, type CameraOptions, 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, type FontSizePreset, 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, MeasureTool, type MeasureToolOptions, type Measurement, MemoryAdapter, type NoteElement, NoteTool, type NoteToolOptions, type OverlayRenderer, PING_PRESENCE_KIND, PencilTool, type PencilToolOptions, type PingEmission, type PingPresence, PingTool, type PingToolOptions, type Point, type PointerState, RemoteLaserOverlay, type RemoteLaserOverlayHost, type RemoteLaserOverlayOptions, RemotePingOverlay, type RemotePingOverlayHost, type RemotePingOverlayOptions, type RenderStatsSnapshot, type RotateDirection, SelectTool, 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, boundsIntersect, createArrow, createGrid, createHtmlElement, createImage, createNote, createShape, createStroke, createTemplate, createText, drawHexPath, exportImage, exportSvg, getActiveFormats, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, getElementBounds, getElementStyle, getElementsBoundingBox, getHexCellsInCone, getHexCellsInLine, getHexCellsInRadius, getHexCellsInRectangle, getHexCellsInSquare, getHexDistance, isLaserTrailPresence, isNearBezier, isPingPresence, setFontSize, smartSnap, snapPoint, snapToHexCenter, styleToPatch, toLaserTrailPresence, toPingPresence, toggleBold, toggleItalic, toggleStrikethrough, toggleUnderline };