@fieldnotes/core 0.28.0 → 0.29.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 CHANGED
@@ -439,7 +439,7 @@ new Viewport(container, {
439
439
 
440
440
  ```typescript
441
441
  new PencilTool({ color: '#ff0000', width: 3, smoothing: 1.5 });
442
- new EraserTool({ radius: 30 });
442
+ new EraserTool({ radius: 30 }); // mode: 'partial' (default) splits strokes at the erased span; mode: 'stroke' deletes the whole stroke
443
443
  new ArrowTool({ color: '#333', width: 2 });
444
444
  ```
445
445
 
package/dist/index.cjs CHANGED
@@ -6291,6 +6291,79 @@ function hitTestStroke(stroke, point, radius) {
6291
6291
  return false;
6292
6292
  }
6293
6293
 
6294
+ // src/elements/stroke-erase.ts
6295
+ function lerp(a, b, t) {
6296
+ return {
6297
+ x: a.x + (b.x - a.x) * t,
6298
+ y: a.y + (b.y - a.y) * t,
6299
+ pressure: a.pressure + (b.pressure - a.pressure) * t
6300
+ };
6301
+ }
6302
+ function erasePoints(points, eraser, radius) {
6303
+ const r2 = radius * radius;
6304
+ if (points.length < 2) {
6305
+ const p = points[0];
6306
+ if (p && (p.x - eraser.x) ** 2 + (p.y - eraser.y) ** 2 <= r2) return [];
6307
+ return null;
6308
+ }
6309
+ const runs = [];
6310
+ let current = [];
6311
+ let erased = false;
6312
+ const flush = () => {
6313
+ if (current.length >= 2) runs.push(current);
6314
+ current = [];
6315
+ };
6316
+ for (let i = 0; i < points.length - 1; i++) {
6317
+ const a = points[i];
6318
+ const b = points[i + 1];
6319
+ if (!a || !b) continue;
6320
+ const dx = b.x - a.x;
6321
+ const dy = b.y - a.y;
6322
+ const fx = a.x - eraser.x;
6323
+ const fy = a.y - eraser.y;
6324
+ const A = dx * dx + dy * dy;
6325
+ const B = 2 * (fx * dx + fy * dy);
6326
+ const C = fx * fx + fy * fy - r2;
6327
+ let tLo = 1;
6328
+ let tHi = 0;
6329
+ if (A === 0) {
6330
+ if (C <= 0) {
6331
+ tLo = 0;
6332
+ tHi = 1;
6333
+ }
6334
+ } else {
6335
+ const disc = B * B - 4 * A * C;
6336
+ if (disc >= 0) {
6337
+ const sq = Math.sqrt(disc);
6338
+ const lo = Math.max(0, (-B - sq) / (2 * A));
6339
+ const hi = Math.min(1, (-B + sq) / (2 * A));
6340
+ if (lo < hi) {
6341
+ tLo = lo;
6342
+ tHi = hi;
6343
+ }
6344
+ }
6345
+ }
6346
+ if (tLo > tHi) {
6347
+ if (current.length === 0) current.push(a);
6348
+ current.push(b);
6349
+ continue;
6350
+ }
6351
+ erased = true;
6352
+ if (tLo > 0) {
6353
+ if (current.length === 0) current.push(a);
6354
+ current.push(lerp(a, b, tLo));
6355
+ flush();
6356
+ } else {
6357
+ flush();
6358
+ }
6359
+ if (tHi < 1) {
6360
+ current = [lerp(a, b, tHi), b];
6361
+ }
6362
+ }
6363
+ flush();
6364
+ return erased ? runs : null;
6365
+ }
6366
+
6294
6367
  // src/tools/eraser-tool.ts
6295
6368
  var DEFAULT_RADIUS = 20;
6296
6369
  function makeEraserCursor(radius) {
@@ -6303,12 +6376,21 @@ var EraserTool = class {
6303
6376
  erasing = false;
6304
6377
  radius;
6305
6378
  cursor;
6379
+ mode;
6306
6380
  constructor(options = {}) {
6307
6381
  this.radius = options.radius ?? DEFAULT_RADIUS;
6308
6382
  this.cursor = makeEraserCursor(this.radius);
6383
+ this.mode = options.mode ?? "partial";
6309
6384
  }
6310
6385
  getOptions() {
6311
- return { radius: this.radius };
6386
+ return { radius: this.radius, mode: this.mode };
6387
+ }
6388
+ setOptions(options) {
6389
+ if (options.mode !== void 0) this.mode = options.mode;
6390
+ if (options.radius !== void 0) {
6391
+ this.radius = options.radius;
6392
+ this.cursor = makeEraserCursor(this.radius);
6393
+ }
6312
6394
  }
6313
6395
  onActivate(ctx) {
6314
6396
  ctx.setCursor?.(this.cursor);
@@ -6341,10 +6423,30 @@ var EraserTool = class {
6341
6423
  if (el.type !== "stroke") continue;
6342
6424
  if (ctx.isLayerVisible && !ctx.isLayerVisible(el.layerId)) continue;
6343
6425
  if (ctx.isLayerLocked && ctx.isLayerLocked(el.layerId)) continue;
6344
- if (this.strokeIntersects(el, world)) {
6426
+ if (!this.strokeIntersects(el, world)) continue;
6427
+ if (this.mode === "stroke") {
6345
6428
  ctx.store.remove(el.id);
6346
6429
  erased = true;
6430
+ continue;
6431
+ }
6432
+ const localEraser = { x: world.x - el.position.x, y: world.y - el.position.y };
6433
+ const runs = erasePoints(el.points, localEraser, this.radius);
6434
+ if (runs === null) continue;
6435
+ ctx.store.remove(el.id);
6436
+ for (const run of runs) {
6437
+ ctx.store.add(
6438
+ createStroke({
6439
+ points: run,
6440
+ color: el.color,
6441
+ width: el.width,
6442
+ opacity: el.opacity,
6443
+ layerId: el.layerId,
6444
+ zIndex: el.zIndex,
6445
+ position: el.position
6446
+ })
6447
+ );
6347
6448
  }
6449
+ erased = true;
6348
6450
  }
6349
6451
  if (erased) ctx.requestRender();
6350
6452
  }
@@ -7883,7 +7985,7 @@ var TemplateTool = class {
7883
7985
  };
7884
7986
 
7885
7987
  // src/index.ts
7886
- var VERSION = "0.28.0";
7988
+ var VERSION = "0.29.0";
7887
7989
  // Annotate the CommonJS export names for ESM import in node:
7888
7990
  0 && (module.exports = {
7889
7991
  ArrowTool,