@fieldnotes/core 0.55.0 → 0.56.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.cts CHANGED
@@ -986,6 +986,120 @@ declare class RemotePingOverlay {
986
986
  private renderPings;
987
987
  }
988
988
 
989
+ /**
990
+ * The one camera capability `PingInput` needs; `Camera` satisfies it. Screen
991
+ * coordinates are element-local (the same space `Camera.screenToWorld`
992
+ * expects).
993
+ */
994
+ interface PingInputHost {
995
+ screenToWorld(screen: Point): Point;
996
+ }
997
+ interface PingInputOptions {
998
+ /**
999
+ * Opt-in for the long-press gesture. Off by default: a hidden hold-to-ping
1000
+ * gesture surprises users outside shared-canvas products, so hosts enable
1001
+ * it deliberately. The keyboard/programmatic paths (`pingAtPointer`,
1002
+ * `pingAt`) are always available. Default `false`.
1003
+ */
1004
+ longPressEnabled?: boolean;
1005
+ /** Hold duration before a still press pings. Default `600`. */
1006
+ longPressMs?: number;
1007
+ /** Maximum pointer travel while holding; moving further cancels. Default `8`. */
1008
+ slopPx?: number;
1009
+ /** Emission style forwarded to hosts; matches `PingTool` defaults. */
1010
+ color?: string;
1011
+ durationMs?: number;
1012
+ radius?: number;
1013
+ /**
1014
+ * Minimum interval between emitted pings, shared across the long-press and
1015
+ * keyboard/programmatic paths. Faster pings are dropped entirely. Default
1016
+ * `300`.
1017
+ */
1018
+ minIntervalMs?: number;
1019
+ /**
1020
+ * Host veto consulted at fire time on every path (e.g. return `false` while
1021
+ * a ping tool is active to avoid double pings). A veto does not consume the
1022
+ * rate-limit interval.
1023
+ */
1024
+ shouldPing?: () => boolean;
1025
+ }
1026
+ /**
1027
+ * Always-available ping input alongside the active tool: long-press-to-ping
1028
+ * plus keyboard/programmatic pings, independent of `PingTool`.
1029
+ *
1030
+ * `PingInput` is a passive observer by contract. Its DOM pointer listeners are
1031
+ * registered `passive: true` and it never calls `preventDefault`,
1032
+ * `stopPropagation`, or captures pointers, so the active tool, panning, and
1033
+ * pinch navigation behave exactly as if it were absent (with a pencil active,
1034
+ * a still hold pings AND the pencil still draws its dot). It also never
1035
+ * renders: hosts feed emissions into their own `RemotePingOverlay` under a
1036
+ * `'self'` sender key. Pings are ephemeral — no elements, no undo history, no
1037
+ * persisted state, no camera movement.
1038
+ *
1039
+ * The long-press gesture is opt-in via `longPressEnabled` (hosts whose users
1040
+ * would not expect hold-to-ping simply leave it off and keep the keyboard
1041
+ * paths). When enabled, a long-press arms on the first pointer down (mouse
1042
+ * primary button, touch, or pen), fires after `longPressMs` at the original
1043
+ * press position, and
1044
+ * cancels on movement past `slopPx`, a second pointer (two-finger
1045
+ * navigation), or pointer up/cancel/leave. Keyboard support is a capability,
1046
+ * not a binding: hosts call `pingAtPointer()` (last tracked hover position,
1047
+ * world-converted at call time) or `pingAt(world)` from their own shortcut
1048
+ * handling.
1049
+ */
1050
+ declare class PingInput {
1051
+ private readonly element;
1052
+ private readonly host;
1053
+ private longPressEnabled;
1054
+ private longPressMs;
1055
+ private slopPx;
1056
+ private color;
1057
+ private durationMs;
1058
+ private radius;
1059
+ private minIntervalMs;
1060
+ private shouldPing;
1061
+ private press;
1062
+ private readonly downPointers;
1063
+ private lastPointerScreen;
1064
+ private lastEmitAt;
1065
+ private disposed;
1066
+ private optionListeners;
1067
+ private pingListeners;
1068
+ private readonly handlePointerDown;
1069
+ private readonly handlePointerMove;
1070
+ private readonly handlePointerUp;
1071
+ private readonly handlePointerCancel;
1072
+ private readonly handlePointerLeave;
1073
+ constructor(element: HTMLElement, host: PingInputHost, options?: PingInputOptions);
1074
+ private now;
1075
+ getOptions(): PingInputOptions;
1076
+ setOptions(options: PingInputOptions): void;
1077
+ onOptionsChange(listener: () => void): () => void;
1078
+ /**
1079
+ * Subscribes to emitted pings. Listeners must not throw; a throwing
1080
+ * listener is isolated so it cannot break input handling or other
1081
+ * listeners.
1082
+ */
1083
+ onPing(listener: (emission: PingEmission) => void): () => void;
1084
+ /**
1085
+ * Pings at the last tracked pointer position (hover moves and presses),
1086
+ * world-converted at call time. Returns `false` when no pointer has been
1087
+ * seen yet, the host vetoes, or the rate limit drops the ping.
1088
+ */
1089
+ pingAtPointer(): boolean;
1090
+ /** Pings a world position directly. Same veto and rate limit as every path. */
1091
+ pingAt(world: Point): boolean;
1092
+ /** Removes all DOM listeners and cancels any pending press. Idempotent. */
1093
+ dispose(): void;
1094
+ private toLocal;
1095
+ private onPointerDown;
1096
+ private onPointerMove;
1097
+ private onPointerEnd;
1098
+ private cancelPress;
1099
+ private firePress;
1100
+ private emit;
1101
+ }
1102
+
989
1103
  interface ActiveFormats {
990
1104
  bold: boolean;
991
1105
  italic: boolean;
@@ -1424,6 +1538,6 @@ declare class TemplateTool implements Tool {
1424
1538
  private notifyOptionsChange;
1425
1539
  }
1426
1540
 
1427
- declare const VERSION = "0.55.0";
1541
+ declare const VERSION = "0.56.0";
1428
1542
 
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 };
1543
+ 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, PingInput, type PingInputHost, type PingInputOptions, 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 };
package/dist/index.d.ts CHANGED
@@ -986,6 +986,120 @@ declare class RemotePingOverlay {
986
986
  private renderPings;
987
987
  }
