@fieldnotes/core 0.54.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 +492 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +259 -3
- package/dist/index.d.ts +259 -3
- package/dist/index.js +486 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -37,8 +37,12 @@ __export(index_exports, {
|
|
|
37
37
|
MeasureTool: () => MeasureTool,
|
|
38
38
|
MemoryAdapter: () => MemoryAdapter,
|
|
39
39
|
NoteTool: () => NoteTool,
|
|
40
|
+
PING_PRESENCE_KIND: () => PING_PRESENCE_KIND,
|
|
40
41
|
PencilTool: () => PencilTool,
|
|
42
|
+
PingInput: () => PingInput,
|
|
43
|
+
PingTool: () => PingTool,
|
|
41
44
|
RemoteLaserOverlay: () => RemoteLaserOverlay,
|
|
45
|
+
RemotePingOverlay: () => RemotePingOverlay,
|
|
42
46
|
SelectTool: () => SelectTool,
|
|
43
47
|
ShapeTool: () => ShapeTool,
|
|
44
48
|
TemplateTool: () => TemplateTool,
|
|
@@ -76,12 +80,14 @@ __export(index_exports, {
|
|
|
76
80
|
getHexDistance: () => getHexDistance,
|
|
77
81
|
isLaserTrailPresence: () => isLaserTrailPresence,
|
|
78
82
|
isNearBezier: () => isNearBezier,
|
|
83
|
+
isPingPresence: () => isPingPresence,
|
|
79
84
|
setFontSize: () => setFontSize,
|
|
80
85
|
smartSnap: () => smartSnap,
|
|
81
86
|
snapPoint: () => snapPoint,
|
|
82
87
|
snapToHexCenter: () => snapToHexCenter,
|
|
83
88
|
styleToPatch: () => styleToPatch,
|
|
84
89
|
toLaserTrailPresence: () => toLaserTrailPresence,
|
|
90
|
+
toPingPresence: () => toPingPresence,
|
|
85
91
|
toggleBold: () => toggleBold,
|
|
86
92
|
toggleItalic: () => toggleItalic,
|
|
87
93
|
toggleStrikethrough: () => toggleStrikethrough,
|
|
@@ -8766,6 +8772,361 @@ var RemoteLaserOverlay = class {
|
|
|
8766
8772
|
}
|
|
8767
8773
|
};
|
|
8768
8774
|
|
|
8775
|
+
// src/canvas/ping-pulse.ts
|
|
8776
|
+
var RIPPLE_OFFSETS = [0, 0.25];
|
|
8777
|
+
function easeOutCubic(t) {
|
|
8778
|
+
const inv = 1 - t;
|
|
8779
|
+
return 1 - inv * inv * inv;
|
|
8780
|
+
}
|
|
8781
|
+
function renderPingPulse(ctx, x, y, ageMs, style) {
|
|
8782
|
+
if (ageMs < 0 || ageMs >= style.durationMs) return;
|
|
8783
|
+
ctx.save();
|
|
8784
|
+
ctx.strokeStyle = style.color;
|
|
8785
|
+
ctx.fillStyle = style.color;
|
|
8786
|
+
for (const offset of RIPPLE_OFFSETS) {
|
|
8787
|
+
const rippleSpan = style.durationMs * (1 - offset);
|
|
8788
|
+
const rippleAge = ageMs - style.durationMs * offset;
|
|
8789
|
+
if (rippleAge < 0) continue;
|
|
8790
|
+
const progress = Math.min(1, rippleAge / rippleSpan);
|
|
8791
|
+
const rippleRadius = style.radius * easeOutCubic(progress);
|
|
8792
|
+
if (rippleRadius <= 0) continue;
|
|
8793
|
+
ctx.globalAlpha = Math.max(0, 1 - progress);
|
|
8794
|
+
ctx.lineWidth = Math.max(1.5, style.radius / 12);
|
|
8795
|
+
ctx.beginPath();
|
|
8796
|
+
ctx.arc(x, y, rippleRadius, 0, Math.PI * 2);
|
|
8797
|
+
ctx.stroke();
|
|
8798
|
+
}
|
|
8799
|
+
const dotProgress = ageMs / style.durationMs;
|
|
8800
|
+
ctx.globalAlpha = Math.max(0, 1 - Math.max(0, dotProgress - 0.5) * 2);
|
|
8801
|
+
ctx.beginPath();
|
|
8802
|
+
ctx.arc(x, y, Math.max(2, style.radius / 8), 0, Math.PI * 2);
|
|
8803
|
+
ctx.fill();
|
|
8804
|
+
ctx.restore();
|
|
8805
|
+
}
|
|
8806
|
+
|
|
8807
|
+
// src/canvas/remote-ping-overlay.ts
|
|
8808
|
+
var PING_PRESENCE_KIND = "ping";
|
|
8809
|
+
function isPingPresence(data) {
|
|
8810
|
+
if (typeof data !== "object" || data === null) return false;
|
|
8811
|
+
const payload = data;
|
|
8812
|
+
if (payload.kind !== PING_PRESENCE_KIND) return false;
|
|
8813
|
+
if (typeof payload.x !== "number" || !Number.isFinite(payload.x)) return false;
|
|
8814
|
+
if (typeof payload.y !== "number" || !Number.isFinite(payload.y)) return false;
|
|
8815
|
+
if (payload.color !== void 0 && typeof payload.color !== "string") return false;
|
|
8816
|
+
if (payload.durationMs !== void 0 && !(typeof payload.durationMs === "number" && Number.isFinite(payload.durationMs) && payload.durationMs > 0)) {
|
|
8817
|
+
return false;
|
|
8818
|
+
}
|
|
8819
|
+
if (payload.radius !== void 0 && !(typeof payload.radius === "number" && Number.isFinite(payload.radius) && payload.radius > 0)) {
|
|
8820
|
+
return false;
|
|
8821
|
+
}
|
|
8822
|
+
return true;
|
|
8823
|
+
}
|
|
8824
|
+
function toPingPresence(emission) {
|
|
8825
|
+
return {
|
|
8826
|
+
kind: PING_PRESENCE_KIND,
|
|
8827
|
+
x: emission.x,
|
|
8828
|
+
y: emission.y,
|
|
8829
|
+
color: emission.color,
|
|
8830
|
+
durationMs: emission.durationMs,
|
|
8831
|
+
radius: emission.radius
|
|
8832
|
+
};
|
|
8833
|
+
}
|
|
8834
|
+
var DEFAULT_COLOR2 = "#ff3b30";
|
|
8835
|
+
var DEFAULT_DURATION_MS = 1800;
|
|
8836
|
+
var DEFAULT_RADIUS = 48;
|
|
8837
|
+
var DEFAULT_MAX_PINGS = 8;
|
|
8838
|
+
var RemotePingOverlay = class {
|
|
8839
|
+
host;
|
|
8840
|
+
color;
|
|
8841
|
+
durationMs;
|
|
8842
|
+
radius;
|
|
8843
|
+
maxPingsPerSender;
|
|
8844
|
+
pings = /* @__PURE__ */ new Map();
|
|
8845
|
+
unregister;
|
|
8846
|
+
rafId = null;
|
|
8847
|
+
disposed = false;
|
|
8848
|
+
constructor(host, options = {}) {
|
|
8849
|
+
this.host = host;
|
|
8850
|
+
this.color = options.color ?? DEFAULT_COLOR2;
|
|
8851
|
+
this.durationMs = options.durationMs ?? DEFAULT_DURATION_MS;
|
|
8852
|
+
this.radius = options.radius ?? DEFAULT_RADIUS;
|
|
8853
|
+
this.maxPingsPerSender = options.maxPingsPerSender ?? DEFAULT_MAX_PINGS;
|
|
8854
|
+
this.unregister = host.registerOverlay((ctx) => this.renderPings(ctx));
|
|
8855
|
+
}
|
|
8856
|
+
now() {
|
|
8857
|
+
return performance.now();
|
|
8858
|
+
}
|
|
8859
|
+
/**
|
|
8860
|
+
* Applies a presence payload from `sender` (any opaque per-sender key, e.g.
|
|
8861
|
+
* the envelope `from`). Non-ping or malformed payloads are ignored and
|
|
8862
|
+
* reported as `false`, so hosts can feed every presence frame through.
|
|
8863
|
+
*/
|
|
8864
|
+
apply(sender, data) {
|
|
8865
|
+
if (this.disposed || !isPingPresence(data)) return false;
|
|
8866
|
+
let senderPings = this.pings.get(sender);
|
|
8867
|
+
if (!senderPings) {
|
|
8868
|
+
senderPings = [];
|
|
8869
|
+
this.pings.set(sender, senderPings);
|
|
8870
|
+
}
|
|
8871
|
+
senderPings.push({
|
|
8872
|
+
x: data.x,
|
|
8873
|
+
y: data.y,
|
|
8874
|
+
t: this.now(),
|
|
8875
|
+
color: data.color ?? this.color,
|
|
8876
|
+
durationMs: data.durationMs ?? this.durationMs,
|
|
8877
|
+
radius: data.radius ?? this.radius
|
|
8878
|
+
});
|
|
8879
|
+
const excess = senderPings.length - this.maxPingsPerSender;
|
|
8880
|
+
if (excess > 0) senderPings.splice(0, excess);
|
|
8881
|
+
this.ensureAnimating();
|
|
8882
|
+
this.host.requestRender();
|
|
8883
|
+
return true;
|
|
8884
|
+
}
|
|
8885
|
+
/** Removes a sender's pings immediately (presence-leave/disconnect). */
|
|
8886
|
+
remove(sender) {
|
|
8887
|
+
if (this.pings.delete(sender)) this.host.requestRender();
|
|
8888
|
+
}
|
|
8889
|
+
/** Removes every ping immediately. */
|
|
8890
|
+
clear() {
|
|
8891
|
+
if (this.pings.size === 0) return;
|
|
8892
|
+
this.pings.clear();
|
|
8893
|
+
this.host.requestRender();
|
|
8894
|
+
}
|
|
8895
|
+
/** Number of senders with a live (unexpired) ping. */
|
|
8896
|
+
get activeSenderCount() {
|
|
8897
|
+
return this.pings.size;
|
|
8898
|
+
}
|
|
8899
|
+
/** Unregisters the overlay and stops the animation loop. Idempotent. */
|
|
8900
|
+
dispose() {
|
|
8901
|
+
if (this.disposed) return;
|
|
8902
|
+
this.disposed = true;
|
|
8903
|
+
if (this.rafId !== null) {
|
|
8904
|
+
cancelAnimationFrame(this.rafId);
|
|
8905
|
+
this.rafId = null;
|
|
8906
|
+
}
|
|
8907
|
+
this.pings.clear();
|
|
8908
|
+
this.unregister?.();
|
|
8909
|
+
this.unregister = null;
|
|
8910
|
+
}
|
|
8911
|
+
ensureAnimating() {
|
|
8912
|
+
if (this.rafId === null) {
|
|
8913
|
+
this.rafId = requestAnimationFrame(() => this.tick());
|
|
8914
|
+
}
|
|
8915
|
+
}
|
|
8916
|
+
tick() {
|
|
8917
|
+
if (this.disposed) return;
|
|
8918
|
+
const now = this.now();
|
|
8919
|
+
for (const [sender, senderPings] of this.pings) {
|
|
8920
|
+
const live = senderPings.filter((ping) => now - ping.t < ping.durationMs);
|
|
8921
|
+
if (live.length === 0) {
|
|
8922
|
+
this.pings.delete(sender);
|
|
8923
|
+
} else {
|
|
8924
|
+
this.pings.set(sender, live);
|
|
8925
|
+
}
|
|
8926
|
+
}
|
|
8927
|
+
this.host.requestRender();
|
|
8928
|
+
this.rafId = this.pings.size > 0 ? requestAnimationFrame(() => this.tick()) : null;
|
|
8929
|
+
}
|
|
8930
|
+
renderPings(ctx) {
|
|
8931
|
+
if (this.pings.size === 0) return;
|
|
8932
|
+
const now = this.now();
|
|
8933
|
+
for (const senderPings of this.pings.values()) {
|
|
8934
|
+
for (const ping of senderPings) {
|
|
8935
|
+
renderPingPulse(ctx, ping.x, ping.y, now - ping.t, {
|
|
8936
|
+
color: ping.color,
|
|
8937
|
+
durationMs: ping.durationMs,
|
|
8938
|
+
radius: ping.radius
|
|
8939
|
+
});
|
|
8940
|
+
}
|
|
8941
|
+
}
|
|
8942
|
+
}
|
|
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
|
+
|
|
8769
9130
|
// src/tools/hand-tool.ts
|
|
8770
9131
|
var HandTool = class {
|
|
8771
9132
|
name = "hand";
|
|
@@ -9027,7 +9388,7 @@ function erasePoints(points, eraser, radius) {
|
|
|
9027
9388
|
}
|
|
9028
9389
|
|
|
9029
9390
|
// src/tools/eraser-tool.ts
|
|
9030
|
-
var
|
|
9391
|
+
var DEFAULT_RADIUS3 = 20;
|
|
9031
9392
|
function makeEraserCursor(radius) {
|
|
9032
9393
|
const size = radius * 2;
|
|
9033
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>`;
|
|
@@ -9040,7 +9401,7 @@ var EraserTool = class {
|
|
|
9040
9401
|
cursor;
|
|
9041
9402
|
mode;
|
|
9042
9403
|
constructor(options = {}) {
|
|
9043
|
-
this.radius = options.radius ??
|
|
9404
|
+
this.radius = options.radius ?? DEFAULT_RADIUS3;
|
|
9044
9405
|
this.cursor = makeEraserCursor(this.radius);
|
|
9045
9406
|
this.mode = options.mode ?? "partial";
|
|
9046
9407
|
}
|
|
@@ -11322,7 +11683,7 @@ var TemplateTool = class {
|
|
|
11322
11683
|
};
|
|
11323
11684
|
|
|
11324
11685
|
// src/tools/laser-tool.ts
|
|
11325
|
-
var
|
|
11686
|
+
var DEFAULT_COLOR4 = "#ff3b30";
|
|
11326
11687
|
var DEFAULT_WIDTH2 = 4;
|
|
11327
11688
|
var DEFAULT_FADE_MS2 = 1200;
|
|
11328
11689
|
var LaserTool = class {
|
|
@@ -11338,7 +11699,7 @@ var LaserTool = class {
|
|
|
11338
11699
|
pendingEmission = [];
|
|
11339
11700
|
constructor(options = {}) {
|
|
11340
11701
|
this.name = options.name ?? "laser";
|
|
11341
|
-
this.color = options.color ??
|
|
11702
|
+
this.color = options.color ?? DEFAULT_COLOR4;
|
|
11342
11703
|
this.width = options.width ?? DEFAULT_WIDTH2;
|
|
11343
11704
|
this.fadeMs = options.fadeMs ?? DEFAULT_FADE_MS2;
|
|
11344
11705
|
}
|
|
@@ -11465,8 +11826,128 @@ var LaserTool = class {
|
|
|
11465
11826
|
}
|
|
11466
11827
|
};
|
|
11467
11828
|
|
|
11829
|
+
// src/tools/ping-tool.ts
|
|
11830
|
+
var DEFAULT_COLOR5 = "#ff3b30";
|
|
11831
|
+
var DEFAULT_DURATION_MS3 = 1800;
|
|
11832
|
+
var DEFAULT_RADIUS4 = 48;
|
|
11833
|
+
var DEFAULT_MIN_INTERVAL_MS2 = 300;
|
|
11834
|
+
var PingTool = class {
|
|
11835
|
+
name;
|
|
11836
|
+
color;
|
|
11837
|
+
durationMs;
|
|
11838
|
+
radius;
|
|
11839
|
+
minIntervalMs;
|
|
11840
|
+
pings = [];
|
|
11841
|
+
lastEmitAt = -Infinity;
|
|
11842
|
+
rafId = null;
|
|
11843
|
+
optionListeners = /* @__PURE__ */ new Set();
|
|
11844
|
+
pingListeners = /* @__PURE__ */ new Set();
|
|
11845
|
+
constructor(options = {}) {
|
|
11846
|
+
this.name = options.name ?? "ping";
|
|
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;
|
|
11851
|
+
}
|
|
11852
|
+
now() {
|
|
11853
|
+
return performance.now();
|
|
11854
|
+
}
|
|
11855
|
+
onActivate(ctx) {
|
|
11856
|
+
ctx.setCursor?.("crosshair");
|
|
11857
|
+
}
|
|
11858
|
+
onDeactivate(ctx) {
|
|
11859
|
+
if (this.rafId !== null) {
|
|
11860
|
+
cancelAnimationFrame(this.rafId);
|
|
11861
|
+
this.rafId = null;
|
|
11862
|
+
}
|
|
11863
|
+
this.pings = [];
|
|
11864
|
+
ctx.setCursor?.("default");
|
|
11865
|
+
ctx.requestRender();
|
|
11866
|
+
}
|
|
11867
|
+
getOptions() {
|
|
11868
|
+
return {
|
|
11869
|
+
name: this.name,
|
|
11870
|
+
color: this.color,
|
|
11871
|
+
durationMs: this.durationMs,
|
|
11872
|
+
radius: this.radius,
|
|
11873
|
+
minIntervalMs: this.minIntervalMs
|
|
11874
|
+
};
|
|
11875
|
+
}
|
|
11876
|
+
setOptions(options) {
|
|
11877
|
+
if (options.color !== void 0) this.color = options.color;
|
|
11878
|
+
if (options.durationMs !== void 0) this.durationMs = options.durationMs;
|
|
11879
|
+
if (options.radius !== void 0) this.radius = options.radius;
|
|
11880
|
+
if (options.minIntervalMs !== void 0) this.minIntervalMs = options.minIntervalMs;
|
|
11881
|
+
this.notifyOptionsChange();
|
|
11882
|
+
}
|
|
11883
|
+
onOptionsChange(listener) {
|
|
11884
|
+
this.optionListeners.add(listener);
|
|
11885
|
+
return () => this.optionListeners.delete(listener);
|
|
11886
|
+
}
|
|
11887
|
+
/**
|
|
11888
|
+
* Subscribes to accepted pings. Listeners must not throw; a throwing
|
|
11889
|
+
* listener is isolated so it cannot break the tap handling or other
|
|
11890
|
+
* listeners.
|
|
11891
|
+
*/
|
|
11892
|
+
onPing(listener) {
|
|
11893
|
+
this.pingListeners.add(listener);
|
|
11894
|
+
return () => this.pingListeners.delete(listener);
|
|
11895
|
+
}
|
|
11896
|
+
onPointerDown(state, ctx) {
|
|
11897
|
+
const t = this.now();
|
|
11898
|
+
if (t - this.lastEmitAt < this.minIntervalMs) return;
|
|
11899
|
+
this.lastEmitAt = t;
|
|
11900
|
+
const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
|
|
11901
|
+
this.pings.push({ x: world.x, y: world.y, t });
|
|
11902
|
+
const emission = {
|
|
11903
|
+
x: world.x,
|
|
11904
|
+
y: world.y,
|
|
11905
|
+
color: this.color,
|
|
11906
|
+
durationMs: this.durationMs,
|
|
11907
|
+
radius: this.radius
|
|
11908
|
+
};
|
|
11909
|
+
for (const listener of this.pingListeners) {
|
|
11910
|
+
try {
|
|
11911
|
+
listener(emission);
|
|
11912
|
+
} catch {
|
|
11913
|
+
}
|
|
11914
|
+
}
|
|
11915
|
+
this.ensureAnimating(ctx);
|
|
11916
|
+
ctx.requestRender();
|
|
11917
|
+
}
|
|
11918
|
+
onPointerMove(_state, _ctx) {
|
|
11919
|
+
}
|
|
11920
|
+
onPointerUp(_state, _ctx) {
|
|
11921
|
+
}
|
|
11922
|
+
renderOverlay(ctx) {
|
|
11923
|
+
if (this.pings.length === 0) return;
|
|
11924
|
+
const now = this.now();
|
|
11925
|
+
for (const ping of this.pings) {
|
|
11926
|
+
renderPingPulse(ctx, ping.x, ping.y, now - ping.t, {
|
|
11927
|
+
color: this.color,
|
|
11928
|
+
durationMs: this.durationMs,
|
|
11929
|
+
radius: this.radius
|
|
11930
|
+
});
|
|
11931
|
+
}
|
|
11932
|
+
}
|
|
11933
|
+
ensureAnimating(ctx) {
|
|
11934
|
+
if (this.rafId === null) {
|
|
11935
|
+
this.rafId = requestAnimationFrame(() => this.tick(ctx));
|
|
11936
|
+
}
|
|
11937
|
+
}
|
|
11938
|
+
tick(ctx) {
|
|
11939
|
+
const cutoff = this.now() - this.durationMs;
|
|
11940
|
+
this.pings = this.pings.filter((ping) => ping.t > cutoff);
|
|
11941
|
+
ctx.requestRender();
|
|
11942
|
+
this.rafId = this.pings.length > 0 ? requestAnimationFrame(() => this.tick(ctx)) : null;
|
|
11943
|
+
}
|
|
11944
|
+
notifyOptionsChange() {
|
|
11945
|
+
for (const listener of this.optionListeners) listener();
|
|
11946
|
+
}
|
|
11947
|
+
};
|
|
11948
|
+
|
|
11468
11949
|
// src/index.ts
|
|
11469
|
-
var VERSION = "0.
|
|
11950
|
+
var VERSION = "0.56.0";
|
|
11470
11951
|
// Annotate the CommonJS export names for ESM import in node:
|
|
11471
11952
|
0 && (module.exports = {
|
|
11472
11953
|
ArrowTool,
|
|
@@ -11486,8 +11967,12 @@ var VERSION = "0.54.0";
|
|
|
11486
11967
|
MeasureTool,
|
|
11487
11968
|
MemoryAdapter,
|
|
11488
11969
|
NoteTool,
|
|
11970
|
+
PING_PRESENCE_KIND,
|
|
11489
11971
|
PencilTool,
|
|
11972
|
+
PingInput,
|
|
11973
|
+
PingTool,
|
|
11490
11974
|
RemoteLaserOverlay,
|
|
11975
|
+
RemotePingOverlay,
|
|
11491
11976
|
SelectTool,
|
|
11492
11977
|
ShapeTool,
|
|
11493
11978
|
TemplateTool,
|
|
@@ -11525,12 +12010,14 @@ var VERSION = "0.54.0";
|
|
|
11525
12010
|
getHexDistance,
|
|
11526
12011
|
isLaserTrailPresence,
|
|
11527
12012
|
isNearBezier,
|
|
12013
|
+
isPingPresence,
|
|
11528
12014
|
setFontSize,
|
|
11529
12015
|
smartSnap,
|
|
11530
12016
|
snapPoint,
|
|
11531
12017
|
snapToHexCenter,
|
|
11532
12018
|
styleToPatch,
|
|
11533
12019
|
toLaserTrailPresence,
|
|
12020
|
+
toPingPresence,
|
|
11534
12021
|
toggleBold,
|
|
11535
12022
|
toggleItalic,
|
|
11536
12023
|
toggleStrikethrough,
|