@fieldnotes/core 0.39.0 → 0.40.1
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 +199 -54
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +34 -3
- package/dist/index.d.ts +34 -3
- package/dist/index.js +198 -54
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -29,6 +29,7 @@ __export(index_exports, {
|
|
|
29
29
|
HandTool: () => HandTool,
|
|
30
30
|
HistoryStack: () => HistoryStack,
|
|
31
31
|
ImageTool: () => ImageTool,
|
|
32
|
+
LaserTool: () => LaserTool,
|
|
32
33
|
LayerManager: () => LayerManager,
|
|
33
34
|
MeasureTool: () => MeasureTool,
|
|
34
35
|
NoteTool: () => NoteTool,
|
|
@@ -913,6 +914,11 @@ var KeyboardActions = class {
|
|
|
913
914
|
if (tool?.name !== "select") return null;
|
|
914
915
|
return { tool, ctx };
|
|
915
916
|
}
|
|
917
|
+
selectableElements(ctx) {
|
|
918
|
+
return ctx.store.getAll().filter(
|
|
919
|
+
(el) => !el.locked && (ctx.isLayerVisible?.(el.layerId) ?? true) && !(ctx.isLayerLocked?.(el.layerId) ?? false)
|
|
920
|
+
);
|
|
921
|
+
}
|
|
916
922
|
nudge(dx, dy, byCell) {
|
|
917
923
|
if (this.deps.isToolActive()) return false;
|
|
918
924
|
const sel = this.selectTool();
|
|
@@ -1055,12 +1061,28 @@ var KeyboardActions = class {
|
|
|
1055
1061
|
}
|
|
1056
1062
|
const sel = this.selectTool();
|
|
1057
1063
|
if (!sel) return;
|
|
1058
|
-
const ids = sel.ctx.
|
|
1059
|
-
(el) => !el.locked && (sel.ctx.isLayerVisible?.(el.layerId) ?? true) && !(sel.ctx.isLayerLocked?.(el.layerId) ?? false)
|
|
1060
|
-
).map((el) => el.id);
|
|
1064
|
+
const ids = this.selectableElements(sel.ctx).map((el) => el.id);
|
|
1061
1065
|
sel.tool.setSelection(ids);
|
|
1062
1066
|
sel.ctx.requestRender();
|
|
1063
1067
|
}
|
|
1068
|
+
cycleSelection(direction) {
|
|
1069
|
+
if (this.deps.isToolActive()) return;
|
|
1070
|
+
const tm = this.deps.getToolManager();
|
|
1071
|
+
const ctx = this.deps.getToolContext();
|
|
1072
|
+
if (!tm || !ctx) return;
|
|
1073
|
+
if (tm.activeTool?.name !== "select") ctx.switchTool?.("select");
|
|
1074
|
+
const sel = this.selectTool();
|
|
1075
|
+
if (!sel) return;
|
|
1076
|
+
const eligible = this.selectableElements(sel.ctx).filter((el) => el.type !== "grid");
|
|
1077
|
+
if (eligible.length === 0) return;
|
|
1078
|
+
const idxs = sel.tool.selectedIds.map((id) => eligible.findIndex((e) => e.id === id)).filter((i) => i >= 0);
|
|
1079
|
+
const anchor = idxs.length === 0 ? direction > 0 ? -1 : 0 : direction > 0 ? Math.max(...idxs) : Math.min(...idxs);
|
|
1080
|
+
const next = (anchor + direction + eligible.length) % eligible.length;
|
|
1081
|
+
const target = eligible[next];
|
|
1082
|
+
if (!target) return;
|
|
1083
|
+
sel.tool.setSelection([target.id]);
|
|
1084
|
+
sel.ctx.requestRender();
|
|
1085
|
+
}
|
|
1064
1086
|
zoomToFit() {
|
|
1065
1087
|
if (this.deps.isToolActive()) return;
|
|
1066
1088
|
this.deps.fitToContent?.();
|
|
@@ -1165,6 +1187,8 @@ var DEFAULT_BINDINGS = [
|
|
|
1165
1187
|
["undo", ["mod+z"]],
|
|
1166
1188
|
["redo", ["mod+y", "mod+shift+z"]],
|
|
1167
1189
|
["select-all", ["mod+a"]],
|
|
1190
|
+
["cycle-selection", ["tab"]],
|
|
1191
|
+
["cycle-selection-reverse", ["shift+tab"]],
|
|
1168
1192
|
["copy", ["mod+c"]],
|
|
1169
1193
|
["duplicate", ["mod+d"]],
|
|
1170
1194
|
["z-forward", ["]"]],
|
|
@@ -1435,6 +1459,14 @@ var KeyboardHandler = class {
|
|
|
1435
1459
|
e?.preventDefault();
|
|
1436
1460
|
this.deps.actions.selectAll();
|
|
1437
1461
|
return;
|
|
1462
|
+
case "cycle-selection":
|
|
1463
|
+
e?.preventDefault();
|
|
1464
|
+
this.deps.actions.cycleSelection(1);
|
|
1465
|
+
return;
|
|
1466
|
+
case "cycle-selection-reverse":
|
|
1467
|
+
e?.preventDefault();
|
|
1468
|
+
this.deps.actions.cycleSelection(-1);
|
|
1469
|
+
return;
|
|
1438
1470
|
case "copy":
|
|
1439
1471
|
e?.preventDefault();
|
|
1440
1472
|
this.deps.actions.copy();
|
|
@@ -4733,6 +4765,16 @@ function renderStyledRuns(ctx, runs, startX, startY, maxWidth) {
|
|
|
4733
4765
|
}
|
|
4734
4766
|
}
|
|
4735
4767
|
|
|
4768
|
+
// src/canvas/text-canvas-renderer.ts
|
|
4769
|
+
function renderTextOnCanvas(ctx, text) {
|
|
4770
|
+
const pad = 2;
|
|
4771
|
+
ctx.save();
|
|
4772
|
+
ctx.fillStyle = text.color;
|
|
4773
|
+
const runs = parseStyledRuns(text.text ?? "", text.fontSize);
|
|
4774
|
+
renderStyledRuns(ctx, runs, text.position.x + pad, text.position.y + pad, text.size.w - pad * 2);
|
|
4775
|
+
ctx.restore();
|
|
4776
|
+
}
|
|
4777
|
+
|
|
4736
4778
|
// src/canvas/export-image.ts
|
|
4737
4779
|
var center = (b) => ({ x: b.x + b.w / 2, y: b.y + b.h / 2 });
|
|
4738
4780
|
function getStrokeBounds(el) {
|
|
@@ -4815,30 +4857,6 @@ function computeBounds(elements, padding) {
|
|
|
4815
4857
|
h: maxY - minY + padding * 2
|
|
4816
4858
|
};
|
|
4817
4859
|
}
|
|
4818
|
-
function renderTextOnCanvas(ctx, text) {
|
|
4819
|
-
if (!text.text) return;
|
|
4820
|
-
ctx.save();
|
|
4821
|
-
ctx.fillStyle = text.color;
|
|
4822
|
-
ctx.font = `${text.fontSize}px system-ui, sans-serif`;
|
|
4823
|
-
ctx.textBaseline = "top";
|
|
4824
|
-
ctx.textAlign = text.textAlign;
|
|
4825
|
-
const pad = 2;
|
|
4826
|
-
let textX = text.position.x + pad;
|
|
4827
|
-
if (text.textAlign === "center") {
|
|
4828
|
-
textX = text.position.x + text.size.w / 2;
|
|
4829
|
-
} else if (text.textAlign === "right") {
|
|
4830
|
-
textX = text.position.x + text.size.w - pad;
|
|
4831
|
-
}
|
|
4832
|
-
const lineHeight = text.fontSize * 1.4;
|
|
4833
|
-
const lines = text.text.split("\n");
|
|
4834
|
-
for (let i = 0; i < lines.length; i++) {
|
|
4835
|
-
const line = lines[i];
|
|
4836
|
-
if (line !== void 0) {
|
|
4837
|
-
ctx.fillText(line, textX, text.position.y + pad + i * lineHeight);
|
|
4838
|
-
}
|
|
4839
|
-
}
|
|
4840
|
-
ctx.restore();
|
|
4841
|
-
}
|
|
4842
4860
|
function renderGridForBounds(ctx, grid, bounds) {
|
|
4843
4861
|
const visibleBounds = {
|
|
4844
4862
|
minX: bounds.x,
|
|
@@ -5060,28 +5078,27 @@ function emitImage(image, dataUri) {
|
|
|
5060
5078
|
const { w, h } = image.size;
|
|
5061
5079
|
return `<image href="${esc(href)}" x="${n(x)}" y="${n(y)}" width="${n(w)}" height="${n(h)}" />`;
|
|
5062
5080
|
}
|
|
5063
|
-
function emitText(text) {
|
|
5081
|
+
function emitText(text, rasterScale) {
|
|
5064
5082
|
if (!text.text) return "";
|
|
5065
|
-
const
|
|
5066
|
-
|
|
5067
|
-
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
|
|
5071
|
-
|
|
5072
|
-
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
const y = text.position.y + pad + i * lineHeight;
|
|
5082
|
-
out += `<text x="${n(textX)}" y="${n(y)}" font-family="system-ui, sans-serif" font-size="${n(text.fontSize)}" fill="${esc(text.color)}" text-anchor="${anchor}" dominant-baseline="text-before-edge">${esc(line)}</text>`;
|
|
5083
|
+
const { x, y } = text.position;
|
|
5084
|
+
const { w, h } = text.size;
|
|
5085
|
+
if (typeof document === "undefined") return "";
|
|
5086
|
+
const canvas = document.createElement("canvas");
|
|
5087
|
+
canvas.width = Math.max(1, Math.ceil(w * rasterScale));
|
|
5088
|
+
canvas.height = Math.max(1, Math.ceil(h * rasterScale));
|
|
5089
|
+
const ctx = canvas.getContext("2d");
|
|
5090
|
+
if (!ctx) return "";
|
|
5091
|
+
ctx.scale(rasterScale, rasterScale);
|
|
5092
|
+
ctx.translate(-x, -y);
|
|
5093
|
+
renderTextOnCanvas(ctx, text);
|
|
5094
|
+
let dataUri;
|
|
5095
|
+
try {
|
|
5096
|
+
dataUri = canvas.toDataURL();
|
|
5097
|
+
} catch {
|
|
5098
|
+
return "";
|
|
5083
5099
|
}
|
|
5084
|
-
return
|
|
5100
|
+
if (!dataUri || !dataUri.startsWith("data:")) return "";
|
|
5101
|
+
return `<image href="${esc(dataUri)}" x="${n(x)}" y="${n(y)}" width="${n(w)}" height="${n(h)}" />`;
|
|
5085
5102
|
}
|
|
5086
5103
|
function emitNote(note, rasterScale) {
|
|
5087
5104
|
const { x, y } = note.position;
|
|
@@ -5264,7 +5281,7 @@ function emitElement(el, imageDataUris, rasterScale, firstGrid, store) {
|
|
|
5264
5281
|
case "image":
|
|
5265
5282
|
return withRotationSvg(el, emitImage(el, imageDataUris.get(el.id)));
|
|
5266
5283
|
case "text":
|
|
5267
|
-
return withRotationSvg(el, emitText(el));
|
|
5284
|
+
return withRotationSvg(el, emitText(el, rasterScale));
|
|
5268
5285
|
case "note":
|
|
5269
5286
|
return withRotationSvg(el, emitNote(el, rasterScale));
|
|
5270
5287
|
case "template":
|
|
@@ -5727,10 +5744,9 @@ var DomNodeManager = class {
|
|
|
5727
5744
|
cursor: "default",
|
|
5728
5745
|
userSelect: "none",
|
|
5729
5746
|
wordWrap: "break-word",
|
|
5730
|
-
whiteSpace: "pre-wrap",
|
|
5731
5747
|
lineHeight: "1.4"
|
|
5732
5748
|
});
|
|
5733
|
-
node.
|
|
5749
|
+
node.innerHTML = element.text || "";
|
|
5734
5750
|
const detector = new DoubleTapDetector();
|
|
5735
5751
|
node.addEventListener("pointerup", (e) => {
|
|
5736
5752
|
if (detector.feed(e)) {
|
|
@@ -5741,8 +5757,9 @@ var DomNodeManager = class {
|
|
|
5741
5757
|
});
|
|
5742
5758
|
}
|
|
5743
5759
|
if (!this.isEditingElement(element.id)) {
|
|
5744
|
-
|
|
5745
|
-
|
|
5760
|
+
const text = element.text || "";
|
|
5761
|
+
if (node.innerHTML !== text) {
|
|
5762
|
+
node.innerHTML = text;
|
|
5746
5763
|
}
|
|
5747
5764
|
Object.assign(node.style, {
|
|
5748
5765
|
fontSize: `${element.fontSize}px`,
|
|
@@ -7773,6 +7790,17 @@ function renderArrowHandles(canvasCtx, arrow, zoom) {
|
|
|
7773
7790
|
canvasCtx.stroke();
|
|
7774
7791
|
}
|
|
7775
7792
|
}
|
|
7793
|
+
function renderArrowHoverHandle(canvasCtx, arrow, zoom) {
|
|
7794
|
+
const mid = getArrowMidpoint(arrow.from, arrow.to, arrow.bend);
|
|
7795
|
+
const radius = HANDLE_RADIUS / zoom;
|
|
7796
|
+
canvasCtx.fillStyle = "#2196F3";
|
|
7797
|
+
canvasCtx.strokeStyle = "#2196F3";
|
|
7798
|
+
canvasCtx.lineWidth = 1.5 / zoom;
|
|
7799
|
+
canvasCtx.beginPath();
|
|
7800
|
+
canvasCtx.arc(mid.x, mid.y, radius, 0, Math.PI * 2);
|
|
7801
|
+
canvasCtx.fill();
|
|
7802
|
+
canvasCtx.stroke();
|
|
7803
|
+
}
|
|
7776
7804
|
|
|
7777
7805
|
// src/elements/snap-guides.ts
|
|
7778
7806
|
function xAnchors(b) {
|
|
@@ -8621,7 +8649,13 @@ var SelectTool = class {
|
|
|
8621
8649
|
if (this.hoveredId && this.ctx && this.mode.type === "idle") {
|
|
8622
8650
|
if (!this._selectedIds.includes(this.hoveredId)) {
|
|
8623
8651
|
const el = this.ctx.store.getById(this.hoveredId);
|
|
8624
|
-
if (el) {
|
|
8652
|
+
if (el?.type === "arrow") {
|
|
8653
|
+
canvasCtx.save();
|
|
8654
|
+
canvasCtx.globalAlpha = 0.35;
|
|
8655
|
+
canvasCtx.setLineDash([]);
|
|
8656
|
+
renderArrowHoverHandle(canvasCtx, el, this.ctx.camera.zoom);
|
|
8657
|
+
canvasCtx.restore();
|
|
8658
|
+
} else if (el) {
|
|
8625
8659
|
const b = getElementBounds(el);
|
|
8626
8660
|
if (b) {
|
|
8627
8661
|
canvasCtx.save();
|
|
@@ -9592,8 +9626,118 @@ var TemplateTool = class {
|
|
|
9592
9626
|
}
|
|
9593
9627
|
};
|
|
9594
9628
|
|
|
9629
|
+
// src/tools/laser-tool.ts
|
|
9630
|
+
var DEFAULT_COLOR = "#ff3b30";
|
|
9631
|
+
var DEFAULT_WIDTH = 4;
|
|
9632
|
+
var DEFAULT_FADE_MS = 1200;
|
|
9633
|
+
var LaserTool = class {
|
|
9634
|
+
name;
|
|
9635
|
+
color;
|
|
9636
|
+
width;
|
|
9637
|
+
fadeMs;
|
|
9638
|
+
trail = [];
|
|
9639
|
+
rafId = null;
|
|
9640
|
+
drawing = false;
|
|
9641
|
+
optionListeners = /* @__PURE__ */ new Set();
|
|
9642
|
+
constructor(options = {}) {
|
|
9643
|
+
this.name = options.name ?? "laser";
|
|
9644
|
+
this.color = options.color ?? DEFAULT_COLOR;
|
|
9645
|
+
this.width = options.width ?? DEFAULT_WIDTH;
|
|
9646
|
+
this.fadeMs = options.fadeMs ?? DEFAULT_FADE_MS;
|
|
9647
|
+
}
|
|
9648
|
+
now() {
|
|
9649
|
+
return performance.now();
|
|
9650
|
+
}
|
|
9651
|
+
onActivate(ctx) {
|
|
9652
|
+
ctx.setCursor?.("crosshair");
|
|
9653
|
+
}
|
|
9654
|
+
onDeactivate(ctx) {
|
|
9655
|
+
if (this.rafId !== null) {
|
|
9656
|
+
cancelAnimationFrame(this.rafId);
|
|
9657
|
+
this.rafId = null;
|
|
9658
|
+
}
|
|
9659
|
+
this.trail = [];
|
|
9660
|
+
this.drawing = false;
|
|
9661
|
+
ctx.setCursor?.("default");
|
|
9662
|
+
ctx.requestRender();
|
|
9663
|
+
}
|
|
9664
|
+
getOptions() {
|
|
9665
|
+
return {
|
|
9666
|
+
name: this.name,
|
|
9667
|
+
color: this.color,
|
|
9668
|
+
width: this.width,
|
|
9669
|
+
fadeMs: this.fadeMs
|
|
9670
|
+
};
|
|
9671
|
+
}
|
|
9672
|
+
onOptionsChange(listener) {
|
|
9673
|
+
this.optionListeners.add(listener);
|
|
9674
|
+
return () => this.optionListeners.delete(listener);
|
|
9675
|
+
}
|
|
9676
|
+
setOptions(options) {
|
|
9677
|
+
if (options.color !== void 0) this.color = options.color;
|
|
9678
|
+
if (options.width !== void 0) this.width = options.width;
|
|
9679
|
+
if (options.fadeMs !== void 0) this.fadeMs = options.fadeMs;
|
|
9680
|
+
this.notifyOptionsChange();
|
|
9681
|
+
}
|
|
9682
|
+
onPointerDown(state, ctx) {
|
|
9683
|
+
this.drawing = true;
|
|
9684
|
+
const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
|
|
9685
|
+
this.trail.push({ x: world.x, y: world.y, t: this.now() });
|
|
9686
|
+
this.ensureAnimating(ctx);
|
|
9687
|
+
}
|
|
9688
|
+
onPointerMove(state, ctx) {
|
|
9689
|
+
if (!this.drawing) return;
|
|
9690
|
+
const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
|
|
9691
|
+
this.trail.push({ x: world.x, y: world.y, t: this.now() });
|
|
9692
|
+
this.ensureAnimating(ctx);
|
|
9693
|
+
}
|
|
9694
|
+
onPointerUp(_state, _ctx) {
|
|
9695
|
+
this.drawing = false;
|
|
9696
|
+
}
|
|
9697
|
+
renderOverlay(ctx) {
|
|
9698
|
+
if (this.trail.length < 2) return;
|
|
9699
|
+
ctx.save();
|
|
9700
|
+
ctx.strokeStyle = this.color;
|
|
9701
|
+
ctx.lineWidth = this.width;
|
|
9702
|
+
ctx.lineCap = "round";
|
|
9703
|
+
ctx.lineJoin = "round";
|
|
9704
|
+
const now = this.now();
|
|
9705
|
+
for (let i = 0; i < this.trail.length - 1; i++) {
|
|
9706
|
+
const a = this.trail[i];
|
|
9707
|
+
const b = this.trail[i + 1];
|
|
9708
|
+
if (!a || !b) continue;
|
|
9709
|
+
const age = now - b.t;
|
|
9710
|
+
ctx.globalAlpha = Math.max(0, 1 - age / this.fadeMs);
|
|
9711
|
+
ctx.beginPath();
|
|
9712
|
+
ctx.moveTo(a.x, a.y);
|
|
9713
|
+
ctx.lineTo(b.x, b.y);
|
|
9714
|
+
ctx.stroke();
|
|
9715
|
+
}
|
|
9716
|
+
ctx.restore();
|
|
9717
|
+
}
|
|
9718
|
+
ensureAnimating(ctx) {
|
|
9719
|
+
if (this.rafId === null) {
|
|
9720
|
+
this.rafId = requestAnimationFrame(() => this.tick(ctx));
|
|
9721
|
+
}
|
|
9722
|
+
}
|
|
9723
|
+
tick(ctx) {
|
|
9724
|
+
const cutoff = this.now() - this.fadeMs;
|
|
9725
|
+
this.trail = this.trail.filter((p) => p.t >= cutoff);
|
|
9726
|
+
if (this.trail.length > 0) {
|
|
9727
|
+
ctx.requestRender();
|
|
9728
|
+
this.rafId = requestAnimationFrame(() => this.tick(ctx));
|
|
9729
|
+
} else {
|
|
9730
|
+
ctx.requestRender();
|
|
9731
|
+
this.rafId = null;
|
|
9732
|
+
}
|
|
9733
|
+
}
|
|
9734
|
+
notifyOptionsChange() {
|
|
9735
|
+
for (const listener of this.optionListeners) listener();
|
|
9736
|
+
}
|
|
9737
|
+
};
|
|
9738
|
+
|
|
9595
9739
|
// src/index.ts
|
|
9596
|
-
var VERSION = "0.
|
|
9740
|
+
var VERSION = "0.40.1";
|
|
9597
9741
|
// Annotate the CommonJS export names for ESM import in node:
|
|
9598
9742
|
0 && (module.exports = {
|
|
9599
9743
|
ArrowTool,
|
|
@@ -9605,6 +9749,7 @@ var VERSION = "0.39.0";
|
|
|
9605
9749
|
HandTool,
|
|
9606
9750
|
HistoryStack,
|
|
9607
9751
|
ImageTool,
|
|
9752
|
+
LaserTool,
|
|
9608
9753
|
LayerManager,
|
|
9609
9754
|
MeasureTool,
|
|
9610
9755
|
NoteTool,
|