988
988
 
989
+ /**
990
+ * The one camera capability `PingInput` needs; `Camera` satisfies it. Screen
991
+ * coordinates are element-local (the same space `Camera.screenToWorld`
992
+ * expects).
993
+ */
994
+ interface PingInputHost {
995
+ screenToWorld(screen: Point): Point;
996
+ }
997
+ interface PingInputOptions {
998
+ /**
999
+ * Opt-in for the long-press gesture. Off by default: a hidden hold-to-ping
1000
+ * gesture surprises users outside shared-canvas products, so hosts enable
1001
+ * it deliberately. The keyboard/programmatic paths (`pingAtPointer`,
1002
+ * `pingAt`) are always available. Default `false`.
1003
+ */
1004
+ longPressEnabled?: boolean;
1005
+ /** Hold duration before a still press pings. Default `600`. */
1006
+ longPressMs?: number;
1007
+ /** Maximum pointer travel while holding; moving further cancels. Default `8`. */
1008
+ slopPx?: number;
1009
+ /** Emission style forwarded to hosts; matches `PingTool` defaults. */
1010
+ color?: string;
1011
+ durationMs?: number;
1012
+ radius?: number;
1013
+ /**
1014
+ * Minimum interval between emitted pings, shared across the long-press and
1015
+ * keyboard/programmatic paths. Faster pings are dropped entirely. Default
1016
+ * `300`.
1017
+ */
1018
+ minIntervalMs?: number;
1019
+ /**
1020
+ * Host veto consulted at fire time on every path (e.g. return `false` while
1021
+ * a ping tool is active to avoid double pings). A veto does not consume the
1022
+ * rate-limit interval.
1023
+ */
1024
+ shouldPing?: () => boolean;
1025
+ }
1026
+ /**
1027
+ * Always-available ping input alongside the active tool: long-press-to-ping
1028
+ * plus keyboard/programmatic pings, independent of `PingTool`.
1029
+ *
1030
+ * `PingInput` is a passive observer by contract. Its DOM pointer listeners are
1031
+ * registered `passive: true` and it never calls `preventDefault`,
1032
+ * `stopPropagation`, or captures pointers, so the active tool, panning, and
1033
+ * pinch navigation behave exactly as if it were absent (with a pencil active,
1034
+ * a still hold pings AND the pencil still draws its dot). It also never
1035
+ * renders: hosts feed emissions into their own `RemotePingOverlay` under a
1036
+ * `'self'` sender key. Pings are ephemeral — no elements, no undo history, no
1037
+ * persisted state, no camera movement.
1038
+ *
1039
+ * The long-press gesture is opt-in via `longPressEnabled` (hosts whose users
1040
+ * would not expect hold-to-ping simply leave it off and keep the keyboard
1041
+ * paths). When enabled, a long-press arms on the first pointer down (mouse
1042
+ * primary button, touch, or pen), fires after `longPressMs` at the original
1043
+ * press position, and
1044
+ * cancels on movement past `slopPx`, a second pointer (two-finger
1045
+ * navigation), or pointer up/cancel/leave. Keyboard support is a capability,
1046
+ * not a binding: hosts call `pingAtPointer()` (last tracked hover position,
1047
+ * world-converted at call time) or `pingAt(world)` from their own shortcut
1048
+ * handling.
1049
+ */
1050
+ declare class PingInput {
1051
+ private readonly element;
1052
+ private readonly host;
1053
+ private longPressEnabled;
1054
+ private longPressMs;
1055
+ private slopPx;
1056
+ private color;
1057
+ private durationMs;
1058
+ private radius;
1059
+ private minIntervalMs;
1060
+ private shouldPing;
1061
+ private press;
1062
+ private readonly downPointers;
1063
+ private lastPointerScreen;
1064
+ private lastEmitAt;
1065
+ private disposed;
1066
+ private optionListeners;
1067
+ private pingListeners;
1068
+ private readonly handlePointerDown;
1069
+ private readonly handlePointerMove;
1070
+ private readonly handlePointerUp;
1071
+ private readonly handlePointerCancel;
1072
+ private readonly handlePointerLeave;
1073
+ constructor(element: HTMLElement, host: PingInputHost, options?: PingInputOptions);
1074
+ private now;
1075
+ getOptions(): PingInputOptions;
1076
+ setOptions(options: PingInputOptions): void;
1077
+ onOptionsChange(listener: () => void): () => void;
1078
+ /**
1079
+ * Subscribes to emitted pings. Listeners must not throw; a throwing
1080
+ * listener is isolated so it cannot break input handling or other
1081
+ * listeners.
1082
+ */
1083
+ onPing(listener: (emission: PingEmission) => void): () => void;
1084
+ /**
1085
+ * Pings at the last tracked pointer position (hover moves and presses),
1086
+ * world-converted at call time. Returns `false` when no pointer has been
1087
+ * seen yet, the host vetoes, or the rate limit drops the ping.
1088
+ */
1089
+ pingAtPointer(): boolean;
1090
+ /** Pings a world position directly. Same veto and rate limit as every path. */
1091
+ pingAt(world: Point): boolean;
1092
+ /** Removes all DOM listeners and cancels any pending press. Idempotent. */
1093
+ dispose(): void;
1094
+ private toLocal;
1095
+ private onPointerDown;
1096
+ private onPointerMove;
1097
+ private onPointerEnd;
1098
+ private cancelPress;
1099
+ private firePress;
1100
+ private emit;
1101
+ }
1102
+
989
1103
  interface ActiveFormats {
990
1104
  bold: boolean;
991
1105
  italic: boolean;
@@ -1424,6 +1538,6 @@ declare class TemplateTool implements Tool {
1424
1538
  private notifyOptionsChange;
1425
1539
  }
1426
1540
 
1427
- declare const VERSION = "0.55.0";
1541
+ declare const VERSION = "0.56.0";
1428
1542
 
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 };
1543
+ 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, PingInput, type PingInputHost, type PingInputOptions, 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 };
package/dist/index.js CHANGED
@@ -8845,6 +8845,191 @@ var RemotePingOverlay = class {
8845
8845
  }
