@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.cjs CHANGED
@@ -39,6 +39,7 @@ __export(index_exports, {
39
39
  NoteTool: () => NoteTool,
40
40
  PING_PRESENCE_KIND: () => PING_PRESENCE_KIND,
41
41
  PencilTool: () => PencilTool,
42
+ PingInput: () => PingInput,
42
43
  PingTool: () => PingTool,
43
44
  RemoteLaserOverlay: () => RemoteLaserOverlay,
44
45
  RemotePingOverlay: () => RemotePingOverlay,
@@ -8941,6 +8942,191 @@ var RemotePingOverlay = class {
8941
8942
  }
8942
8943
  };
8943
8944
 
8945
+ // src/canvas/ping-input.ts
8946
+ var DEFAULT_LONG_PRESS_MS = 600;
8947
+ var DEFAULT_SLOP_PX = 8;
8948
+ var DEFAULT_COLOR3 = "#ff3b30";
8949
+ var DEFAULT_DURATION_MS2 = 1800;
8950
+ var DEFAULT_RADIUS2 = 48;
8951
+ var DEFAULT_MIN_INTERVAL_MS = 300;
8952
+ var PingInput = class {
8953
+ element;
8954
+ host;
8955
+ longPressEnabled;
8956
+ longPressMs;
8957
+ slopPx;
8958
+ color;
8959
+ durationMs;
8960
+ radius;
8961
+ minIntervalMs;
8962
+ shouldPing;
8963
+ press = null;
8964
+ downPointers = /* @__PURE__ */ new Set();
8965
+ lastPointerScreen = null;
8966
+ lastEmitAt = -Infinity;
8967
+ disposed = false;
8968
+ optionListeners = /* @__PURE__ */ new Set();
8969
+ pingListeners = /* @__PURE__ */ new Set();
8970
+ handlePointerDown = (e) => this.onPointerDown(e);
8971
+ handlePointerMove = (e) => this.onPointerMove(e);
8972
+ handlePointerUp = (e) => this.onPointerEnd(e);
8973
+ handlePointerCancel = (e) => this.onPointerEnd(e);
8974
+ handlePointerLeave = (e) => this.onPointerEnd(e);
8975
+ constructor(element, host, options = {}) {
8976
+ this.element = element;
8977
+ this.host = host;
8978
+ this.longPressEnabled = options.longPressEnabled ?? false;
8979
+ this.longPressMs = options.longPressMs ?? DEFAULT_LONG_PRESS_MS;
8980
+ this.slopPx = options.slopPx ?? DEFAULT_SLOP_PX;
8981
+ this.color = options.color ?? DEFAULT_COLOR3;
8982
+ this.durationMs = options.durationMs ?? DEFAULT_DURATION_MS2;
8983
+ this.radius = options.radius ?? DEFAULT_RADIUS2;
8984
+ this.minIntervalMs = options.minIntervalMs ?? DEFAULT_MIN_INTERVAL_MS;
8985
+ this.shouldPing = options.shouldPing;
8986
+ const opts = { passive: true };
8987
+ element.addEventListener("pointerdown", this.handlePointerDown, opts);
8988
+ element.addEventListener("pointermove", this.handlePointerMove, opts);
8989
+ element.addEventListener("pointerup", this.handlePointerUp, opts);
8990
+ element.addEventListener("pointercancel", this.handlePointerCancel, opts);
8991
+ element.addEventListener("pointerleave", this.handlePointerLeave, opts);
8992
+ }
8993
+ now() {
8994
+ return performance.now();
8995
+ }
8996
+ getOptions() {
8997
+ return {
8998
+ longPressEnabled: this.longPressEnabled,
8999
+ longPressMs: this.longPressMs,
9000
+ slopPx: this.slopPx,
9001
+ color: this.color,
9002
+ durationMs: this.durationMs,
9003
+ radius: this.radius,
9004
+ minIntervalMs: this.minIntervalMs,
9005
+ ...this.shouldPing ? { shouldPing: this.shouldPing } : {}
9006
+ };
9007
+ }
9008
+ setOptions(options) {
9009
+ if (options.longPressEnabled !== void 0) {
9010
+ this.longPressEnabled = options.longPressEnabled;
9011
+ if (!this.longPressEnabled) this.cancelPress();
9012
+ }
9013
+ if (options.longPressMs !== void 0) this.longPressMs = options.longPressMs;
9014
+ if (options.slopPx !== void 0) this.slopPx = options.slopPx;
9015
+ if (options.color !== void 0) this.color = options.color;
9016
+ if (options.durationMs !== void 0) this.durationMs = options.durationMs;
9017
+ if (options.radius !== void 0) this.radius = options.radius;
9018
+ if (options.minIntervalMs !== void 0) this.minIntervalMs = options.minIntervalMs;
9019
+ if (options.shouldPing !== void 0) this.shouldPing = options.shouldPing;
9020
+ for (const listener of this.optionListeners) listener();
9021
+ }
9022
+ onOptionsChange(listener) {
9023
+ this.optionListeners.add(listener);
9024
+ return () => this.optionListeners.delete(listener);
9025
+ }
9026
+ /**
9027
+ * Subscribes to emitted pings. Listeners must not throw; a throwing
9028
+ * listener is isolated so it cannot break input handling or other
9029
+ * listeners.
9030
+ */
9031
+ onPing(listener) {
9032
+ this.pingListeners.add(listener);
9033
+ return () => this.pingListeners.delete(listener);
9034
+ }
9035
+ /**
9036
+ * Pings at the last tracked pointer position (hover moves and presses),
9037
+ * world-converted at call time. Returns `false` when no pointer has been
9038
+ * seen yet, the host vetoes, or the rate limit drops the ping.
9039
+ */
9040
+ pingAtPointer() {
9041
+ if (this.disposed || this.lastPointerScreen === null) return false;
9042
+ return this.emit(this.host.screenToWorld(this.lastPointerScreen));
9043
+ }
9044
+ /** Pings a world position directly. Same veto and rate limit as every path. */
9045
+ pingAt(world) {
9046
+ if (this.disposed) return false;
9047
+ return this.emit(world);
9048
+ }
9049
+ /** Removes all DOM listeners and cancels any pending press. Idempotent. */
9050
+ dispose() {
9051
+ if (this.disposed) return;
9052
+ this.disposed = true;
9053
+ this.cancelPress();
9054
+ this.downPointers.clear();
9055
+ this.element.removeEventListener("pointerdown", this.handlePointerDown);
9056
+ this.element.removeEventListener("pointermove", this.handlePointerMove);
9057
+ this.element.removeEventListener("pointerup", this.handlePointerUp);
9058
+ this.element.removeEventListener("pointercancel", this.handlePointerCancel);
9059
+ this.element.removeEventListener("pointerleave", this.handlePointerLeave);
9060
+ this.optionListeners.clear();
9061
+ this.pingListeners.clear();
9062
+ }
9063
+ toLocal(e) {
9064
+ const rect = this.element.getBoundingClientRect();
9065
+ return { x: e.clientX - rect.left, y: e.clientY - rect.top };
9066
+ }
9067
+ onPointerDown(e) {
9068
+ const local = this.toLocal(e);
9069
+ this.lastPointerScreen = local;
9070
+ const hadPointers = this.downPointers.size > 0;
9071
+ this.downPointers.add(e.pointerId);
9072
+ if (hadPointers) {
9073
+ this.cancelPress();
9074
+ return;
9075
+ }
9076
+ if (!this.longPressEnabled) return;
9077
+ if (e.pointerType === "mouse" && e.button !== 0) return;
9078
+ this.press = {
9079
+ pointerId: e.pointerId,
9080
+ x: local.x,
9081
+ y: local.y,
9082
+ timer: setTimeout(() => this.firePress(), this.longPressMs)
9083
+ };
9084
+ }
9085
+ onPointerMove(e) {
9086
+ const local = this.toLocal(e);
9087
+ this.lastPointerScreen = local;
9088
+ if (this.press === null || e.pointerId !== this.press.pointerId) return;
9089
+ const dx = local.x - this.press.x;
9090
+ const dy = local.y - this.press.y;
9091
+ if (Math.hypot(dx, dy) > this.slopPx) this.cancelPress();
9092
+ }
9093
+ onPointerEnd(e) {
9094
+ this.downPointers.delete(e.pointerId);
9095
+ if (this.press !== null && e.pointerId === this.press.pointerId) this.cancelPress();
9096
+ }
9097
+ cancelPress() {
9098
+ if (this.press === null) return;
9099
+ clearTimeout(this.press.timer);
9100
+ this.press = null;
9101
+ }
9102
+ firePress() {
9103
+ if (this.press === null) return;
9104
+ const screen = { x: this.press.x, y: this.press.y };
9105
+ this.press = null;
9106
+ this.emit(this.host.screenToWorld(screen));
9107
+ }
9108
+ emit(world) {
9109
+ if (this.shouldPing && !this.shouldPing()) return false;
9110
+ const t = this.now();
9111
+ if (t - this.lastEmitAt < this.minIntervalMs) return false;
9112
+ this.lastEmitAt = t;
9113
+ const emission = {
9114
+ x: world.x,
9115
+ y: world.y,
9116
+ color: this.color,
9117
+ durationMs: this.durationMs,
9118
+ radius: this.radius
9119
+ };
9120
+ for (const listener of this.pingListeners) {
9121
+ try {
9122
+ listener(emission);
9123
+ } catch {
9124
+ }
9125
+ }
9126
+ return true;
9127
+ }
9128
+ };
9129
+
8944
9130
  // src/tools/hand-tool.ts
8945
9131
  var HandTool = class {
8946
9132
  name = "hand";
@@ -9202,7 +9388,7 @@ function erasePoints(points, eraser, radius) {
9202
9388
  }
9203
9389
 
9204
9390
  // src/tools/eraser-tool.ts
9205
- var DEFAULT_RADIUS2 = 20;
9391
+ var DEFAULT_RADIUS3 = 20;
9206
9392
  function makeEraserCursor(radius) {
9207
9393
  const size = radius * 2;
9208
9394
  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>`;
@@ -9215,7 +9401,7 @@ var EraserTool = class {
9215
9401
  cursor;
9216
9402
  mode;
9217
9403
  constructor(options = {}) {
9218
- this.radius = options.radius ?? DEFAULT_RADIUS2;
9404
+ this.radius = options.radius ?? DEFAULT_RADIUS3;
9219
9405
  this.cursor = makeEraserCursor(this.radius);
9220
9406
  this.mode = options.mode ?? "partial";
9221
9407
  }
@@ -11497,7 +11683,7 @@ var TemplateTool = class {
11497
11683
  };
11498
11684
 
11499
11685
  // src/tools/laser-tool.ts
11500
- var DEFAULT_COLOR3 = "#ff3b30";
11686
+ var DEFAULT_COLOR4 = "#ff3b30";
11501
11687
  var DEFAULT_WIDTH2 = 4;
11502
11688
  var DEFAULT_FADE_MS2 = 1200;
11503
11689
  var LaserTool = class {
@@ -11513,7 +11699,7 @@ var LaserTool = class {
11513
11699
  pendingEmission = [];
11514
11700
  constructor(options = {}) {
11515
11701
  this.name = options.name ?? "laser";
11516
- this.color = options.color ?? DEFAULT_COLOR3;
11702
+ this.color = options.color ?? DEFAULT_COLOR4;
11517
11703
  this.width = options.width ?? DEFAULT_WIDTH2;
11518
11704
  this.fadeMs = options.fadeMs ?? DEFAULT_FADE_MS2;
11519
11705
  }
@@ -11641,10 +11827,10 @@ var LaserTool = class {
11641
11827
  };
11642
11828
 
11643
11829
  // src/tools/ping-tool.ts
11644
- var DEFAULT_COLOR4 = "#ff3b30";
11645
- var DEFAULT_DURATION_MS2 = 1800;
11646
- var DEFAULT_RADIUS3 = 48;
11647
- var DEFAULT_MIN_INTERVAL_MS = 300;
11830
+ var DEFAULT_COLOR5 = "#ff3b30";
11831
+ var DEFAULT_DURATION_MS3 = 1800;
11832
+ var DEFAULT_RADIUS4 = 48;
11833
+ var DEFAULT_MIN_INTERVAL_MS2 = 300;
11648
11834
  var PingTool = class {
11649
11835
  name;
11650
11836
  color;
@@ -11658,10 +11844,10 @@ var PingTool = class {
11658
11844
  pingListeners = /* @__PURE__ */ new Set();
11659
11845
  constructor(options = {}) {
11660
11846
  this.name = options.name ?? "ping";
11661
- this.color = options.color ?? DEFAULT_COLOR4;
11662
- this.durationMs = options.durationMs ?? DEFAULT_DURATION_MS2;
11663
- this.radius = options.radius ?? DEFAULT_RADIUS3;
11664
- this.minIntervalMs = options.minIntervalMs ?? DEFAULT_MIN_INTERVAL_MS;
11847
+ this.color = options.color ?? DEFAULT_COLOR5;
11848
+ this.durationMs = options.durationMs ?? DEFAULT_DURATION_MS3;
11849
+ this.radius = options.radius ?? DEFAULT_RADIUS4;
11850
+ this.minIntervalMs = options.minIntervalMs ?? DEFAULT_MIN_INTERVAL_MS2;
11665
11851
  }
11666
11852
  now() {
11667
11853
  return performance.now();
@@ -11761,7 +11947,7 @@ var PingTool = class {
11761
11947
  };
11762
11948
 
11763
11949
  // src/index.ts
11764
- var VERSION = "0.55.0";
11950
+ var VERSION = "0.56.0";
11765
11951
  // Annotate the CommonJS export names for ESM import in node:
11766
11952
  0 && (module.exports = {
11767
11953
  ArrowTool,
@@ -11783,6 +11969,7 @@ var VERSION = "0.55.0";
11783
11969
  NoteTool,
11784
11970
  PING_PRESENCE_KIND,
11785
11971
  PencilTool,
11972
+ PingInput,
11786
11973
  PingTool,
11787
11974
  RemoteLaserOverlay,
11788
11975
  RemotePingOverlay,