@fieldnotes/core 0.28.0 → 0.30.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 +179 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -5
- package/dist/index.d.ts +19 -5
- package/dist/index.js +179 -12
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -2276,6 +2276,19 @@ function getArrowRenderGeometry(arrow) {
|
|
|
2276
2276
|
return geometry;
|
|
2277
2277
|
}
|
|
2278
2278
|
|
|
2279
|
+
// src/elements/shape-geometry.ts
|
|
2280
|
+
function lineEndpoints(shape) {
|
|
2281
|
+
const { x, y } = shape.position;
|
|
2282
|
+
const { w, h } = shape.size;
|
|
2283
|
+
return shape.flip ? [
|
|
2284
|
+
{ x, y: y + h },
|
|
2285
|
+
{ x: x + w, y }
|
|
2286
|
+
] : [
|
|
2287
|
+
{ x, y },
|
|
2288
|
+
{ x: x + w, y: y + h }
|
|
2289
|
+
];
|
|
2290
|
+
}
|
|
2291
|
+
|
|
2279
2292
|
// src/elements/arrow-binding.ts
|
|
2280
2293
|
var BINDABLE_TYPES = /* @__PURE__ */ new Set(["note", "text", "image", "html", "shape"]);
|
|
2281
2294
|
function isBindable(element) {
|
|
@@ -2800,6 +2813,7 @@ var ElementRenderer = class {
|
|
|
2800
2813
|
renderStroke(ctx, stroke) {
|
|
2801
2814
|
if (stroke.points.length < 2) return;
|
|
2802
2815
|
ctx.save();
|
|
2816
|
+
if (stroke.blendMode) ctx.globalCompositeOperation = stroke.blendMode;
|
|
2803
2817
|
ctx.translate(stroke.position.x, stroke.position.y);
|
|
2804
2818
|
ctx.strokeStyle = stroke.color;
|
|
2805
2819
|
ctx.lineCap = "round";
|
|
@@ -2922,7 +2936,7 @@ var ElementRenderer = class {
|
|
|
2922
2936
|
}
|
|
2923
2937
|
renderShape(ctx, shape) {
|
|
2924
2938
|
ctx.save();
|
|
2925
|
-
if (shape.fillColor !== "none") {
|
|
2939
|
+
if (shape.fillColor !== "none" && shape.shape !== "line") {
|
|
2926
2940
|
ctx.fillStyle = shape.fillColor;
|
|
2927
2941
|
this.fillShapePath(ctx, shape);
|
|
2928
2942
|
}
|
|
@@ -2961,6 +2975,15 @@ var ElementRenderer = class {
|
|
|
2961
2975
|
ctx.stroke();
|
|
2962
2976
|
break;
|
|
2963
2977
|
}
|
|
2978
|
+
case "line": {
|
|
2979
|
+
const [a, b] = lineEndpoints(shape);
|
|
2980
|
+
ctx.lineCap = "round";
|
|
2981
|
+
ctx.beginPath();
|
|
2982
|
+
ctx.moveTo(a.x, a.y);
|
|
2983
|
+
ctx.lineTo(b.x, b.y);
|
|
2984
|
+
ctx.stroke();
|
|
2985
|
+
break;
|
|
2986
|
+
}
|
|
2964
2987
|
}
|
|
2965
2988
|
}
|
|
2966
2989
|
renderGrid(ctx, grid) {
|
|
@@ -3234,7 +3257,7 @@ var ElementRenderer = class {
|
|
|
3234
3257
|
// src/elements/element-factory.ts
|
|
3235
3258
|
var DEFAULT_NOTE_FONT_SIZE = 18;
|
|
3236
3259
|
function createStroke(input) {
|
|
3237
|
-
|
|
3260
|
+
const result = {
|
|
3238
3261
|
id: createId("stroke"),
|
|
3239
3262
|
type: "stroke",
|
|
3240
3263
|
position: input.position ?? { x: 0, y: 0 },
|
|
@@ -3246,6 +3269,8 @@ function createStroke(input) {
|
|
|
3246
3269
|
width: input.width ?? 2,
|
|
3247
3270
|
opacity: input.opacity ?? 1
|
|
3248
3271
|
};
|
|
3272
|
+
if (input.blendMode) result.blendMode = input.blendMode;
|
|
3273
|
+
return result;
|
|
3249
3274
|
}
|
|
3250
3275
|
function createNote(input) {
|
|
3251
3276
|
return {
|
|
@@ -3310,7 +3335,7 @@ function createHtmlElement(input) {
|
|
|
3310
3335
|
return el;
|
|
3311
3336
|
}
|
|
3312
3337
|
function createShape(input) {
|
|
3313
|
-
|
|
3338
|
+
const result = {
|
|
3314
3339
|
id: createId("shape"),
|
|
3315
3340
|
type: "shape",
|
|
3316
3341
|
position: input.position,
|
|
@@ -3323,6 +3348,8 @@ function createShape(input) {
|
|
|
3323
3348
|
strokeWidth: input.strokeWidth ?? 2,
|
|
3324
3349
|
fillColor: input.fillColor ?? "none"
|
|
3325
3350
|
};
|
|
3351
|
+
if (input.flip) result.flip = input.flip;
|
|
3352
|
+
return result;
|
|
3326
3353
|
}
|
|
3327
3354
|
function createGrid(input) {
|
|
3328
3355
|
return {
|
|
@@ -6153,7 +6180,7 @@ var DEFAULT_MIN_POINT_DISTANCE = 3;
|
|
|
6153
6180
|
var DEFAULT_PROGRESSIVE_THRESHOLD = 200;
|
|
6154
6181
|
var PROGRESSIVE_HOT_ZONE = 30;
|
|
6155
6182
|
var PencilTool = class {
|
|
6156
|
-
name
|
|
6183
|
+
name;
|
|
6157
6184
|
drawing = false;
|
|
6158
6185
|
points = [];
|
|
6159
6186
|
color;
|
|
@@ -6162,14 +6189,19 @@ var PencilTool = class {
|
|
|
6162
6189
|
minPointDistance;
|
|
6163
6190
|
progressiveThreshold;
|
|
6164
6191
|
nextSimplifyAt;
|
|
6192
|
+
opacity;
|
|
6193
|
+
blendMode;
|
|
6165
6194
|
optionListeners = /* @__PURE__ */ new Set();
|
|
6166
6195
|
constructor(options = {}) {
|
|
6196
|
+
this.name = options.name ?? "pencil";
|
|
6167
6197
|
this.color = options.color ?? "#000000";
|
|
6168
6198
|
this.width = options.width ?? 2;
|
|
6169
6199
|
this.smoothing = options.smoothing ?? DEFAULT_SMOOTHING;
|
|
6170
6200
|
this.minPointDistance = options.minPointDistance ?? DEFAULT_MIN_POINT_DISTANCE;
|
|
6171
6201
|
this.progressiveThreshold = options.progressiveSimplifyThreshold ?? DEFAULT_PROGRESSIVE_THRESHOLD;
|
|
6172
6202
|
this.nextSimplifyAt = this.progressiveThreshold;
|
|
6203
|
+
this.opacity = options.opacity ?? 1;
|
|
6204
|
+
this.blendMode = options.blendMode;
|
|
6173
6205
|
}
|
|
6174
6206
|
onActivate(ctx) {
|
|
6175
6207
|
ctx.setCursor?.("crosshair");
|
|
@@ -6183,7 +6215,9 @@ var PencilTool = class {
|
|
|
6183
6215
|
width: this.width,
|
|
6184
6216
|
smoothing: this.smoothing,
|
|
6185
6217
|
minPointDistance: this.minPointDistance,
|
|
6186
|
-
progressiveSimplifyThreshold: this.progressiveThreshold
|
|
6218
|
+
progressiveSimplifyThreshold: this.progressiveThreshold,
|
|
6219
|
+
opacity: this.opacity,
|
|
6220
|
+
blendMode: this.blendMode
|
|
6187
6221
|
};
|
|
6188
6222
|
}
|
|
6189
6223
|
onOptionsChange(listener) {
|
|
@@ -6197,6 +6231,8 @@ var PencilTool = class {
|
|
|
6197
6231
|
if (options.minPointDistance !== void 0) this.minPointDistance = options.minPointDistance;
|
|
6198
6232
|
if (options.progressiveSimplifyThreshold !== void 0)
|
|
6199
6233
|
this.progressiveThreshold = options.progressiveSimplifyThreshold;
|
|
6234
|
+
if (options.opacity !== void 0) this.opacity = options.opacity;
|
|
6235
|
+
if (options.blendMode !== void 0) this.blendMode = options.blendMode;
|
|
6200
6236
|
this.notifyOptionsChange();
|
|
6201
6237
|
}
|
|
6202
6238
|
onPointerDown(state, ctx) {
|
|
@@ -6238,7 +6274,9 @@ var PencilTool = class {
|
|
|
6238
6274
|
points: simplified,
|
|
6239
6275
|
color: this.color,
|
|
6240
6276
|
width: this.width,
|
|
6241
|
-
layerId: ctx.activeLayerId ?? ""
|
|
6277
|
+
layerId: ctx.activeLayerId ?? "",
|
|
6278
|
+
opacity: this.opacity,
|
|
6279
|
+
blendMode: this.blendMode
|
|
6242
6280
|
});
|
|
6243
6281
|
ctx.store.add(stroke);
|
|
6244
6282
|
computeStrokeSegments(stroke);
|
|
@@ -6254,7 +6292,8 @@ var PencilTool = class {
|
|
|
6254
6292
|
ctx.strokeStyle = this.color;
|
|
6255
6293
|
ctx.lineCap = "round";
|
|
6256
6294
|
ctx.lineJoin = "round";
|
|
6257
|
-
ctx.globalAlpha = 0.8;
|
|
6295
|
+
ctx.globalAlpha = this.blendMode ? this.opacity : 0.8;
|
|
6296
|
+
if (this.blendMode) ctx.globalCompositeOperation = this.blendMode;
|
|
6258
6297
|
const segments = smoothToSegments(this.points);
|
|
6259
6298
|
for (const seg of segments) {
|
|
6260
6299
|
const w = (pressureToWidth(seg.start.pressure, this.width) + pressureToWidth(seg.end.pressure, this.width)) / 2;
|
|
@@ -6291,6 +6330,79 @@ function hitTestStroke(stroke, point, radius) {
|
|
|
6291
6330
|
return false;
|
|
6292
6331
|
}
|
|
6293
6332
|
|
|
6333
|
+
// src/elements/stroke-erase.ts
|
|
6334
|
+
function lerp(a, b, t) {
|
|
6335
|
+
return {
|
|
6336
|
+
x: a.x + (b.x - a.x) * t,
|
|
6337
|
+
y: a.y + (b.y - a.y) * t,
|
|
6338
|
+
pressure: a.pressure + (b.pressure - a.pressure) * t
|
|
6339
|
+
};
|
|
6340
|
+
}
|
|
6341
|
+
function erasePoints(points, eraser, radius) {
|
|
6342
|
+
const r2 = radius * radius;
|
|
6343
|
+
if (points.length < 2) {
|
|
6344
|
+
const p = points[0];
|
|
6345
|
+
if (p && (p.x - eraser.x) ** 2 + (p.y - eraser.y) ** 2 <= r2) return [];
|
|
6346
|
+
return null;
|
|
6347
|
+
}
|
|
6348
|
+
const runs = [];
|
|
6349
|
+
let current = [];
|
|
6350
|
+
let erased = false;
|
|
6351
|
+
const flush = () => {
|
|
6352
|
+
if (current.length >= 2) runs.push(current);
|
|
6353
|
+
current = [];
|
|
6354
|
+
};
|
|
6355
|
+
for (let i = 0; i < points.length - 1; i++) {
|
|
6356
|
+
const a = points[i];
|
|
6357
|
+
const b = points[i + 1];
|
|
6358
|
+
if (!a || !b) continue;
|
|
6359
|
+
const dx = b.x - a.x;
|
|
6360
|
+
const dy = b.y - a.y;
|
|
6361
|
+
const fx = a.x - eraser.x;
|
|
6362
|
+
const fy = a.y - eraser.y;
|
|
6363
|
+
const A = dx * dx + dy * dy;
|
|
6364
|
+
const B = 2 * (fx * dx + fy * dy);
|
|
6365
|
+
const C = fx * fx + fy * fy - r2;
|
|
6366
|
+
let tLo = 1;
|
|
6367
|
+
let tHi = 0;
|
|
6368
|
+
if (A === 0) {
|
|
6369
|
+
if (C <= 0) {
|
|
6370
|
+
tLo = 0;
|
|
6371
|
+
tHi = 1;
|
|
6372
|
+
}
|
|
6373
|
+
} else {
|
|
6374
|
+
const disc = B * B - 4 * A * C;
|
|
6375
|
+
if (disc >= 0) {
|
|
6376
|
+
const sq = Math.sqrt(disc);
|
|
6377
|
+
const lo = Math.max(0, (-B - sq) / (2 * A));
|
|
6378
|
+
const hi = Math.min(1, (-B + sq) / (2 * A));
|
|
6379
|
+
if (lo < hi) {
|
|
6380
|
+
tLo = lo;
|
|
6381
|
+
tHi = hi;
|
|
6382
|
+
}
|
|
6383
|
+
}
|
|
6384
|
+
}
|
|
6385
|
+
if (tLo > tHi) {
|
|
6386
|
+
if (current.length === 0) current.push(a);
|
|
6387
|
+
current.push(b);
|
|
6388
|
+
continue;
|
|
6389
|
+
}
|
|
6390
|
+
erased = true;
|
|
6391
|
+
if (tLo > 0) {
|
|
6392
|
+
if (current.length === 0) current.push(a);
|
|
6393
|
+
current.push(lerp(a, b, tLo));
|
|
6394
|
+
flush();
|
|
6395
|
+
} else {
|
|
6396
|
+
flush();
|
|
6397
|
+
}
|
|
6398
|
+
if (tHi < 1) {
|
|
6399
|
+
current = [lerp(a, b, tHi), b];
|
|
6400
|
+
}
|
|
6401
|
+
}
|
|
6402
|
+
flush();
|
|
6403
|
+
return erased ? runs : null;
|
|
6404
|
+
}
|
|
6405
|
+
|
|
6294
6406
|
// src/tools/eraser-tool.ts
|
|
6295
6407
|
var DEFAULT_RADIUS = 20;
|
|
6296
6408
|
function makeEraserCursor(radius) {
|
|
@@ -6303,12 +6415,21 @@ var EraserTool = class {
|
|
|
6303
6415
|
erasing = false;
|
|
6304
6416
|
radius;
|
|
6305
6417
|
cursor;
|
|
6418
|
+
mode;
|
|
6306
6419
|
constructor(options = {}) {
|
|
6307
6420
|
this.radius = options.radius ?? DEFAULT_RADIUS;
|
|
6308
6421
|
this.cursor = makeEraserCursor(this.radius);
|
|
6422
|
+
this.mode = options.mode ?? "partial";
|
|
6309
6423
|
}
|
|
6310
6424
|
getOptions() {
|
|
6311
|
-
return { radius: this.radius };
|
|
6425
|
+
return { radius: this.radius, mode: this.mode };
|
|
6426
|
+
}
|
|
6427
|
+
setOptions(options) {
|
|
6428
|
+
if (options.mode !== void 0) this.mode = options.mode;
|
|
6429
|
+
if (options.radius !== void 0) {
|
|
6430
|
+
this.radius = options.radius;
|
|
6431
|
+
this.cursor = makeEraserCursor(this.radius);
|
|
6432
|
+
}
|
|
6312
6433
|
}
|
|
6313
6434
|
onActivate(ctx) {
|
|
6314
6435
|
ctx.setCursor?.(this.cursor);
|
|
@@ -6341,10 +6462,30 @@ var EraserTool = class {
|
|
|
6341
6462
|
if (el.type !== "stroke") continue;
|
|
6342
6463
|
if (ctx.isLayerVisible && !ctx.isLayerVisible(el.layerId)) continue;
|
|
6343
6464
|
if (ctx.isLayerLocked && ctx.isLayerLocked(el.layerId)) continue;
|
|
6344
|
-
if (this.strokeIntersects(el, world))
|
|
6465
|
+
if (!this.strokeIntersects(el, world)) continue;
|
|
6466
|
+
if (this.mode === "stroke") {
|
|
6345
6467
|
ctx.store.remove(el.id);
|
|
6346
6468
|
erased = true;
|
|
6469
|
+
continue;
|
|
6347
6470
|
}
|
|
6471
|
+
const localEraser = { x: world.x - el.position.x, y: world.y - el.position.y };
|
|
6472
|
+
const runs = erasePoints(el.points, localEraser, this.radius);
|
|
6473
|
+
if (runs === null) continue;
|
|
6474
|
+
ctx.store.remove(el.id);
|
|
6475
|
+
for (const run of runs) {
|
|
6476
|
+
ctx.store.add(
|
|
6477
|
+
createStroke({
|
|
6478
|
+
points: run,
|
|
6479
|
+
color: el.color,
|
|
6480
|
+
width: el.width,
|
|
6481
|
+
opacity: el.opacity,
|
|
6482
|
+
layerId: el.layerId,
|
|
6483
|
+
zIndex: el.zIndex,
|
|
6484
|
+
position: el.position
|
|
6485
|
+
})
|
|
6486
|
+
);
|
|
6487
|
+
}
|
|
6488
|
+
erased = true;
|
|
6348
6489
|
}
|
|
6349
6490
|
if (erased) ctx.requestRender();
|
|
6350
6491
|
}
|
|
@@ -7042,6 +7183,11 @@ var SelectTool = class {
|
|
|
7042
7183
|
}
|
|
7043
7184
|
isInsideBounds(point, el) {
|
|
7044
7185
|
if (el.type === "grid") return false;
|
|
7186
|
+
if (el.type === "shape" && el.shape === "line") {
|
|
7187
|
+
const [a, b] = lineEndpoints(el);
|
|
7188
|
+
const threshold = Math.max(el.strokeWidth / 2, 6);
|
|
7189
|
+
return distSqToSegment(point, a, b) <= threshold * threshold;
|
|
7190
|
+
}
|
|
7045
7191
|
if ("size" in el) {
|
|
7046
7192
|
const s = el.size;
|
|
7047
7193
|
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;
|
|
@@ -7355,6 +7501,15 @@ var ImageTool = class {
|
|
|
7355
7501
|
};
|
|
7356
7502
|
|
|
7357
7503
|
// src/tools/shape-tool.ts
|
|
7504
|
+
function snapTo45(start, end) {
|
|
7505
|
+
const dx = end.x - start.x;
|
|
7506
|
+
const dy = end.y - start.y;
|
|
7507
|
+
const len = Math.hypot(dx, dy);
|
|
7508
|
+
if (len === 0) return { ...end };
|
|
7509
|
+
const step = Math.PI / 4;
|
|
7510
|
+
const angle = Math.round(Math.atan2(dy, dx) / step) * step;
|
|
7511
|
+
return { x: start.x + Math.cos(angle) * len, y: start.y + Math.sin(angle) * len };
|
|
7512
|
+
}
|
|
7358
7513
|
var ShapeTool = class {
|
|
7359
7514
|
name = "shape";
|
|
7360
7515
|
drawing = false;
|
|
@@ -7412,13 +7567,17 @@ var ShapeTool = class {
|
|
|
7412
7567
|
onPointerMove(state, ctx) {
|
|
7413
7568
|
if (!this.drawing) return;
|
|
7414
7569
|
this.end = this.snap(ctx.camera.screenToWorld({ x: state.x, y: state.y }), ctx);
|
|
7570
|
+
if (this.shape === "line" && this.shiftHeld) {
|
|
7571
|
+
this.end = snapTo45(this.start, this.end);
|
|
7572
|
+
}
|
|
7415
7573
|
ctx.requestRender();
|
|
7416
7574
|
}
|
|
7417
7575
|
onPointerUp(_state, ctx) {
|
|
7418
7576
|
if (!this.drawing) return;
|
|
7419
7577
|
this.drawing = false;
|
|
7420
7578
|
const { position, size } = this.computeRect();
|
|
7421
|
-
|
|
7579
|
+
const isLine = this.shape === "line";
|
|
7580
|
+
if (isLine ? size.w === 0 && size.h === 0 : size.w === 0 || size.h === 0) return;
|
|
7422
7581
|
const shape = createShape({
|
|
7423
7582
|
position,
|
|
7424
7583
|
size,
|
|
@@ -7426,6 +7585,7 @@ var ShapeTool = class {
|
|
|
7426
7585
|
strokeColor: this.strokeColor,
|
|
7427
7586
|
strokeWidth: this.strokeWidth,
|
|
7428
7587
|
fillColor: this.fillColor,
|
|
7588
|
+
...isLine ? { flip: this.end.x > this.start.x !== this.end.y > this.start.y } : {},
|
|
7429
7589
|
layerId: ctx.activeLayerId ?? ""
|
|
7430
7590
|
});
|
|
7431
7591
|
ctx.store.add(shape);
|
|
@@ -7459,6 +7619,13 @@ var ShapeTool = class {
|
|
|
7459
7619
|
ctx.stroke();
|
|
7460
7620
|
break;
|
|
7461
7621
|
}
|
|
7622
|
+
case "line":
|
|
7623
|
+
ctx.lineCap = "round";
|
|
7624
|
+
ctx.beginPath();
|
|
7625
|
+
ctx.moveTo(this.start.x, this.start.y);
|
|
7626
|
+
ctx.lineTo(this.end.x, this.end.y);
|
|
7627
|
+
ctx.stroke();
|
|
7628
|
+
break;
|
|
7462
7629
|
}
|
|
7463
7630
|
ctx.restore();
|
|
7464
7631
|
}
|
|
@@ -7467,7 +7634,7 @@ var ShapeTool = class {
|
|
|
7467
7634
|
let y = Math.min(this.start.y, this.end.y);
|
|
7468
7635
|
let w = Math.abs(this.end.x - this.start.x);
|
|
7469
7636
|
let h = Math.abs(this.end.y - this.start.y);
|
|
7470
|
-
if (this.shiftHeld) {
|
|
7637
|
+
if (this.shiftHeld && this.shape !== "line") {
|
|
7471
7638
|
const side = Math.max(w, h);
|
|
7472
7639
|
w = side;
|
|
7473
7640
|
h = side;
|
|
@@ -7883,7 +8050,7 @@ var TemplateTool = class {
|
|
|
7883
8050
|
};
|
|
7884
8051
|
|
|
7885
8052
|
// src/index.ts
|
|
7886
|
-
var VERSION = "0.
|
|
8053
|
+
var VERSION = "0.30.0";
|
|
7887
8054
|
// Annotate the CommonJS export names for ESM import in node:
|
|
7888
8055
|
0 && (module.exports = {
|
|
7889
8056
|
ArrowTool,
|