@fieldnotes/core 0.54.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.cjs +305 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +145 -3
- package/dist/index.d.ts +145 -3
- package/dist/index.js +300 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -37,8 +37,11 @@ __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
|
+
PingTool: () => PingTool,
|
|
41
43
|
RemoteLaserOverlay: () => RemoteLaserOverlay,
|
|
44
|
+
RemotePingOverlay: () => RemotePingOverlay,
|
|
42
45
|
SelectTool: () => SelectTool,
|
|
43
46
|
ShapeTool: () => ShapeTool,
|
|
44
47
|
TemplateTool: () => TemplateTool,
|
|
@@ -76,12 +79,14 @@ __export(index_exports, {
|
|
|
76
79
|
getHexDistance: () => getHexDistance,
|
|
77
80
|
isLaserTrailPresence: () => isLaserTrailPresence,
|
|
78
81
|
isNearBezier: () => isNearBezier,
|
|
82
|
+
isPingPresence: () => isPingPresence,
|
|
79
83
|
setFontSize: () => setFontSize,
|
|
80
84
|
smartSnap: () => smartSnap,
|
|
81
85
|
snapPoint: () => snapPoint,
|
|
82
86
|
snapToHexCenter: () => snapToHexCenter,
|
|
83
87
|
styleToPatch: () => styleToPatch,
|
|
84
88
|
toLaserTrailPresence: () => toLaserTrailPresence,
|
|
89
|
+
toPingPresence: () => toPingPresence,
|
|
85
90
|
toggleBold: () => toggleBold,
|
|
86
91
|
toggleItalic: () => toggleItalic,
|
|
87
92
|
toggleStrikethrough: () => toggleStrikethrough,
|
|
@@ -8766,6 +8771,176 @@ var RemoteLaserOverlay = class {
|
|
|
8766
8771
|
}
|
|
8767
8772
|
};
|
|
8768
8773
|
|
|
8774
|
+
// src/canvas/ping-pulse.ts
|
|
8775
|
+
var RIPPLE_OFFSETS = [0, 0.25];
|
|
8776
|
+
function easeOutCubic(t) {
|
|
8777
|
+
const inv = 1 - t;
|
|
8778
|
+
return 1 - inv * inv * inv;
|
|
8779
|
+
}
|
|
8780
|
+
function renderPingPulse(ctx, x, y, ageMs, style) {
|
|
8781
|
+
if (ageMs < 0 || ageMs >= style.durationMs) return;
|
|
8782
|
+
ctx.save();
|
|
8783
|
+
ctx.strokeStyle = style.color;
|
|
8784
|
+
ctx.fillStyle = style.color;
|
|
8785
|
+
for (const offset of RIPPLE_OFFSETS) {
|
|
8786
|
+
const rippleSpan = style.durationMs * (1 - offset);
|
|
8787
|
+
const rippleAge = ageMs - style.durationMs * offset;
|
|
8788
|
+
if (rippleAge < 0) continue;
|
|
8789
|
+
const progress = Math.min(1, rippleAge / rippleSpan);
|
|
8790
|
+
const rippleRadius = style.radius * easeOutCubic(progress);
|
|
8791
|
+
if (rippleRadius <= 0) continue;
|
|
8792
|
+
ctx.globalAlpha = Math.max(0, 1 - progress);
|
|
8793
|
+
ctx.lineWidth = Math.max(1.5, style.radius / 12);
|
|
8794
|
+
ctx.beginPath();
|
|
8795
|
+
ctx.arc(x, y, rippleRadius, 0, Math.PI * 2);
|
|
8796
|
+
ctx.stroke();
|
|
8797
|
+
}
|
|
8798
|
+
const dotProgress = ageMs / style.durationMs;
|
|
8799
|
+
ctx.globalAlpha = Math.max(0, 1 - Math.max(0, dotProgress - 0.5) * 2);
|
|
8800
|
+
ctx.beginPath();
|
|
8801
|
+
ctx.arc(x, y, Math.max(2, style.radius / 8), 0, Math.PI * 2);
|
|
8802
|
+
ctx.fill();
|
|
8803
|
+
ctx.restore();
|
|
8804
|
+
}
|
|
8805
|
+
|
|
8806
|
+
// src/canvas/remote-ping-overlay.ts
|
|
8807
|
+
var PING_PRESENCE_KIND = "ping";
|
|
8808
|
+
function isPingPresence(data) {
|
|
8809
|
+
if (typeof data !== "object" || data === null) return false;
|
|
8810
|
+
const payload = data;
|
|
8811
|
+
if (payload.kind !== PING_PRESENCE_KIND) return false;
|
|
8812
|
+
if (typeof payload.x !== "number" || !Number.isFinite(payload.x)) return false;
|
|
8813
|
+
if (typeof payload.y !== "number" || !Number.isFinite(payload.y)) return false;
|
|
8814
|
+
if (payload.color !== void 0 && typeof payload.color !== "string") return false;
|
|
8815
|
+
if (payload.durationMs !== void 0 && !(typeof payload.durationMs === "number" && Number.isFinite(payload.durationMs) && payload.durationMs > 0)) {
|
|
8816
|
+
return false;
|
|
8817
|
+
}
|
|
8818
|
+
if (payload.radius !== void 0 && !(typeof payload.radius === "number" && Number.isFinite(payload.radius) && payload.radius > 0)) {
|
|
8819
|
+
return false;
|
|
8820
|
+
}
|
|
8821
|
+
return true;
|
|
8822
|
+
}
|
|
8823
|
+
function toPingPresence(emission) {
|
|
8824
|
+
return {
|
|
8825
|
+
kind: PING_PRESENCE_KIND,
|
|
8826
|
+
x: emission.x,
|
|
8827
|
+
y: emission.y,
|
|
8828
|
+
color: emission.color,
|
|
8829
|
+
durationMs: emission.durationMs,
|
|
8830
|
+
radius: emission.radius
|
|
8831
|
+
};
|
|
8832
|
+
}
|
|
8833
|
+
var DEFAULT_COLOR2 = "#ff3b30";
|
|
8834
|
+
var DEFAULT_DURATION_MS = 1800;
|
|
8835
|
+
var DEFAULT_RADIUS = 48;
|
|
8836
|
+
var DEFAULT_MAX_PINGS = 8;
|
|
8837
|
+
var RemotePingOverlay = class {
|
|
8838
|
+
host;
|
|
8839
|
+
color;
|
|
8840
|
+
durationMs;
|
|
8841
|
+
radius;
|
|
8842
|
+
maxPingsPerSender;
|
|
8843
|
+
pings = /* @__PURE__ */ new Map();
|
|
8844
|
+
unregister;
|
|
8845
|
+
rafId = null;
|
|
8846
|
+
disposed = false;
|
|
8847
|
+
constructor(host, options = {}) {
|
|
8848
|
+
this.host = host;
|
|
8849
|
+
this.color = options.color ?? DEFAULT_COLOR2;
|
|
8850
|
+
this.durationMs = options.durationMs ?? DEFAULT_DURATION_MS;
|
|
8851
|
+
this.radius = options.radius ?? DEFAULT_RADIUS;
|
|
8852
|
+
this.maxPingsPerSender = options.maxPingsPerSender ?? DEFAULT_MAX_PINGS;
|
|
8853
|
+
this.unregister = host.registerOverlay((ctx) => this.renderPings(ctx));
|
|
8854
|
+
}
|
|
8855
|
+
now() {
|
|
8856
|
+
return performance.now();
|
|
8857
|
+
}
|
|
8858
|
+
/**
|
|
8859
|
+
* Applies a presence payload from `sender` (any opaque per-sender key, e.g.
|
|
8860
|
+
* the envelope `from`). Non-ping or malformed payloads are ignored and
|
|
8861
|
+
* reported as `false`, so hosts can feed every presence frame through.
|
|
8862
|
+
*/
|
|
8863
|
+
apply(sender, data) {
|
|
8864
|
+
if (this.disposed || !isPingPresence(data)) return false;
|
|
8865
|
+
let senderPings = this.pings.get(sender);
|
|
8866
|
+
if (!senderPings) {
|
|
8867
|
+
senderPings = [];
|
|
8868
|
+
this.pings.set(sender, senderPings);
|
|
8869
|
+
}
|
|
8870
|
+
senderPings.push({
|
|
8871
|
+
x: data.x,
|
|
8872
|
+
y: data.y,
|
|
8873
|
+
t: this.now(),
|
|
8874
|
+
color: data.color ?? this.color,
|
|
8875
|
+
durationMs: data.durationMs ?? this.durationMs,
|
|
8876
|
+
radius: data.radius ?? this.radius
|
|
8877
|
+
});
|
|
8878
|
+
const excess = senderPings.length - this.maxPingsPerSender;
|
|
8879
|
+
if (excess > 0) senderPings.splice(0, excess);
|
|
8880
|
+
this.ensureAnimating();
|
|
8881
|
+
this.host.requestRender();
|
|
8882
|
+
return true;
|
|
8883
|
+
}
|
|
8884
|
+
/** Removes a sender's pings immediately (presence-leave/disconnect). */
|
|
8885
|
+
remove(sender) {
|
|
8886
|
+
if (this.pings.delete(sender)) this.host.requestRender();
|
|
8887
|
+
}
|
|
8888
|
+
/** Removes every ping immediately. */
|
|
8889
|
+
clear() {
|
|
8890
|
+
if (this.pings.size === 0) return;
|
|
8891
|
+
this.pings.clear();
|
|
8892
|
+
this.host.requestRender();
|
|
8893
|
+
}
|
|
8894
|
+
/** Number of senders with a live (unexpired) ping. */
|
|
8895
|
+
get activeSenderCount() {
|
|
8896
|
+
return this.pings.size;
|
|
8897
|
+
}
|
|
8898
|
+
/** Unregisters the overlay and stops the animation loop. Idempotent. */
|
|
8899
|
+
dispose() {
|
|
8900
|
+
if (this.disposed) return;
|
|
8901
|
+
this.disposed = true;
|
|
8902
|
+
if (this.rafId !== null) {
|
|
8903
|
+
cancelAnimationFrame(this.rafId);
|
|
8904
|
+
this.rafId = null;
|
|
8905
|
+
}
|
|
8906
|
+
this.pings.clear();
|
|
8907
|
+
this.unregister?.();
|
|
8908
|
+
this.unregister = null;
|
|
8909
|
+
}
|
|
8910
|
+
ensureAnimating() {
|
|
8911
|
+
if (this.rafId === null) {
|
|
8912
|
+
this.rafId = requestAnimationFrame(() => this.tick());
|
|
8913
|
+
}
|
|
8914
|
+
}
|
|
8915
|
+
tick() {
|
|
8916
|
+
if (this.disposed) return;
|
|
8917
|
+
const now = this.now();
|
|
8918
|
+
for (const [sender, senderPings] of this.pings) {
|
|
8919
|
+
const live = senderPings.filter((ping) => now - ping.t < ping.durationMs);
|
|
8920
|
+
if (live.length === 0) {
|
|
8921
|
+
this.pings.delete(sender);
|
|
8922
|
+
} else {
|
|
8923
|
+
this.pings.set(sender, live);
|
|
8924
|
+
}
|
|
8925
|
+
}
|
|
8926
|
+
this.host.requestRender();
|
|
8927
|
+
this.rafId = this.pings.size > 0 ? requestAnimationFrame(() => this.tick()) : null;
|
|
8928
|
+
}
|
|
8929
|
+
renderPings(ctx) {
|
|
8930
|
+
if (this.pings.size === 0) return;
|
|
8931
|
+
const now = this.now();
|
|
8932
|
+
for (const senderPings of this.pings.values()) {
|
|
8933
|
+
for (const ping of senderPings) {
|
|
8934
|
+
renderPingPulse(ctx, ping.x, ping.y, now - ping.t, {
|
|
8935
|
+
color: ping.color,
|
|
8936
|
+
durationMs: ping.durationMs,
|
|
8937
|
+
radius: ping.radius
|
|
8938
|
+
});
|
|
8939
|
+
}
|
|
8940
|
+
}
|
|
8941
|
+
}
|
|
8942
|
+
};
|
|
8943
|
+
|
|
8769
8944
|
// src/tools/hand-tool.ts
|
|
8770
8945
|
var HandTool = class {
|
|
8771
8946
|
name = "hand";
|
|
@@ -9027,7 +9202,7 @@ function erasePoints(points, eraser, radius) {
|
|
|
9027
9202
|
}
|
|
9028
9203
|
|
|
9029
9204
|
// src/tools/eraser-tool.ts
|
|
9030
|
-
var
|
|
9205
|
+
var DEFAULT_RADIUS2 = 20;
|
|
9031
9206
|
function makeEraserCursor(radius) {
|
|
9032
9207
|
const size = radius * 2;
|
|
9033
9208
|
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 +9215,7 @@ var EraserTool = class {
|
|
|
9040
9215
|
cursor;
|
|
9041
9216
|
mode;
|
|
9042
9217
|
constructor(options = {}) {
|
|
9043
|
-
this.radius = options.radius ??
|
|
9218
|
+
this.radius = options.radius ?? DEFAULT_RADIUS2;
|
|
9044
9219
|
this.cursor = makeEraserCursor(this.radius);
|
|
9045
9220
|
this.mode = options.mode ?? "partial";
|
|
9046
9221
|
}
|
|
@@ -11322,7 +11497,7 @@ var TemplateTool = class {
|
|
|
11322
11497
|
};
|
|
11323
11498
|
|
|
11324
11499
|
// src/tools/laser-tool.ts
|
|
11325
|
-
var
|
|
11500
|
+
var DEFAULT_COLOR3 = "#ff3b30";
|
|
11326
11501
|
var DEFAULT_WIDTH2 = 4;
|
|
11327
11502
|
var DEFAULT_FADE_MS2 = 1200;
|
|
11328
11503
|
var LaserTool = class {
|
|
@@ -11338,7 +11513,7 @@ var LaserTool = class {
|
|
|
11338
11513
|
pendingEmission = [];
|
|
11339
11514
|
constructor(options = {}) {
|
|
11340
11515
|
this.name = options.name ?? "laser";
|
|
11341
|
-
this.color = options.color ??
|
|
11516
|
+
this.color = options.color ?? DEFAULT_COLOR3;
|
|
11342
11517
|
this.width = options.width ?? DEFAULT_WIDTH2;
|
|
11343
11518
|
this.fadeMs = options.fadeMs ?? DEFAULT_FADE_MS2;
|
|
11344
11519
|
}
|
|
@@ -11465,8 +11640,128 @@ var LaserTool = class {
|
|
|
11465
11640
|
}
|
|
11466
11641
|
};
|
|
11467
11642
|
|
|
11643
|
+
// 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;
|
|
11648
|
+
var PingTool = class {
|
|
11649
|
+
name;
|
|
11650
|
+
color;
|
|
11651
|
+
durationMs;
|
|
11652
|
+
radius;
|
|
11653
|
+
minIntervalMs;
|
|
11654
|
+
pings = [];
|
|
11655
|
+
lastEmitAt = -Infinity;
|
|
11656
|
+
rafId = null;
|
|
11657
|
+
optionListeners = /* @__PURE__ */ new Set();
|
|
11658
|
+
pingListeners = /* @__PURE__ */ new Set();
|
|
11659
|
+
constructor(options = {}) {
|
|
11660
|
+
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;
|
|
11665
|
+
}
|
|
11666
|
+
now() {
|
|
11667
|
+
return performance.now();
|
|
11668
|
+
}
|
|
11669
|
+
onActivate(ctx) {
|
|
11670
|
+
ctx.setCursor?.("crosshair");
|
|
11671
|
+
}
|
|
11672
|
+
onDeactivate(ctx) {
|
|
11673
|
+
if (this.rafId !== null) {
|
|
11674
|
+
cancelAnimationFrame(this.rafId);
|
|
11675
|
+
this.rafId = null;
|
|
11676
|
+
}
|
|
11677
|
+
this.pings = [];
|
|
11678
|
+
ctx.setCursor?.("default");
|
|
11679
|
+
ctx.requestRender();
|
|
11680
|
+
}
|
|
11681
|
+
getOptions() {
|
|
11682
|
+
return {
|
|
11683
|
+
name: this.name,
|
|
11684
|
+
color: this.color,
|
|
11685
|
+
durationMs: this.durationMs,
|
|
11686
|
+
radius: this.radius,
|
|
11687
|
+
minIntervalMs: this.minIntervalMs
|
|
11688
|
+
};
|
|
11689
|
+
}
|
|
11690
|
+
setOptions(options) {
|
|
11691
|
+
if (options.color !== void 0) this.color = options.color;
|
|
11692
|
+
if (options.durationMs !== void 0) this.durationMs = options.durationMs;
|
|
11693
|
+
if (options.radius !== void 0) this.radius = options.radius;
|
|
11694
|
+
if (options.minIntervalMs !== void 0) this.minIntervalMs = options.minIntervalMs;
|
|
11695
|
+
this.notifyOptionsChange();
|
|
11696
|
+
}
|
|
11697
|
+
onOptionsChange(listener) {
|
|
11698
|
+
this.optionListeners.add(listener);
|
|
11699
|
+
return () => this.optionListeners.delete(listener);
|
|
11700
|
+
}
|
|
11701
|
+
/**
|
|
11702
|
+
* Subscribes to accepted pings. Listeners must not throw; a throwing
|
|
11703
|
+
* listener is isolated so it cannot break the tap handling or other
|
|
11704
|
+
* listeners.
|
|
11705
|
+
*/
|
|
11706
|
+
onPing(listener) {
|
|
11707
|
+
this.pingListeners.add(listener);
|
|
11708
|
+
return () => this.pingListeners.delete(listener);
|
|
11709
|
+
}
|
|
11710
|
+
onPointerDown(state, ctx) {
|
|
11711
|
+
const t = this.now();
|
|
11712
|
+
if (t - this.lastEmitAt < this.minIntervalMs) return;
|
|
11713
|
+
this.lastEmitAt = t;
|
|
11714
|
+
const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
|
|
11715
|
+
this.pings.push({ x: world.x, y: world.y, t });
|
|
11716
|
+
const emission = {
|
|
11717
|
+
x: world.x,
|
|
11718
|
+
y: world.y,
|
|
11719
|
+
color: this.color,
|
|
11720
|
+
durationMs: this.durationMs,
|
|
11721
|
+
radius: this.radius
|
|
11722
|
+
};
|
|
11723
|
+
for (const listener of this.pingListeners) {
|
|
11724
|
+
try {
|
|
11725
|
+
listener(emission);
|
|
11726
|
+
} catch {
|
|
11727
|
+
}
|
|
11728
|
+
}
|
|
11729
|
+
this.ensureAnimating(ctx);
|
|
11730
|
+
ctx.requestRender();
|
|
11731
|
+
}
|
|
11732
|
+
onPointerMove(_state, _ctx) {
|
|
11733
|
+
}
|
|
11734
|
+
onPointerUp(_state, _ctx) {
|
|
11735
|
+
}
|
|
11736
|
+
renderOverlay(ctx) {
|
|
11737
|
+
if (this.pings.length === 0) return;
|
|
11738
|
+
const now = this.now();
|
|
11739
|
+
for (const ping of this.pings) {
|
|
11740
|
+
renderPingPulse(ctx, ping.x, ping.y, now - ping.t, {
|
|
11741
|
+
color: this.color,
|
|
11742
|
+
durationMs: this.durationMs,
|
|
11743
|
+
radius: this.radius
|
|
11744
|
+
});
|
|
11745
|
+
}
|
|
11746
|
+
}
|
|
11747
|
+
ensureAnimating(ctx) {
|
|
11748
|
+
if (this.rafId === null) {
|
|
11749
|
+
this.rafId = requestAnimationFrame(() => this.tick(ctx));
|
|
11750
|
+
}
|
|
11751
|
+
}
|
|
11752
|
+
tick(ctx) {
|
|
11753
|
+
const cutoff = this.now() - this.durationMs;
|
|
11754
|
+
this.pings = this.pings.filter((ping) => ping.t > cutoff);
|
|
11755
|
+
ctx.requestRender();
|
|
11756
|
+
this.rafId = this.pings.length > 0 ? requestAnimationFrame(() => this.tick(ctx)) : null;
|
|
11757
|
+
}
|
|
11758
|
+
notifyOptionsChange() {
|
|
11759
|
+
for (const listener of this.optionListeners) listener();
|
|
11760
|
+
}
|
|
11761
|
+
};
|
|
11762
|
+
|
|
11468
11763
|
// src/index.ts
|
|
11469
|
-
var VERSION = "0.
|
|
11764
|
+
var VERSION = "0.55.0";
|
|
11470
11765
|
// Annotate the CommonJS export names for ESM import in node:
|
|
11471
11766
|
0 && (module.exports = {
|
|
11472
11767
|
ArrowTool,
|
|
@@ -11486,8 +11781,11 @@ var VERSION = "0.54.0";
|
|
|
11486
11781
|
MeasureTool,
|
|
11487
11782
|
MemoryAdapter,
|
|
11488
11783
|
NoteTool,
|
|
11784
|
+
PING_PRESENCE_KIND,
|
|
11489
11785
|
PencilTool,
|
|
11786
|
+
PingTool,
|
|
11490
11787
|
RemoteLaserOverlay,
|
|
11788
|
+
RemotePingOverlay,
|
|
11491
11789
|
SelectTool,
|
|
11492
11790
|
ShapeTool,
|
|
11493
11791
|
TemplateTool,
|
|
@@ -11525,12 +11823,14 @@ var VERSION = "0.54.0";
|
|
|
11525
11823
|
getHexDistance,
|
|
11526
11824
|
isLaserTrailPresence,
|
|
11527
11825
|
isNearBezier,
|
|
11826
|
+
isPingPresence,
|
|
11528
11827
|
setFontSize,
|
|
11529
11828
|
smartSnap,
|
|
11530
11829
|
snapPoint,
|
|
11531
11830
|
snapToHexCenter,
|
|
11532
11831
|
styleToPatch,
|
|
11533
11832
|
toLaserTrailPresence,
|
|
11833
|
+
toPingPresence,
|
|
11534
11834
|
toggleBold,
|
|
11535
11835
|
toggleItalic,
|
|
11536
11836
|
toggleStrikethrough,
|