8846
8846
  };
8847
8847
 
8848
+ // src/canvas/ping-input.ts
8849
+ var DEFAULT_LONG_PRESS_MS = 600;
8850
+ var DEFAULT_SLOP_PX = 8;
8851
+ var DEFAULT_COLOR3 = "#ff3b30";
8852
+ var DEFAULT_DURATION_MS2 = 1800;
8853
+ var DEFAULT_RADIUS2 = 48;
8854
+ var DEFAULT_MIN_INTERVAL_MS = 300;
8855
+ var PingInput = class {
8856
+ element;
8857
+ host;
8858
+ longPressEnabled;
8859
+ longPressMs;
8860
+ slopPx;
8861
+ color;
8862
+ durationMs;
8863
+ radius;
8864
+ minIntervalMs;
8865
+ shouldPing;
8866
+ press = null;
8867
+ downPointers = /* @__PURE__ */ new Set();
8868
+ lastPointerScreen = null;
8869
+ lastEmitAt = -Infinity;
8870
+ disposed = false;
8871
+ optionListeners = /* @__PURE__ */ new Set();
8872
+ pingListeners = /* @__PURE__ */ new Set();
8873
+ handlePointerDown = (e) => this.onPointerDown(e);
8874
+ handlePointerMove = (e) => this.onPointerMove(e);
8875
+ handlePointerUp = (e) => this.onPointerEnd(e);
8876
+ handlePointerCancel = (e) => this.onPointerEnd(e);
8877
+ handlePointerLeave = (e) => this.onPointerEnd(e);
8878
+ constructor(element, host, options = {}) {
8879
+ this.element = element;
8880
+ this.host = host;
8881
+ this.longPressEnabled = options.longPressEnabled ?? false;
8882
+ this.longPressMs = options.longPressMs ?? DEFAULT_LONG_PRESS_MS;
8883
+ this.slopPx = options.slopPx ?? DEFAULT_SLOP_PX;
8884
+ this.color = options.color ?? DEFAULT_COLOR3;
8885
+ this.durationMs = options.durationMs ?? DEFAULT_DURATION_MS2;
8886
+ this.radius = options.radius ?? DEFAULT_RADIUS2;
8887
+ this.minIntervalMs = options.minIntervalMs ?? DEFAULT_MIN_INTERVAL_MS;
8888
+ this.shouldPing = options.shouldPing;
8889
+ const opts = { passive: true };
8890
+ element.addEventListener("pointerdown", this.handlePointerDown, opts);
8891
+ element.addEventListener("pointermove", this.handlePointerMove, opts);
8892
+ element.addEventListener("pointerup", this.handlePointerUp, opts);
8893
+ element.addEventListener("pointercancel", this.handlePointerCancel, opts);
8894
+ element.addEventListener("pointerleave", this.handlePointerLeave, opts);
8895
+ }
8896
+ now() {
8897
+ return performance.now();
8898
+ }
8899
+ getOptions() {
8900
+ return {
8901
+ longPressEnabled: this.longPressEnabled,
8902
+ longPressMs: this.longPressMs,
8903
+ slopPx: this.slopPx,
8904
+ color: this.color,
8905
+ durationMs: this.durationMs,
8906
+ radius: this.radius,
8907
+ minIntervalMs: this.minIntervalMs,
8908
+ ...this.shouldPing ? { shouldPing: this.shouldPing } : {}
8909
+ };
8910
+ }
8911
+ setOptions(options) {
8912
+ if (options.longPressEnabled !== void 0) {
8913
+ this.longPressEnabled = options.longPressEnabled;
8914
+ if (!this.longPressEnabled) this.cancelPress();
8915
+ }
8916
+ if (options.longPressMs !== void 0) this.longPressMs = options.longPressMs;
8917
+ if (options.slopPx !== void 0) this.slopPx = options.slopPx;
8918
+ if (options.color !== void 0) this.color = options.color;
8919
+ if (options.durationMs !== void 0) this.durationMs = options.durationMs;
8920
+ if (options.radius !== void 0) this.radius = options.radius;
8921
+ if (options.minIntervalMs !== void 0) this.minIntervalMs = options.minIntervalMs;
8922
+ if (options.shouldPing !== void 0) this.shouldPing = options.shouldPing;
8923
+ for (const listener of this.optionListeners) listener();
8924
+ }
8925
+ onOptionsChange(listener) {
8926
+ this.optionListeners.add(listener);
8927
+ return () => this.optionListeners.delete(listener);
8928
+ }
8929
+ /**
8930
+ * Subscribes to emitted pings. Listeners must not throw; a throwing
8931
+ * listener is isolated so it cannot break input handling or other
8932
+ * listeners.
8933
+ */
8934
+ onPing(listener) {
8935
+ this.pingListeners.add(listener);
8936
+ return () => this.pingListeners.delete(listener);
8937
+ }
8938
+ /**
8939
+ * Pings at the last tracked pointer position (hover moves and presses),
8940
+ * world-converted at call time. Returns `false` when no pointer has been
8941
+ * seen yet, the host vetoes, or the rate limit drops the ping.
8942
+ */
8943
+ pingAtPointer() {
8944
+ if (this.disposed || this.lastPointerScreen === null) return false;
8945
+ return this.emit(this.host.screenToWorld(this.lastPointerScreen));
8946
+ }
8947
+ /** Pings a world position directly. Same veto and rate limit as every path. */
8948
+ pingAt(world) {
8949
+ if (this.disposed) return false;
8950
+ return this.emit(world);
8951
+ }
8952
+ /** Removes all DOM listeners and cancels any pending press. Idempotent. */
8953
+ dispose() {
8954
+ if (this.disposed) return;
8955
+ this.disposed = true;
8956
+ this.cancelPress();
8957
+ this.downPointers.clear();
8958
+ this.element.removeEventListener("pointerdown", this.handlePointerDown);
8959
+ this.element.removeEventListener("pointermove", this.handlePointerMove);
8960
+ this.element.removeEventListener("pointerup", this.handlePointerUp);
8961
+ this.element.removeEventListener("pointercancel", this.handlePointerCancel);
8962
+ this.element.removeEventListener("pointerleave", this.handlePointerLeave);
8963
+ this.optionListeners.clear();
8964
+ this.pingListeners.clear();
8965
+ }
8966
+ toLocal(e) {
8967
+ const rect = this.element.getBoundingClientRect();
8968
+ return { x: e.clientX - rect.left, y: e.clientY - rect.top };
8969
+ }
8970
+ onPointerDown(e) {
8971
+ const local = this.toLocal(e);
8972
+ this.lastPointerScreen = local;
8973
+ const hadPointers = this.downPointers.size > 0;
8974
+ this.downPointers.add(e.pointerId);
8975
+ if (hadPointers) {
8976
+ this.cancelPress();
8977
+ return;
8978
+ }
8979
+ if (!this.longPressEnabled) return;
8980
+ if (e.pointerType === "mouse" && e.button !== 0) return;
8981
+ this.press = {
8982
+ pointerId: e.pointerId,
8983
+ x: local.x,
8984
+ y: local.y,
8985
+ timer: setTimeout(() => this.firePress(), this.longPressMs)
8986
+ };
8987
+ }
8988
+ onPointerMove(e) {
8989
+ const local = this.toLocal(e);
8990
+ this.lastPointerScreen = local;
8991
+ if (this.press === null || e.pointerId !== this.press.pointerId) return;
8992
+ const dx = local.x - this.press.x;
8993
+ const dy = local.y - this.press.y;
8994
+ if (Math.hypot(dx, dy) > this.slopPx) this.cancelPress();
8995
+ }
8996
+ onPointerEnd(e) {
8997
+ this.downPointers.delete(e.pointerId);
8998
+ if (this.press !== null && e.pointerId === this.press.pointerId) this.cancelPress();
8999
+ }
9000
+ cancelPress() {
9001
+ if (this.press === null) return;
9002
+ clearTimeout(this.press.timer);
9003
+ this.press = null;
9004
+ }
9005
+ firePress() {
9006
+ if (this.press === null) return;
9007
+ const screen = { x: this.press.x, y: this.press.y };
9008
+ this.press = null;
9009
+ this.emit(this.host.screenToWorld(screen));
9010
+ }
9011
+ emit(world) {
9012
+ if (this.shouldPing && !this.shouldPing()) return false;
9013
+ const t = this.now();
9014
+ if (t - this.lastEmitAt < this.minIntervalMs) return false;
9015
+ this.lastEmitAt = t;
9016
+ const emission = {
9017
+ x: world.x,
9018
+ y: world.y,
9019
+ color: this.color,
9020
+ durationMs: this.durationMs,
9021
+ radius: this.radius
9022
+ };
9023
+ for (const listener of this.pingListeners) {
9024
+ try {
9025
+ listener(emission);
9026
+ } catch {
9027
+ }
9028
+ }
9029
+ return true;
9030
+ }
9031
+ };
9032
+
8848
9033
  // src/tools/hand-tool.ts
