@fieldnotes/core 0.60.0 → 0.61.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 +390 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +209 -2
- package/dist/index.d.ts +209 -2
- package/dist/index.js +381 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -23,9 +23,11 @@ __export(index_exports, {
|
|
|
23
23
|
ArrowTool: () => ArrowTool,
|
|
24
24
|
AutoSave: () => AutoSave,
|
|
25
25
|
Camera: () => Camera,
|
|
26
|
+
CameraAnimator: () => CameraAnimator,
|
|
26
27
|
DEFAULT_NOTE_FONT_SIZE: () => DEFAULT_NOTE_FONT_SIZE,
|
|
27
28
|
ElementStore: () => ElementStore,
|
|
28
29
|
EraserTool: () => EraserTool,
|
|
30
|
+
FOCUS_PRESENCE_KIND: () => FOCUS_PRESENCE_KIND,
|
|
29
31
|
HandTool: () => HandTool,
|
|
30
32
|
HistoryStack: () => HistoryStack,
|
|
31
33
|
ImageTool: () => ImageTool,
|
|
@@ -43,6 +45,7 @@ __export(index_exports, {
|
|
|
43
45
|
PencilTool: () => PencilTool,
|
|
44
46
|
PingInput: () => PingInput,
|
|
45
47
|
PingTool: () => PingTool,
|
|
48
|
+
RemoteFocusReceiver: () => RemoteFocusReceiver,
|
|
46
49
|
RemoteLaserOverlay: () => RemoteLaserOverlay,
|
|
47
50
|
RemoteMeasureOverlay: () => RemoteMeasureOverlay,
|
|
48
51
|
RemotePingOverlay: () => RemotePingOverlay,
|
|
@@ -53,7 +56,10 @@ __export(index_exports, {
|
|
|
53
56
|
ToolManager: () => ToolManager,
|
|
54
57
|
VERSION: () => VERSION,
|
|
55
58
|
Viewport: () => Viewport,
|
|
59
|
+
applyCameraView: () => applyCameraView,
|
|
56
60
|
boundsIntersect: () => boundsIntersect,
|
|
61
|
+
cameraOriginForView: () => cameraOriginForView,
|
|
62
|
+
captureCameraView: () => captureCameraView,
|
|
57
63
|
createArrow: () => createArrow,
|
|
58
64
|
createGrid: () => createGrid,
|
|
59
65
|
createHtmlElement: () => createHtmlElement,
|
|
@@ -66,6 +72,7 @@ __export(index_exports, {
|
|
|
66
72
|
drawHexPath: () => drawHexPath,
|
|
67
73
|
exportImage: () => exportImage,
|
|
68
74
|
exportSvg: () => exportSvg,
|
|
75
|
+
fitZoomForView: () => fitZoomForView,
|
|
69
76
|
getActiveFormats: () => getActiveFormats,
|
|
70
77
|
getArrowBounds: () => getArrowBounds,
|
|
71
78
|
getArrowControlPoint: () => getArrowControlPoint,
|
|
@@ -81,6 +88,7 @@ __export(index_exports, {
|
|
|
81
88
|
getHexCellsInRectangle: () => getHexCellsInRectangle,
|
|
82
89
|
getHexCellsInSquare: () => getHexCellsInSquare,
|
|
83
90
|
getHexDistance: () => getHexDistance,
|
|
91
|
+
isFocusPresence: () => isFocusPresence,
|
|
84
92
|
isLaserTrailPresence: () => isLaserTrailPresence,
|
|
85
93
|
isMeasurePresence: () => isMeasurePresence,
|
|
86
94
|
isNearBezier: () => isNearBezier,
|
|
@@ -90,6 +98,7 @@ __export(index_exports, {
|
|
|
90
98
|
snapPoint: () => snapPoint,
|
|
91
99
|
snapToHexCenter: () => snapToHexCenter,
|
|
92
100
|
styleToPatch: () => styleToPatch,
|
|
101
|
+
toFocusPresence: () => toFocusPresence,
|
|
93
102
|
toLaserTrailPresence: () => toLaserTrailPresence,
|
|
94
103
|
toMeasurePresence: () => toMeasurePresence,
|
|
95
104
|
toPingPresence: () => toPingPresence,
|
|
@@ -8586,6 +8595,17 @@ var Viewport = class _Viewport {
|
|
|
8586
8595
|
getVisibleRect() {
|
|
8587
8596
|
return this.camera.getVisibleRect(this.canvasEl.clientWidth, this.canvasEl.clientHeight);
|
|
8588
8597
|
}
|
|
8598
|
+
/**
|
|
8599
|
+
* Size in CSS pixels of the canvas that `getVisibleRect()` measures.
|
|
8600
|
+
* Exposed because `canvasEl` is private: consumers can only reach the
|
|
8601
|
+
* wrapper (via `domLayer.parentElement`), so without this accessor the
|
|
8602
|
+
* canonical size behind `getVisibleRect()` is unreachable and callers
|
|
8603
|
+
* resort to `getVisibleRect().w * camera.zoom`. Capture and restore must
|
|
8604
|
+
* measure the same element or saved views do not round-trip.
|
|
8605
|
+
*/
|
|
8606
|
+
getCanvasSize() {
|
|
8607
|
+
return { w: this.canvasEl.clientWidth, h: this.canvasEl.clientHeight };
|
|
8608
|
+
}
|
|
8589
8609
|
/** Centers the camera on a world point without changing zoom. */
|
|
8590
8610
|
centerCameraAt(world) {
|
|
8591
8611
|
const z = this.camera.zoom;
|
|
@@ -9734,6 +9754,361 @@ var PingInput = class {
|
|
|
9734
9754
|
}
|
|
9735
9755
|
};
|
|
9736
9756
|
|
|
9757
|
+
// src/canvas/camera-view.ts
|
|
9758
|
+
function assertValidView(view) {
|
|
9759
|
+
const { x, y, w, h } = view;
|
|
9760
|
+
if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(w) || !Number.isFinite(h)) {
|
|
9761
|
+
throw new Error("[fieldnotes] CameraView requires finite x, y, w, and h");
|
|
9762
|
+
}
|
|
9763
|
+
if (w <= 0 || h <= 0) {
|
|
9764
|
+
throw new Error("[fieldnotes] CameraView requires positive w and h");
|
|
9765
|
+
}
|
|
9766
|
+
}
|
|
9767
|
+
function assertCanvasDims(canvasW, canvasH) {
|
|
9768
|
+
if (!Number.isFinite(canvasW) || !Number.isFinite(canvasH)) {
|
|
9769
|
+
throw new Error("[fieldnotes] canvas dimensions must be finite");
|
|
9770
|
+
}
|
|
9771
|
+
if (canvasW < 0 || canvasH < 0) {
|
|
9772
|
+
throw new Error("[fieldnotes] canvas dimensions must not be negative");
|
|
9773
|
+
}
|
|
9774
|
+
}
|
|
9775
|
+
function canvasDimsUsable(canvasW, canvasH) {
|
|
9776
|
+
return Number.isFinite(canvasW) && Number.isFinite(canvasH) && canvasW >= 0 && canvasH >= 0;
|
|
9777
|
+
}
|
|
9778
|
+
function captureCameraView(viewport) {
|
|
9779
|
+
const rect = viewport.getVisibleRect();
|
|
9780
|
+
return { x: rect.x, y: rect.y, w: rect.w, h: rect.h };
|
|
9781
|
+
}
|
|
9782
|
+
function fitZoomForView(view, canvasW, canvasH) {
|
|
9783
|
+
assertValidView(view);
|
|
9784
|
+
assertCanvasDims(canvasW, canvasH);
|
|
9785
|
+
if (canvasW === 0 || canvasH === 0) {
|
|
9786
|
+
throw new Error("[fieldnotes] fitZoomForView requires a non-zero canvas size");
|
|
9787
|
+
}
|
|
9788
|
+
return Math.min(canvasW / view.w, canvasH / view.h);
|
|
9789
|
+
}
|
|
9790
|
+
function cameraOriginForView(view, zoom, canvasW, canvasH) {
|
|
9791
|
+
return {
|
|
9792
|
+
x: canvasW / 2 - (view.x + view.w / 2) * zoom,
|
|
9793
|
+
y: canvasH / 2 - (view.y + view.h / 2) * zoom
|
|
9794
|
+
};
|
|
9795
|
+
}
|
|
9796
|
+
function applyCameraView(camera, view, canvasW, canvasH) {
|
|
9797
|
+
assertValidView(view);
|
|
9798
|
+
assertCanvasDims(canvasW, canvasH);
|
|
9799
|
+
if (canvasW === 0 || canvasH === 0) return;
|
|
9800
|
+
camera.setZoom(fitZoomForView(view, canvasW, canvasH));
|
|
9801
|
+
const origin = cameraOriginForView(view, camera.zoom, canvasW, canvasH);
|
|
9802
|
+
camera.moveTo(origin.x, origin.y);
|
|
9803
|
+
}
|
|
9804
|
+
|
|
9805
|
+
// src/canvas/camera-animator.ts
|
|
9806
|
+
var DEFAULT_DURATION_MS3 = 400;
|
|
9807
|
+
var FRAMED_EPSILON = 1e-6;
|
|
9808
|
+
var easeOutCubic2 = (t) => 1 - Math.pow(1 - t, 3);
|
|
9809
|
+
function lerp(a, b, k) {
|
|
9810
|
+
return a + (b - a) * k;
|
|
9811
|
+
}
|
|
9812
|
+
function lerpView(from, to, k) {
|
|
9813
|
+
return {
|
|
9814
|
+
x: lerp(from.x, to.x, k),
|
|
9815
|
+
y: lerp(from.y, to.y, k),
|
|
9816
|
+
w: lerp(from.w, to.w, k),
|
|
9817
|
+
h: lerp(from.h, to.h, k)
|
|
9818
|
+
};
|
|
9819
|
+
}
|
|
9820
|
+
function viewsClose(a, b) {
|
|
9821
|
+
return Math.abs(a.x - b.x) < FRAMED_EPSILON && Math.abs(a.y - b.y) < FRAMED_EPSILON && Math.abs(a.w - b.w) < FRAMED_EPSILON && Math.abs(a.h - b.h) < FRAMED_EPSILON;
|
|
9822
|
+
}
|
|
9823
|
+
var CameraAnimator = class {
|
|
9824
|
+
camera;
|
|
9825
|
+
getCanvasSize;
|
|
9826
|
+
frames;
|
|
9827
|
+
now;
|
|
9828
|
+
durationMs;
|
|
9829
|
+
easing;
|
|
9830
|
+
rafId = null;
|
|
9831
|
+
from = null;
|
|
9832
|
+
to = null;
|
|
9833
|
+
startedAt = 0;
|
|
9834
|
+
endListeners = /* @__PURE__ */ new Set();
|
|
9835
|
+
/**
|
|
9836
|
+
* Monotonic operation counter. `animateTo`/`jumpTo` claim a generation
|
|
9837
|
+
* before emitting 'superseded'; if an onEnd listener starts a newer
|
|
9838
|
+
* operation during that emit, the outer call sees a bumped counter and
|
|
9839
|
+
* bails instead of overwriting the nested animation's state. Without this,
|
|
9840
|
+
* the nested animation would run to completion having never reported an end
|
|
9841
|
+
* reason, breaking the exactly-one guarantee the spec makes.
|
|
9842
|
+
*/
|
|
9843
|
+
generation = 0;
|
|
9844
|
+
lastWrite = null;
|
|
9845
|
+
disposed = false;
|
|
9846
|
+
detachListeners = null;
|
|
9847
|
+
constructor(element, camera, options) {
|
|
9848
|
+
if (options.frames !== void 0) {
|
|
9849
|
+
const { requestFrame, cancelFrame } = options.frames;
|
|
9850
|
+
if (typeof requestFrame !== "function" || typeof cancelFrame !== "function") {
|
|
9851
|
+
throw new Error(
|
|
9852
|
+
"[fieldnotes] CameraAnimator: `frames` must supply both requestFrame and cancelFrame"
|
|
9853
|
+
);
|
|
9854
|
+
}
|
|
9855
|
+
}
|
|
9856
|
+
this.camera = camera;
|
|
9857
|
+
this.getCanvasSize = options.getCanvasSize;
|
|
9858
|
+
this.frames = options.frames ?? {
|
|
9859
|
+
requestFrame: (cb) => requestAnimationFrame(cb),
|
|
9860
|
+
cancelFrame: (id) => cancelAnimationFrame(id)
|
|
9861
|
+
};
|
|
9862
|
+
this.now = options.now ?? (() => performance.now());
|
|
9863
|
+
this.durationMs = options.durationMs ?? DEFAULT_DURATION_MS3;
|
|
9864
|
+
this.easing = options.easing ?? easeOutCubic2;
|
|
9865
|
+
if (options.interactive ?? true) {
|
|
9866
|
+
const onUserInput = () => {
|
|
9867
|
+
this.end("cancelled");
|
|
9868
|
+
};
|
|
9869
|
+
const types = ["pointerdown", "wheel", "keydown"];
|
|
9870
|
+
for (const type of types) {
|
|
9871
|
+
element.addEventListener(type, onUserInput, { passive: true });
|
|
9872
|
+
}
|
|
9873
|
+
this.detachListeners = () => {
|
|
9874
|
+
for (const type of types) {
|
|
9875
|
+
element.removeEventListener(type, onUserInput);
|
|
9876
|
+
}
|
|
9877
|
+
};
|
|
9878
|
+
}
|
|
9879
|
+
}
|
|
9880
|
+
get animating() {
|
|
9881
|
+
return this.to !== null;
|
|
9882
|
+
}
|
|
9883
|
+
onEnd(listener) {
|
|
9884
|
+
this.endListeners.add(listener);
|
|
9885
|
+
return () => this.endListeners.delete(listener);
|
|
9886
|
+
}
|
|
9887
|
+
animateTo(view) {
|
|
9888
|
+
const size = this.validateAndMeasure(view);
|
|
9889
|
+
if (size === null) return;
|
|
9890
|
+
const current = this.camera.getVisibleRect(size.w, size.h);
|
|
9891
|
+
const generation = ++this.generation;
|
|
9892
|
+
this.end("superseded");
|
|
9893
|
+
if (this.generation !== generation) return;
|
|
9894
|
+
if (viewsClose(current, view)) {
|
|
9895
|
+
applyCameraView(this.camera, view, size.w, size.h);
|
|
9896
|
+
this.emit("complete");
|
|
9897
|
+
return;
|
|
9898
|
+
}
|
|
9899
|
+
this.from = current;
|
|
9900
|
+
this.to = view;
|
|
9901
|
+
this.startedAt = this.now();
|
|
9902
|
+
this.lastWrite = null;
|
|
9903
|
+
this.rafId = this.frames.requestFrame(this.step);
|
|
9904
|
+
}
|
|
9905
|
+
jumpTo(view) {
|
|
9906
|
+
const size = this.validateAndMeasure(view);
|
|
9907
|
+
if (size === null) return;
|
|
9908
|
+
const generation = ++this.generation;
|
|
9909
|
+
this.end("superseded");
|
|
9910
|
+
if (this.generation !== generation) return;
|
|
9911
|
+
applyCameraView(this.camera, view, size.w, size.h);
|
|
9912
|
+
this.lastWrite = null;
|
|
9913
|
+
}
|
|
9914
|
+
cancel() {
|
|
9915
|
+
if (this.disposed) return;
|
|
9916
|
+
this.end("cancelled");
|
|
9917
|
+
}
|
|
9918
|
+
/**
|
|
9919
|
+
* Terminal. Order is load-bearing: the flag is set BEFORE any listener runs,
|
|
9920
|
+
* because an onEnd listener can call animateTo during the disposal callback.
|
|
9921
|
+
* With the flag set last, that call would start a real animation which the
|
|
9922
|
+
* listener clear then silently discards — a second animation with no end
|
|
9923
|
+
* reason, breaking the exactly-one guarantee.
|
|
9924
|
+
*/
|
|
9925
|
+
dispose() {
|
|
9926
|
+
if (this.disposed) return;
|
|
9927
|
+
this.disposed = true;
|
|
9928
|
+
this.generation++;
|
|
9929
|
+
const wasAnimating = this.to !== null;
|
|
9930
|
+
this.clearFrame();
|
|
9931
|
+
this.from = null;
|
|
9932
|
+
this.to = null;
|
|
9933
|
+
this.lastWrite = null;
|
|
9934
|
+
if (wasAnimating) this.emit("cancelled");
|
|
9935
|
+
this.endListeners.clear();
|
|
9936
|
+
this.detachListeners?.();
|
|
9937
|
+
this.detachListeners = null;
|
|
9938
|
+
}
|
|
9939
|
+
/**
|
|
9940
|
+
* Steps 1-3 of the public-call contract. Returns null when the caller must
|
|
9941
|
+
* stop, having already handled termination.
|
|
9942
|
+
*
|
|
9943
|
+
* The disposed check precedes validation deliberately: ordering it after
|
|
9944
|
+
* would make `disposed.animateTo(invalidView)` both required to throw and
|
|
9945
|
+
* required to stay silent. Disposal wins — a terminal animator is inert for
|
|
9946
|
+
* every input, and post-disposal calls are exactly the racy teardown paths
|
|
9947
|
+
* where a throw is least useful.
|
|
9948
|
+
*/
|
|
9949
|
+
validateAndMeasure(view) {
|
|
9950
|
+
if (this.disposed) return null;
|
|
9951
|
+
assertValidView(view);
|
|
9952
|
+
const size = this.getCanvasSize();
|
|
9953
|
+
assertCanvasDims(size.w, size.h);
|
|
9954
|
+
if (size.w === 0 || size.h === 0) {
|
|
9955
|
+
this.end("cancelled");
|
|
9956
|
+
return null;
|
|
9957
|
+
}
|
|
9958
|
+
return size;
|
|
9959
|
+
}
|
|
9960
|
+
step = () => {
|
|
9961
|
+
if (this.disposed || this.to === null || this.from === null) return;
|
|
9962
|
+
if (this.foreignWrite()) {
|
|
9963
|
+
this.end("cancelled");
|
|
9964
|
+
return;
|
|
9965
|
+
}
|
|
9966
|
+
const size = this.getCanvasSize();
|
|
9967
|
+
if (!canvasDimsUsable(size.w, size.h)) {
|
|
9968
|
+
this.end("cancelled");
|
|
9969
|
+
return;
|
|
9970
|
+
}
|
|
9971
|
+
if (size.w === 0 || size.h === 0) {
|
|
9972
|
+
this.end("cancelled");
|
|
9973
|
+
return;
|
|
9974
|
+
}
|
|
9975
|
+
const elapsed = this.now() - this.startedAt;
|
|
9976
|
+
const t = this.durationMs <= 0 ? 1 : Math.min(1, elapsed / this.durationMs);
|
|
9977
|
+
const view = lerpView(this.from, this.to, this.easing(t));
|
|
9978
|
+
applyCameraView(this.camera, view, size.w, size.h);
|
|
9979
|
+
this.recordWrite();
|
|
9980
|
+
if (t >= 1) {
|
|
9981
|
+
this.end("complete");
|
|
9982
|
+
return;
|
|
9983
|
+
}
|
|
9984
|
+
this.rafId = this.frames.requestFrame(this.step);
|
|
9985
|
+
};
|
|
9986
|
+
recordWrite() {
|
|
9987
|
+
this.lastWrite = {
|
|
9988
|
+
x: this.camera.position.x,
|
|
9989
|
+
y: this.camera.position.y,
|
|
9990
|
+
zoom: this.camera.zoom
|
|
9991
|
+
};
|
|
9992
|
+
}
|
|
9993
|
+
foreignWrite() {
|
|
9994
|
+
if (this.lastWrite === null) return false;
|
|
9995
|
+
const eps = 1e-6;
|
|
9996
|
+
return Math.abs(this.camera.position.x - this.lastWrite.x) > eps || Math.abs(this.camera.position.y - this.lastWrite.y) > eps || Math.abs(this.camera.zoom - this.lastWrite.zoom) > eps;
|
|
9997
|
+
}
|
|
9998
|
+
/** Terminates an in-flight animation with `reason`. No-op when idle. */
|
|
9999
|
+
end(reason) {
|
|
10000
|
+
if (this.disposed || this.to === null) return;
|
|
10001
|
+
this.clearFrame();
|
|
10002
|
+
this.from = null;
|
|
10003
|
+
this.to = null;
|
|
10004
|
+
this.lastWrite = null;
|
|
10005
|
+
this.emit(reason);
|
|
10006
|
+
}
|
|
10007
|
+
clearFrame() {
|
|
10008
|
+
if (this.rafId !== null) {
|
|
10009
|
+
this.frames.cancelFrame(this.rafId);
|
|
10010
|
+
this.rafId = null;
|
|
10011
|
+
}
|
|
10012
|
+
}
|
|
10013
|
+
emit(reason) {
|
|
10014
|
+
for (const listener of [...this.endListeners]) {
|
|
10015
|
+
try {
|
|
10016
|
+
listener(reason);
|
|
10017
|
+
} catch {
|
|
10018
|
+
}
|
|
10019
|
+
}
|
|
10020
|
+
}
|
|
10021
|
+
};
|
|
10022
|
+
|
|
10023
|
+
// src/canvas/focus-presence.ts
|
|
10024
|
+
var FOCUS_PRESENCE_KIND = "focus";
|
|
10025
|
+
var AUDIENCES = ["all", "players", "display"];
|
|
10026
|
+
function isPositiveFinite(value) {
|
|
10027
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0;
|
|
10028
|
+
}
|
|
10029
|
+
function isFiniteNumber2(value) {
|
|
10030
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
10031
|
+
}
|
|
10032
|
+
function isFocusPresence(data) {
|
|
10033
|
+
if (typeof data !== "object" || data === null) return false;
|
|
10034
|
+
const payload = data;
|
|
10035
|
+
if (payload.kind !== FOCUS_PRESENCE_KIND) return false;
|
|
10036
|
+
if (!isFiniteNumber2(payload.x) || !isFiniteNumber2(payload.y)) return false;
|
|
10037
|
+
if (!isPositiveFinite(payload.w) || !isPositiveFinite(payload.h)) return false;
|
|
10038
|
+
if (typeof payload.audience !== "string" || !AUDIENCES.some((a) => a === payload.audience)) {
|
|
10039
|
+
return false;
|
|
10040
|
+
}
|
|
10041
|
+
if (payload.color !== void 0 && typeof payload.color !== "string") return false;
|
|
10042
|
+
return true;
|
|
10043
|
+
}
|
|
10044
|
+
function toFocusPresence(view, audience, color) {
|
|
10045
|
+
return {
|
|
10046
|
+
kind: FOCUS_PRESENCE_KIND,
|
|
10047
|
+
x: view.x,
|
|
10048
|
+
y: view.y,
|
|
10049
|
+
w: view.w,
|
|
10050
|
+
h: view.h,
|
|
10051
|
+
audience,
|
|
10052
|
+
...color === void 0 ? {} : { color }
|
|
10053
|
+
};
|
|
10054
|
+
}
|
|
10055
|
+
|
|
10056
|
+
// src/canvas/remote-focus-receiver.ts
|
|
10057
|
+
function audienceIncludes(audience, role) {
|
|
10058
|
+
if (role === "dm") return false;
|
|
10059
|
+
if (audience === "all") return true;
|
|
10060
|
+
if (audience === "players") return role === "player";
|
|
10061
|
+
return role === "display";
|
|
10062
|
+
}
|
|
10063
|
+
var RemoteFocusReceiver = class {
|
|
10064
|
+
role;
|
|
10065
|
+
animator;
|
|
10066
|
+
animate;
|
|
10067
|
+
pulseColor;
|
|
10068
|
+
overlay;
|
|
10069
|
+
disposed = false;
|
|
10070
|
+
constructor(host, options) {
|
|
10071
|
+
this.role = options.role;
|
|
10072
|
+
this.animator = options.animator;
|
|
10073
|
+
this.animate = options.animate ?? true;
|
|
10074
|
+
this.pulseColor = options.pulseColor;
|
|
10075
|
+
this.overlay = options.pulse ?? true ? new RemotePingOverlay(host, {
|
|
10076
|
+
...options.pulseColor === void 0 ? {} : { color: options.pulseColor },
|
|
10077
|
+
...options.pulseDurationMs === void 0 ? {} : { durationMs: options.pulseDurationMs },
|
|
10078
|
+
...options.pulseRadius === void 0 ? {} : { radius: options.pulseRadius },
|
|
10079
|
+
maxPingsPerSender: 1
|
|
10080
|
+
}) : null;
|
|
10081
|
+
}
|
|
10082
|
+
/**
|
|
10083
|
+
* Applies a presence payload from `sender`. Returns `false` for payloads
|
|
10084
|
+
* that are not focus frames, or are addressed to a different role, so hosts
|
|
10085
|
+
* can feed every presence frame through without disturbing other handlers.
|
|
10086
|
+
*/
|
|
10087
|
+
apply(from, data) {
|
|
10088
|
+
if (this.disposed || !isFocusPresence(data)) return false;
|
|
10089
|
+
if (!audienceIncludes(data.audience, this.role)) return false;
|
|
10090
|
+
const view = { x: data.x, y: data.y, w: data.w, h: data.h };
|
|
10091
|
+
if (this.animate) {
|
|
10092
|
+
this.animator.animateTo(view);
|
|
10093
|
+
} else {
|
|
10094
|
+
this.animator.jumpTo(view);
|
|
10095
|
+
}
|
|
10096
|
+
this.overlay?.apply(from, {
|
|
10097
|
+
kind: "ping",
|
|
10098
|
+
x: view.x + view.w / 2,
|
|
10099
|
+
y: view.y + view.h / 2,
|
|
10100
|
+
color: data.color ?? this.pulseColor
|
|
10101
|
+
});
|
|
10102
|
+
return true;
|
|
10103
|
+
}
|
|
10104
|
+
/** Idempotent. Does NOT dispose the animator — the host owns that. */
|
|
10105
|
+
dispose() {
|
|
10106
|
+
if (this.disposed) return;
|
|
10107
|
+
this.disposed = true;
|
|
10108
|
+
this.overlay?.dispose();
|
|
10109
|
+
}
|
|
10110
|
+
};
|
|
10111
|
+
|
|
9737
10112
|
// src/tools/hand-tool.ts
|
|
9738
10113
|
var HandTool = class {
|
|
9739
10114
|
name = "hand";
|
|
@@ -9922,7 +10297,7 @@ function hitTestStroke(stroke, point, radius) {
|
|
|
9922
10297
|
}
|
|
9923
10298
|
|
|
9924
10299
|
// src/elements/stroke-erase.ts
|
|
9925
|
-
function
|
|
10300
|
+
function lerp2(a, b, t) {
|
|
9926
10301
|
return {
|
|
9927
10302
|
x: a.x + (b.x - a.x) * t,
|
|
9928
10303
|
y: a.y + (b.y - a.y) * t,
|
|
@@ -9981,13 +10356,13 @@ function erasePoints(points, eraser, radius) {
|
|
|
9981
10356
|
erased = true;
|
|
9982
10357
|
if (tLo > 0) {
|
|
9983
10358
|
if (current.length === 0) current.push(a);
|
|
9984
|
-
current.push(
|
|
10359
|
+
current.push(lerp2(a, b, tLo));
|
|
9985
10360
|
flush();
|
|
9986
10361
|
} else {
|
|
9987
10362
|
flush();
|
|
9988
10363
|
}
|
|
9989
10364
|
if (tHi < 1) {
|
|
9990
|
-
current = [
|
|
10365
|
+
current = [lerp2(a, b, tHi), b];
|
|
9991
10366
|
}
|
|
9992
10367
|
}
|
|
9993
10368
|
flush();
|
|
@@ -12443,7 +12818,7 @@ var LaserTool = class {
|
|
|
12443
12818
|
|
|
12444
12819
|
// src/tools/ping-tool.ts
|
|
12445
12820
|
var DEFAULT_COLOR6 = "#ff3b30";
|
|
12446
|
-
var
|
|
12821
|
+
var DEFAULT_DURATION_MS4 = 1800;
|
|
12447
12822
|
var DEFAULT_RADIUS4 = 48;
|
|
12448
12823
|
var DEFAULT_MIN_INTERVAL_MS2 = 300;
|
|
12449
12824
|
var PingTool = class {
|
|
@@ -12460,7 +12835,7 @@ var PingTool = class {
|
|
|
12460
12835
|
constructor(options = {}) {
|
|
12461
12836
|
this.name = options.name ?? "ping";
|
|
12462
12837
|
this.color = options.color ?? DEFAULT_COLOR6;
|
|
12463
|
-
this.durationMs = options.durationMs ??
|
|
12838
|
+
this.durationMs = options.durationMs ?? DEFAULT_DURATION_MS4;
|
|
12464
12839
|
this.radius = options.radius ?? DEFAULT_RADIUS4;
|
|
12465
12840
|
this.minIntervalMs = options.minIntervalMs ?? DEFAULT_MIN_INTERVAL_MS2;
|
|
12466
12841
|
}
|
|
@@ -12562,15 +12937,17 @@ var PingTool = class {
|
|
|
12562
12937
|
};
|
|
12563
12938
|
|
|
12564
12939
|
// src/index.ts
|
|
12565
|
-
var VERSION = "0.
|
|
12940
|
+
var VERSION = "0.61.0";
|
|
12566
12941
|
// Annotate the CommonJS export names for ESM import in node:
|
|
12567
12942
|
0 && (module.exports = {
|
|
12568
12943
|
ArrowTool,
|
|
12569
12944
|
AutoSave,
|
|
12570
12945
|
Camera,
|
|
12946
|
+
CameraAnimator,
|
|
12571
12947
|
DEFAULT_NOTE_FONT_SIZE,
|
|
12572
12948
|
ElementStore,
|
|
12573
12949
|
EraserTool,
|
|
12950
|
+
FOCUS_PRESENCE_KIND,
|
|
12574
12951
|
HandTool,
|
|
12575
12952
|
HistoryStack,
|
|
12576
12953
|
ImageTool,
|
|
@@ -12588,6 +12965,7 @@ var VERSION = "0.60.0";
|
|
|
12588
12965
|
PencilTool,
|
|
12589
12966
|
PingInput,
|
|
12590
12967
|
PingTool,
|
|
12968
|
+
RemoteFocusReceiver,
|
|
12591
12969
|
RemoteLaserOverlay,
|
|
12592
12970
|
RemoteMeasureOverlay,
|
|
12593
12971
|
RemotePingOverlay,
|
|
@@ -12598,7 +12976,10 @@ var VERSION = "0.60.0";
|
|
|
12598
12976
|
ToolManager,
|
|
12599
12977
|
VERSION,
|
|
12600
12978
|
Viewport,
|
|
12979
|
+
applyCameraView,
|
|
12601
12980
|
boundsIntersect,
|
|
12981
|
+
cameraOriginForView,
|
|
12982
|
+
captureCameraView,
|
|
12602
12983
|
createArrow,
|
|
12603
12984
|
createGrid,
|
|
12604
12985
|
createHtmlElement,
|
|
@@ -12611,6 +12992,7 @@ var VERSION = "0.60.0";
|
|
|
12611
12992
|
drawHexPath,
|
|
12612
12993
|
exportImage,
|
|
12613
12994
|
exportSvg,
|
|
12995
|
+
fitZoomForView,
|
|
12614
12996
|
getActiveFormats,
|
|
12615
12997
|
getArrowBounds,
|
|
12616
12998
|
getArrowControlPoint,
|
|
@@ -12626,6 +13008,7 @@ var VERSION = "0.60.0";
|
|
|
12626
13008
|
getHexCellsInRectangle,
|
|
12627
13009
|
getHexCellsInSquare,
|
|
12628
13010
|
getHexDistance,
|
|
13011
|
+
isFocusPresence,
|
|
12629
13012
|
isLaserTrailPresence,
|
|
12630
13013
|
isMeasurePresence,
|
|
12631
13014
|
isNearBezier,
|
|
@@ -12635,6 +13018,7 @@ var VERSION = "0.60.0";
|
|
|
12635
13018
|
snapPoint,
|
|
12636
13019
|
snapToHexCenter,
|
|
12637
13020
|
styleToPatch,
|
|
13021
|
+
toFocusPresence,
|
|
12638
13022
|
toLaserTrailPresence,
|
|
12639
13023
|
toMeasurePresence,
|
|
12640
13024
|
toPingPresence,
|