@fieldnotes/core 0.29.0 → 0.31.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/README.md +595 -583
- package/dist/index.cjs +129 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +15 -3
- package/dist/index.d.ts +15 -3
- package/dist/index.js +129 -10
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -2276,6 +2276,26 @@ function getArrowRenderGeometry(arrow) {
|
|
|
2276
2276
|
return geometry;
|
|
2277
2277
|
}
|
|
2278
2278
|
|
|
2279
|
+
// src/elements/shape-geometry.ts
|
|
2280
|
+
function lineFromEndpoints(a, b) {
|
|
2281
|
+
return {
|
|
2282
|
+
position: { x: Math.min(a.x, b.x), y: Math.min(a.y, b.y) },
|
|
2283
|
+
size: { w: Math.abs(b.x - a.x), h: Math.abs(b.y - a.y) },
|
|
2284
|
+
flip: b.x > a.x !== b.y > a.y
|
|
2285
|
+
};
|
|
2286
|
+
}
|
|
2287
|
+
function lineEndpoints(shape) {
|
|
2288
|
+
const { x, y } = shape.position;
|
|
2289
|
+
const { w, h } = shape.size;
|
|
2290
|
+
return shape.flip ? [
|
|
2291
|
+
{ x, y: y + h },
|
|
2292
|
+
{ x: x + w, y }
|
|
2293
|
+
] : [
|
|
2294
|
+
{ x, y },
|
|
2295
|
+
{ x: x + w, y: y + h }
|
|
2296
|
+
];
|
|
2297
|
+
}
|
|
2298
|
+
|
|
2279
2299
|
// src/elements/arrow-binding.ts
|
|
2280
2300
|
var BINDABLE_TYPES = /* @__PURE__ */ new Set(["note", "text", "image", "html", "shape"]);
|
|
2281
2301
|
function isBindable(element) {
|
|
@@ -2800,6 +2820,7 @@ var ElementRenderer = class {
|
|
|
2800
2820
|
renderStroke(ctx, stroke) {
|
|
2801
2821
|
if (stroke.points.length < 2) return;
|
|
2802
2822
|
ctx.save();
|
|
2823
|
+
if (stroke.blendMode) ctx.globalCompositeOperation = stroke.blendMode;
|
|
2803
2824
|
ctx.translate(stroke.position.x, stroke.position.y);
|
|
2804
2825
|
ctx.strokeStyle = stroke.color;
|
|
2805
2826
|
ctx.lineCap = "round";
|
|
@@ -2922,7 +2943,7 @@ var ElementRenderer = class {
|
|
|
2922
2943
|
}
|
|
2923
2944
|
renderShape(ctx, shape) {
|
|
2924
2945
|
ctx.save();
|
|
2925
|
-
if (shape.fillColor !== "none") {
|
|
2946
|
+
if (shape.fillColor !== "none" && shape.shape !== "line") {
|
|
2926
2947
|
ctx.fillStyle = shape.fillColor;
|
|
2927
2948
|
this.fillShapePath(ctx, shape);
|
|
2928
2949
|
}
|
|
@@ -2961,6 +2982,15 @@ var ElementRenderer = class {
|
|
|
2961
2982
|
ctx.stroke();
|
|
2962
2983
|
break;
|
|
2963
2984
|
}
|
|
2985
|
+
case "line": {
|
|
2986
|
+
const [a, b] = lineEndpoints(shape);
|
|
2987
|
+
ctx.lineCap = "round";
|
|
2988
|
+
ctx.beginPath();
|
|
2989
|
+
ctx.moveTo(a.x, a.y);
|
|
2990
|
+
ctx.lineTo(b.x, b.y);
|
|
2991
|
+
ctx.stroke();
|
|
2992
|
+
break;
|
|
2993
|
+
}
|
|
2964
2994
|
}
|
|
2965
2995
|
}
|
|
2966
2996
|
renderGrid(ctx, grid) {
|
|
@@ -3234,7 +3264,7 @@ var ElementRenderer = class {
|
|
|
3234
3264
|
// src/elements/element-factory.ts
|
|
3235
3265
|
var DEFAULT_NOTE_FONT_SIZE = 18;
|
|
3236
3266
|
function createStroke(input) {
|
|
3237
|
-
|
|
3267
|
+
const result = {
|
|
3238
3268
|
id: createId("stroke"),
|
|
3239
3269
|
type: "stroke",
|
|
3240
3270
|
position: input.position ?? { x: 0, y: 0 },
|
|
@@ -3246,6 +3276,8 @@ function createStroke(input) {
|
|
|
3246
3276
|
width: input.width ?? 2,
|
|
3247
3277
|
opacity: input.opacity ?? 1
|
|
3248
3278
|
};
|
|
3279
|
+
if (input.blendMode) result.blendMode = input.blendMode;
|
|
3280
|
+
return result;
|
|
3249
3281
|
}
|
|
3250
3282
|
function createNote(input) {
|
|
3251
3283
|
return {
|
|
@@ -3310,7 +3342,7 @@ function createHtmlElement(input) {
|
|
|
3310
3342
|
return el;
|
|
3311
3343
|
}
|
|
3312
3344
|
function createShape(input) {
|
|
3313
|
-
|
|
3345
|
+
const result = {
|
|
3314
3346
|
id: createId("shape"),
|
|
3315
3347
|
type: "shape",
|
|
3316
3348
|
position: input.position,
|
|
@@ -3323,6 +3355,8 @@ function createShape(input) {
|
|
|
3323
3355
|
strokeWidth: input.strokeWidth ?? 2,
|
|
3324
3356
|
fillColor: input.fillColor ?? "none"
|
|
3325
3357
|
};
|
|
3358
|
+
if (input.flip) result.flip = input.flip;
|
|
3359
|
+
return result;
|
|
3326
3360
|
}
|
|
3327
3361
|
function createGrid(input) {
|
|
3328
3362
|
return {
|
|
@@ -6153,7 +6187,7 @@ var DEFAULT_MIN_POINT_DISTANCE = 3;
|
|
|
6153
6187
|
var DEFAULT_PROGRESSIVE_THRESHOLD = 200;
|
|
6154
6188
|
var PROGRESSIVE_HOT_ZONE = 30;
|
|
6155
6189
|
var PencilTool = class {
|
|
6156
|
-
name
|
|
6190
|
+
name;
|
|
6157
6191
|
drawing = false;
|
|
6158
6192
|
points = [];
|
|
6159
6193
|
color;
|
|
@@ -6162,14 +6196,19 @@ var PencilTool = class {
|
|
|
6162
6196
|
minPointDistance;
|
|
6163
6197
|
progressiveThreshold;
|
|
6164
6198
|
nextSimplifyAt;
|
|
6199
|
+
opacity;
|
|
6200
|
+
blendMode;
|
|
6165
6201
|
optionListeners = /* @__PURE__ */ new Set();
|
|
6166
6202
|
constructor(options = {}) {
|
|
6203
|
+
this.name = options.name ?? "pencil";
|
|
6167
6204
|
this.color = options.color ?? "#000000";
|
|
6168
6205
|
this.width = options.width ?? 2;
|
|
6169
6206
|
this.smoothing = options.smoothing ?? DEFAULT_SMOOTHING;
|
|
6170
6207
|
this.minPointDistance = options.minPointDistance ?? DEFAULT_MIN_POINT_DISTANCE;
|
|
6171
6208
|
this.progressiveThreshold = options.progressiveSimplifyThreshold ?? DEFAULT_PROGRESSIVE_THRESHOLD;
|
|
6172
6209
|
this.nextSimplifyAt = this.progressiveThreshold;
|
|
6210
|
+
this.opacity = options.opacity ?? 1;
|
|
6211
|
+
this.blendMode = options.blendMode;
|
|
6173
6212
|
}
|
|
6174
6213
|
onActivate(ctx) {
|
|
6175
6214
|
ctx.setCursor?.("crosshair");
|
|
@@ -6183,7 +6222,9 @@ var PencilTool = class {
|
|
|
6183
6222
|
width: this.width,
|
|
6184
6223
|
smoothing: this.smoothing,
|
|
6185
6224
|
minPointDistance: this.minPointDistance,
|
|
6186
|
-
progressiveSimplifyThreshold: this.progressiveThreshold
|
|
6225
|
+
progressiveSimplifyThreshold: this.progressiveThreshold,
|
|
6226
|
+
opacity: this.opacity,
|
|
6227
|
+
blendMode: this.blendMode
|
|
6187
6228
|
};
|
|
6188
6229
|
}
|
|
6189
6230
|
onOptionsChange(listener) {
|
|
@@ -6197,6 +6238,8 @@ var PencilTool = class {
|
|
|
6197
6238
|
if (options.minPointDistance !== void 0) this.minPointDistance = options.minPointDistance;
|
|
6198
6239
|
if (options.progressiveSimplifyThreshold !== void 0)
|
|
6199
6240
|
this.progressiveThreshold = options.progressiveSimplifyThreshold;
|
|
6241
|
+
if (options.opacity !== void 0) this.opacity = options.opacity;
|
|
6242
|
+
if (options.blendMode !== void 0) this.blendMode = options.blendMode;
|
|
6200
6243
|
this.notifyOptionsChange();
|
|
6201
6244
|
}
|
|
6202
6245
|
onPointerDown(state, ctx) {
|
|
@@ -6238,7 +6281,9 @@ var PencilTool = class {
|
|
|
6238
6281
|
points: simplified,
|
|
6239
6282
|
color: this.color,
|
|
6240
6283
|
width: this.width,
|
|
6241
|
-
layerId: ctx.activeLayerId ?? ""
|
|
6284
|
+
layerId: ctx.activeLayerId ?? "",
|
|
6285
|
+
opacity: this.opacity,
|
|
6286
|
+
blendMode: this.blendMode
|
|
6242
6287
|
});
|
|
6243
6288
|
ctx.store.add(stroke);
|
|
6244
6289
|
computeStrokeSegments(stroke);
|
|
@@ -6254,7 +6299,8 @@ var PencilTool = class {
|
|
|
6254
6299
|
ctx.strokeStyle = this.color;
|
|
6255
6300
|
ctx.lineCap = "round";
|
|
6256
6301
|
ctx.lineJoin = "round";
|
|
6257
|
-
ctx.globalAlpha = 0.8;
|
|
6302
|
+
ctx.globalAlpha = this.blendMode ? this.opacity : 0.8;
|
|
6303
|
+
if (this.blendMode) ctx.globalCompositeOperation = this.blendMode;
|
|
6258
6304
|
const segments = smoothToSegments(this.points);
|
|
6259
6305
|
for (const seg of segments) {
|
|
6260
6306
|
const w = (pressureToWidth(seg.start.pressure, this.width) + pressureToWidth(seg.end.pressure, this.width)) / 2;
|
|
@@ -6643,6 +6689,12 @@ var SelectTool = class {
|
|
|
6643
6689
|
ctx.requestRender();
|
|
6644
6690
|
return;
|
|
6645
6691
|
}
|
|
6692
|
+
const lineHit = this.hitTestLineHandles(world, ctx);
|
|
6693
|
+
if (lineHit) {
|
|
6694
|
+
this.mode = { type: "line-handle", elementId: lineHit.elementId, fixed: lineHit.fixed };
|
|
6695
|
+
ctx.requestRender();
|
|
6696
|
+
return;
|
|
6697
|
+
}
|
|
6646
6698
|
const templateResizeHit = this.hitTestTemplateResizeHandle(world, ctx);
|
|
6647
6699
|
if (templateResizeHit) {
|
|
6648
6700
|
this.mode = { type: "resizing-template", elementId: templateResizeHit };
|
|
@@ -6698,6 +6750,15 @@ var SelectTool = class {
|
|
|
6698
6750
|
applyArrowHandleDrag(this.mode.handle, this.mode.elementId, world, ctx);
|
|
6699
6751
|
return;
|
|
6700
6752
|
}
|
|
6753
|
+
if (this.mode.type === "line-handle") {
|
|
6754
|
+
ctx.setCursor?.("grabbing");
|
|
6755
|
+
const el = ctx.store.getById(this.mode.elementId);
|
|
6756
|
+
if (el && el.type === "shape") {
|
|
6757
|
+
ctx.store.update(el.id, lineFromEndpoints(this.mode.fixed, world));
|
|
6758
|
+
}
|
|
6759
|
+
ctx.requestRender();
|
|
6760
|
+
return;
|
|
6761
|
+
}
|
|
6701
6762
|
if (this.mode.type === "resizing-template") {
|
|
6702
6763
|
ctx.setCursor?.("nwse-resize");
|
|
6703
6764
|
this.handleTemplateResize(world, ctx);
|
|
@@ -6866,6 +6927,10 @@ var SelectTool = class {
|
|
|
6866
6927
|
ctx.setCursor?.(getArrowHandleCursor(arrowHit.handle, false));
|
|
6867
6928
|
return null;
|
|
6868
6929
|
}
|
|
6930
|
+
if (this.hitTestLineHandles(world, ctx)) {
|
|
6931
|
+
ctx.setCursor?.("grab");
|
|
6932
|
+
return null;
|
|
6933
|
+
}
|
|
6869
6934
|
const templateResizeHit = this.hitTestTemplateResizeHandle(world, ctx);
|
|
6870
6935
|
if (templateResizeHit) {
|
|
6871
6936
|
ctx.setCursor?.("nwse-resize");
|
|
@@ -6953,6 +7018,7 @@ var SelectTool = class {
|
|
|
6953
7018
|
for (const id of this._selectedIds) {
|
|
6954
7019
|
const el = ctx.store.getById(id);
|
|
6955
7020
|
if (!el || !("size" in el)) continue;
|
|
7021
|
+
if (el.type === "shape" && el.shape === "line") continue;
|
|
6956
7022
|
const bounds = getElementBounds(el);
|
|
6957
7023
|
if (!bounds) continue;
|
|
6958
7024
|
const corners = this.getHandlePositions(bounds);
|
|
@@ -6964,6 +7030,20 @@ var SelectTool = class {
|
|
|
6964
7030
|
}
|
|
6965
7031
|
return null;
|
|
6966
7032
|
}
|
|
7033
|
+
hitTestLineHandles(world, ctx) {
|
|
7034
|
+
if (this._selectedIds.length === 0) return null;
|
|
7035
|
+
const zoom = ctx.camera.zoom;
|
|
7036
|
+
const r = (HANDLE_SIZE / 2 + HANDLE_HIT_PADDING2) / zoom;
|
|
7037
|
+
const r2 = r * r;
|
|
7038
|
+
for (const id of this._selectedIds) {
|
|
7039
|
+
const el = ctx.store.getById(id);
|
|
7040
|
+
if (!el || el.type !== "shape" || el.shape !== "line") continue;
|
|
7041
|
+
const [a, b] = lineEndpoints(el);
|
|
7042
|
+
if ((world.x - a.x) ** 2 + (world.y - a.y) ** 2 <= r2) return { elementId: id, fixed: b };
|
|
7043
|
+
if ((world.x - b.x) ** 2 + (world.y - b.y) ** 2 <= r2) return { elementId: id, fixed: a };
|
|
7044
|
+
}
|
|
7045
|
+
return null;
|
|
7046
|
+
}
|
|
6967
7047
|
getHandlePositions(bounds) {
|
|
6968
7048
|
return [
|
|
6969
7049
|
["nw", { x: bounds.x, y: bounds.y }],
|
|
@@ -7001,6 +7081,19 @@ var SelectTool = class {
|
|
|
7001
7081
|
this.renderBindingHighlights(canvasCtx, el, zoom);
|
|
7002
7082
|
continue;
|
|
7003
7083
|
}
|
|
7084
|
+
if (el.type === "shape" && el.shape === "line") {
|
|
7085
|
+
canvasCtx.setLineDash([]);
|
|
7086
|
+
canvasCtx.fillStyle = "#ffffff";
|
|
7087
|
+
const r = handleWorldSize / 2;
|
|
7088
|
+
for (const p of lineEndpoints(el)) {
|
|
7089
|
+
canvasCtx.beginPath();
|
|
7090
|
+
canvasCtx.arc(p.x, p.y, r, 0, Math.PI * 2);
|
|
7091
|
+
canvasCtx.fill();
|
|
7092
|
+
canvasCtx.stroke();
|
|
7093
|
+
}
|
|
7094
|
+
canvasCtx.setLineDash([4 / zoom, 4 / zoom]);
|
|
7095
|
+
continue;
|
|
7096
|
+
}
|
|
7004
7097
|
const bounds = getElementBounds(el);
|
|
7005
7098
|
if (!bounds) continue;
|
|
7006
7099
|
const pad = SELECTION_PAD / zoom;
|
|
@@ -7144,6 +7237,11 @@ var SelectTool = class {
|
|
|
7144
7237
|
}
|
|
7145
7238
|
isInsideBounds(point, el) {
|
|
7146
7239
|
if (el.type === "grid") return false;
|
|
7240
|
+
if (el.type === "shape" && el.shape === "line") {
|
|
7241
|
+
const [a, b] = lineEndpoints(el);
|
|
7242
|
+
const threshold = Math.max(el.strokeWidth / 2, 6);
|
|
7243
|
+
return distSqToSegment(point, a, b) <= threshold * threshold;
|
|
7244
|
+
}
|
|
7147
7245
|
if ("size" in el) {
|
|
7148
7246
|
const s = el.size;
|
|
7149
7247
|
return point.x >= el.position.x && point.x <= el.position.x + s.w && point.y >= el.position.y && point.y <= el.position.y + s.h;
|
|
@@ -7457,6 +7555,15 @@ var ImageTool = class {
|
|
|
7457
7555
|
};
|
|
7458
7556
|
|
|
7459
7557
|
// src/tools/shape-tool.ts
|
|
7558
|
+
function snapTo45(start, end) {
|
|
7559
|
+
const dx = end.x - start.x;
|
|
7560
|
+
const dy = end.y - start.y;
|
|
7561
|
+
const len = Math.hypot(dx, dy);
|
|
7562
|
+
if (len === 0) return { ...end };
|
|
7563
|
+
const step = Math.PI / 4;
|
|
7564
|
+
const angle = Math.round(Math.atan2(dy, dx) / step) * step;
|
|
7565
|
+
return { x: start.x + Math.cos(angle) * len, y: start.y + Math.sin(angle) * len };
|
|
7566
|
+
}
|
|
7460
7567
|
var ShapeTool = class {
|
|
7461
7568
|
name = "shape";
|
|
7462
7569
|
drawing = false;
|
|
@@ -7514,13 +7621,17 @@ var ShapeTool = class {
|
|
|
7514
7621
|
onPointerMove(state, ctx) {
|
|
7515
7622
|
if (!this.drawing) return;
|
|
7516
7623
|
this.end = this.snap(ctx.camera.screenToWorld({ x: state.x, y: state.y }), ctx);
|
|
7624
|
+
if (this.shape === "line" && this.shiftHeld) {
|
|
7625
|
+
this.end = snapTo45(this.start, this.end);
|
|
7626
|
+
}
|
|
7517
7627
|
ctx.requestRender();
|
|
7518
7628
|
}
|
|
7519
7629
|
onPointerUp(_state, ctx) {
|
|
7520
7630
|
if (!this.drawing) return;
|
|
7521
7631
|
this.drawing = false;
|
|
7522
7632
|
const { position, size } = this.computeRect();
|
|
7523
|
-
|
|
7633
|
+
const isLine = this.shape === "line";
|
|
7634
|
+
if (isLine ? size.w === 0 && size.h === 0 : size.w === 0 || size.h === 0) return;
|
|
7524
7635
|
const shape = createShape({
|
|
7525
7636
|
position,
|
|
7526
7637
|
size,
|
|
@@ -7528,6 +7639,7 @@ var ShapeTool = class {
|
|
|
7528
7639
|
strokeColor: this.strokeColor,
|
|
7529
7640
|
strokeWidth: this.strokeWidth,
|
|
7530
7641
|
fillColor: this.fillColor,
|
|
7642
|
+
...isLine ? { flip: this.end.x > this.start.x !== this.end.y > this.start.y } : {},
|
|
7531
7643
|
layerId: ctx.activeLayerId ?? ""
|
|
7532
7644
|
});
|
|
7533
7645
|
ctx.store.add(shape);
|
|
@@ -7561,6 +7673,13 @@ var ShapeTool = class {
|
|
|
7561
7673
|
ctx.stroke();
|
|
7562
7674
|
break;
|
|
7563
7675
|
}
|
|
7676
|
+
case "line":
|
|
7677
|
+
ctx.lineCap = "round";
|
|
7678
|
+
ctx.beginPath();
|
|
7679
|
+
ctx.moveTo(this.start.x, this.start.y);
|
|
7680
|
+
ctx.lineTo(this.end.x, this.end.y);
|
|
7681
|
+
ctx.stroke();
|
|
7682
|
+
break;
|
|
7564
7683
|
}
|
|
7565
7684
|
ctx.restore();
|
|
7566
7685
|
}
|
|
@@ -7569,7 +7688,7 @@ var ShapeTool = class {
|
|
|
7569
7688
|
let y = Math.min(this.start.y, this.end.y);
|
|
7570
7689
|
let w = Math.abs(this.end.x - this.start.x);
|
|
7571
7690
|
let h = Math.abs(this.end.y - this.start.y);
|
|
7572
|
-
if (this.shiftHeld) {
|
|
7691
|
+
if (this.shiftHeld && this.shape !== "line") {
|
|
7573
7692
|
const side = Math.max(w, h);
|
|
7574
7693
|
w = side;
|
|
7575
7694
|
h = side;
|
|
@@ -7985,7 +8104,7 @@ var TemplateTool = class {
|
|
|
7985
8104
|
};
|
|
7986
8105
|
|
|
7987
8106
|
// src/index.ts
|
|
7988
|
-
var VERSION = "0.
|
|
8107
|
+
var VERSION = "0.31.0";
|
|
7989
8108
|
// Annotate the CommonJS export names for ESM import in node:
|
|
7990
8109
|
0 && (module.exports = {
|
|
7991
8110
|
ArrowTool,
|