8849
9034
  var HandTool = class {
8850
9035
  name = "hand";
@@ -9106,7 +9291,7 @@ function erasePoints(points, eraser, radius) {
9106
9291
  }
9107
9292
 
9108
9293
  // src/tools/eraser-tool.ts
9109
- var DEFAULT_RADIUS2 = 20;
9294
+ var DEFAULT_RADIUS3 = 20;
9110
9295
  function makeEraserCursor(radius) {
9111
9296
  const size = radius * 2;
9112
9297
  const svg = `<svg xmlns='http://www.w3.org/2000/svg' width='${size}' height='${size}'><circle cx='${radius}' cy='${radius}' r='${radius - 1}' fill='none' stroke='%23666' stroke-width='1.5'/></svg>`;
@@ -9119,7 +9304,7 @@ var EraserTool = class {
9119
9304
  cursor;
9120
9305
  mode;
9121
9306
  constructor(options = {}) {
9122
- this.radius = options.radius ?? DEFAULT_RADIUS2;
9307
+ this.radius = options.radius ?? DEFAULT_RADIUS3;
9123
9308
  this.cursor = makeEraserCursor(this.radius);
9124
9309
  this.mode = options.mode ?? "partial";
9125
9310
  }
@@ -11401,7 +11586,7 @@ var TemplateTool = class {
11401
11586
  };
11402
11587
 
11403
11588
  // src/tools/laser-tool.ts
11404
- var DEFAULT_COLOR3 = "#ff3b30";
11589
+ var DEFAULT_COLOR4 = "#ff3b30";
11405
11590
  var DEFAULT_WIDTH2 = 4;
11406
11591
  var DEFAULT_FADE_MS2 = 1200;
11407
11592
  var LaserTool = class {
@@ -11417,7 +11602,7 @@ var LaserTool = class {
11417
11602
  pendingEmission = [];
11418
11603
  constructor(options = {}) {
11419
11604
  this.name = options.name ?? "laser";
11420
- this.color = options.color ?? DEFAULT_COLOR3;
11605
+ this.color = options.color ?? DEFAULT_COLOR4;
11421
11606
  this.width = options.width ?? DEFAULT_WIDTH2;
11422
11607
  this.fadeMs = options.fadeMs ?? DEFAULT_FADE_MS2;
11423
11608
  }
@@ -11545,10 +11730,10 @@ var LaserTool = class {
11545
11730
  };
11546
11731
 
11547
11732
  // src/tools/ping-tool.ts
11548
- var DEFAULT_COLOR4 = "#ff3b30";
11549
- var DEFAULT_DURATION_MS2 = 1800;
11550
- var DEFAULT_RADIUS3 = 48;
11551
- var DEFAULT_MIN_INTERVAL_MS = 300;
11733
+ var DEFAULT_COLOR5 = "#ff3b30";
11734
+ var DEFAULT_DURATION_MS3 = 1800;
11735
+ var DEFAULT_RADIUS4 = 48;
11736
+ var DEFAULT_MIN_INTERVAL_MS2 = 300;
11552
11737
  var PingTool = class {
11553
11738
  name;
11554
11739
  color;
@@ -11562,10 +11747,10 @@ var PingTool = class {
11562
11747
  pingListeners = /* @__PURE__ */ new Set();
11563
11748
  constructor(options = {}) {
11564
11749
  this.name = options.name ?? "ping";
11565
- this.color = options.color ?? DEFAULT_COLOR4;
11566
- this.durationMs = options.durationMs ?? DEFAULT_DURATION_MS2;
11567
- this.radius = options.radius ?? DEFAULT_RADIUS3;
11568
- this.minIntervalMs = options.minIntervalMs ?? DEFAULT_MIN_INTERVAL_MS;
11750
+ this.color = options.color ?? DEFAULT_COLOR5;
11751
+ this.durationMs = options.durationMs ?? DEFAULT_DURATION_MS3;
11752
+ this.radius = options.radius ?? DEFAULT_RADIUS4;
11753
+ this.minIntervalMs = options.minIntervalMs ?? DEFAULT_MIN_INTERVAL_MS2;
11569
11754
  }
11570
11755
  now() {
11571
11756
  return performance.now();
@@ -11665,7 +11850,7 @@ var PingTool = class {
11665
11850
  };
11666
11851
 
11667
11852
  // src/index.ts
11668
- var VERSION = "0.55.0";
11853
+ var VERSION = "0.56.0";
11669
11854
  export {
11670
11855
  ArrowTool,
11671
11856
  AutoSave,
@@ -11686,6 +11871,7 @@ export {
11686
11871
  NoteTool,
11687
11872
  PING_PRESENCE_KIND,
11688
11873
  PencilTool,
11874
+ PingInput,
11689
11875
  PingTool,
11690
11876
  RemoteLaserOverlay,
11691
11877
  RemotePingOverlay,