@fieldnotes/core 0.39.0 → 0.40.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.d.cts CHANGED
@@ -233,7 +233,7 @@ interface Tool {
233
233
  setOptions?(options: object): void;
234
234
  onOptionsChange?(listener: () => void): () => void;
235
235
  }
236
- type ToolName = 'hand' | 'select' | 'pencil' | 'eraser' | 'arrow' | 'note' | 'image' | 'text' | 'shape' | 'measure' | 'template';
236
+ type ToolName = 'hand' | 'select' | 'pencil' | 'eraser' | 'arrow' | 'note' | 'image' | 'text' | 'shape' | 'measure' | 'template' | 'laser';
237
237
 
238
238
  declare function snapPoint(point: Point, gridSize: number): Point;
239
239
  declare function snapToHexCenter(point: Point, cellSize: number, orientation: HexOrientation): Point;
@@ -1017,6 +1017,37 @@ declare class TemplateTool implements Tool {
1017
1017
  private notifyOptionsChange;
1018
1018
  }
1019
1019
 
1020
- declare const VERSION = "0.39.0";
1020
+ interface LaserToolOptions {
1021
+ name?: string;
1022
+ color?: string;
1023
+ width?: number;
1024
+ fadeMs?: number;
1025
+ }
1026
+ declare class LaserTool implements Tool {
1027
+ readonly name: string;
1028
+ private color;
1029
+ private width;
1030
+ private fadeMs;
1031
+ private trail;
1032
+ private rafId;
1033
+ private drawing;
1034
+ private optionListeners;
1035
+ constructor(options?: LaserToolOptions);
1036
+ private now;
1037
+ onActivate(ctx: ToolContext): void;
1038
+ onDeactivate(ctx: ToolContext): void;
1039
+ getOptions(): LaserToolOptions;
1040
+ onOptionsChange(listener: () => void): () => void;
1041
+ setOptions(options: LaserToolOptions): void;
1042
+ onPointerDown(state: PointerState, ctx: ToolContext): void;
1043
+ onPointerMove(state: PointerState, ctx: ToolContext): void;
1044
+ onPointerUp(_state: PointerState, _ctx: ToolContext): void;
1045
+ renderOverlay(ctx: CanvasRenderingContext2D): void;
1046
+ private ensureAnimating;
1047
+ private tick;
1048
+ private notifyOptionsChange;
1049
+ }
1050
+
1051
+ declare const VERSION = "0.40.0";
1021
1052
 
1022
- export { type ActiveFormats, type AlignEdge, type ArrowElement, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, type BackgroundOptions, type BackgroundPattern, type Binding, type Bounds, Camera, type CameraChangeInfo, type CameraOptions, type CanvasElement, type CanvasState, type Command, DEFAULT_NOTE_FONT_SIZE, type DistributeAxis, ElementStore, type ElementStyle, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, type ExportImageOptions, type ExportSvgOptions, type FontSizePreset, type GridElement, type GridInfo, HandTool, type HexOrientation, HistoryStack, type HistoryStackOptions, type HtmlElement, type ImageElement, ImageTool, type ImageToolOptions, type Layer, LayerManager, MeasureTool, type MeasureToolOptions, type Measurement, type NoteElement, NoteTool, type NoteToolOptions, PencilTool, type PencilToolOptions, type Point, type PointerState, type RenderStatsSnapshot, SelectTool, type ShapeElement, type ShapeKind, ShapeTool, type ShapeToolOptions, type ShortcutBindings, type ShortcutOptions, type ShortcutsApi, type Size, type StrokeElement, type StrokePoint, type TemplateElement, type TemplateShape, TemplateTool, type TemplateToolOptions, type TextElement, TextTool, type TextToolOptions, type Tool, type ToolContext, ToolManager, type ToolName, VERSION, Viewport, type ViewportOptions, boundsIntersect, createArrow, createGrid, createHtmlElement, createImage, createNote, createShape, createStroke, createTemplate, createText, drawHexPath, exportImage, exportSvg, getActiveFormats, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, getElementBounds, getElementStyle, getElementsBoundingBox, getHexCellsInCone, getHexCellsInLine, getHexCellsInRadius, getHexCellsInSquare, getHexDistance, isNearBezier, setFontSize, smartSnap, snapPoint, snapToHexCenter, styleToPatch, toggleBold, toggleItalic, toggleStrikethrough, toggleUnderline };
1053
+ export { type ActiveFormats, type AlignEdge, type ArrowElement, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, type BackgroundOptions, type BackgroundPattern, type Binding, type Bounds, Camera, type CameraChangeInfo, type CameraOptions, type CanvasElement, type CanvasState, type Command, DEFAULT_NOTE_FONT_SIZE, type DistributeAxis, ElementStore, type ElementStyle, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, type ExportImageOptions, type ExportSvgOptions, type FontSizePreset, type GridElement, type GridInfo, HandTool, type HexOrientation, HistoryStack, type HistoryStackOptions, type HtmlElement, type ImageElement, ImageTool, type ImageToolOptions, LaserTool, type LaserToolOptions, type Layer, LayerManager, MeasureTool, type MeasureToolOptions, type Measurement, type NoteElement, NoteTool, type NoteToolOptions, PencilTool, type PencilToolOptions, type Point, type PointerState, type RenderStatsSnapshot, SelectTool, type ShapeElement, type ShapeKind, ShapeTool, type ShapeToolOptions, type ShortcutBindings, type ShortcutOptions, type ShortcutsApi, type Size, type StrokeElement, type StrokePoint, type TemplateElement, type TemplateShape, TemplateTool, type TemplateToolOptions, type TextElement, TextTool, type TextToolOptions, type Tool, type ToolContext, ToolManager, type ToolName, VERSION, Viewport, type ViewportOptions, boundsIntersect, createArrow, createGrid, createHtmlElement, createImage, createNote, createShape, createStroke, createTemplate, createText, drawHexPath, exportImage, exportSvg, getActiveFormats, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, getElementBounds, getElementStyle, getElementsBoundingBox, getHexCellsInCone, getHexCellsInLine, getHexCellsInRadius, getHexCellsInSquare, getHexDistance, isNearBezier, setFontSize, smartSnap, snapPoint, snapToHexCenter, styleToPatch, toggleBold, toggleItalic, toggleStrikethrough, toggleUnderline };
package/dist/index.d.ts CHANGED
@@ -233,7 +233,7 @@ interface Tool {
233
233
  setOptions?(options: object): void;
234
234
  onOptionsChange?(listener: () => void): () => void;
235
235
  }
236
- type ToolName = 'hand' | 'select' | 'pencil' | 'eraser' | 'arrow' | 'note' | 'image' | 'text' | 'shape' | 'measure' | 'template';
236
+ type ToolName = 'hand' | 'select' | 'pencil' | 'eraser' | 'arrow' | 'note' | 'image' | 'text' | 'shape' | 'measure' | 'template' | 'laser';
237
237
 
238
238
  declare function snapPoint(point: Point, gridSize: number): Point;
239
239
  declare function snapToHexCenter(point: Point, cellSize: number, orientation: HexOrientation): Point;
@@ -1017,6 +1017,37 @@ declare class TemplateTool implements Tool {
1017
1017
  private notifyOptionsChange;
1018
1018
  }
1019
1019
 
1020
- declare const VERSION = "0.39.0";
1020
+ interface LaserToolOptions {
1021
+ name?: string;
1022
+ color?: string;
1023
+ width?: number;
1024
+ fadeMs?: number;
1025
+ }
1026
+ declare class LaserTool implements Tool {
1027
+ readonly name: string;
1028
+ private color;
1029
+ private width;
1030
+ private fadeMs;
1031
+ private trail;
1032
+ private rafId;
1033
+ private drawing;
1034
+ private optionListeners;
1035
+ constructor(options?: LaserToolOptions);
1036
+ private now;
1037
+ onActivate(ctx: ToolContext): void;
1038
+ onDeactivate(ctx: ToolContext): void;
1039
+ getOptions(): LaserToolOptions;
1040
+ onOptionsChange(listener: () => void): () => void;
1041
+ setOptions(options: LaserToolOptions): void;
1042
+ onPointerDown(state: PointerState, ctx: ToolContext): void;
1043
+ onPointerMove(state: PointerState, ctx: ToolContext): void;
1044
+ onPointerUp(_state: PointerState, _ctx: ToolContext): void;
1045
+ renderOverlay(ctx: CanvasRenderingContext2D): void;
1046
+ private ensureAnimating;
1047
+ private tick;
1048
+ private notifyOptionsChange;
1049
+ }
1050
+
1051
+ declare const VERSION = "0.40.0";
1021
1052
 
1022
- export { type ActiveFormats, type AlignEdge, type ArrowElement, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, type BackgroundOptions, type BackgroundPattern, type Binding, type Bounds, Camera, type CameraChangeInfo, type CameraOptions, type CanvasElement, type CanvasState, type Command, DEFAULT_NOTE_FONT_SIZE, type DistributeAxis, ElementStore, type ElementStyle, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, type ExportImageOptions, type ExportSvgOptions, type FontSizePreset, type GridElement, type GridInfo, HandTool, type HexOrientation, HistoryStack, type HistoryStackOptions, type HtmlElement, type ImageElement, ImageTool, type ImageToolOptions, type Layer, LayerManager, MeasureTool, type MeasureToolOptions, type Measurement, type NoteElement, NoteTool, type NoteToolOptions, PencilTool, type PencilToolOptions, type Point, type PointerState, type RenderStatsSnapshot, SelectTool, type ShapeElement, type ShapeKind, ShapeTool, type ShapeToolOptions, type ShortcutBindings, type ShortcutOptions, type ShortcutsApi, type Size, type StrokeElement, type StrokePoint, type TemplateElement, type TemplateShape, TemplateTool, type TemplateToolOptions, type TextElement, TextTool, type TextToolOptions, type Tool, type ToolContext, ToolManager, type ToolName, VERSION, Viewport, type ViewportOptions, boundsIntersect, createArrow, createGrid, createHtmlElement, createImage, createNote, createShape, createStroke, createTemplate, createText, drawHexPath, exportImage, exportSvg, getActiveFormats, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, getElementBounds, getElementStyle, getElementsBoundingBox, getHexCellsInCone, getHexCellsInLine, getHexCellsInRadius, getHexCellsInSquare, getHexDistance, isNearBezier, setFontSize, smartSnap, snapPoint, snapToHexCenter, styleToPatch, toggleBold, toggleItalic, toggleStrikethrough, toggleUnderline };
1053
+ export { type ActiveFormats, type AlignEdge, type ArrowElement, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, type BackgroundOptions, type BackgroundPattern, type Binding, type Bounds, Camera, type CameraChangeInfo, type CameraOptions, type CanvasElement, type CanvasState, type Command, DEFAULT_NOTE_FONT_SIZE, type DistributeAxis, ElementStore, type ElementStyle, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, type ExportImageOptions, type ExportSvgOptions, type FontSizePreset, type GridElement, type GridInfo, HandTool, type HexOrientation, HistoryStack, type HistoryStackOptions, type HtmlElement, type ImageElement, ImageTool, type ImageToolOptions, LaserTool, type LaserToolOptions, type Layer, LayerManager, MeasureTool, type MeasureToolOptions, type Measurement, type NoteElement, NoteTool, type NoteToolOptions, PencilTool, type PencilToolOptions, type Point, type PointerState, type RenderStatsSnapshot, SelectTool, type ShapeElement, type ShapeKind, ShapeTool, type ShapeToolOptions, type ShortcutBindings, type ShortcutOptions, type ShortcutsApi, type Size, type StrokeElement, type StrokePoint, type TemplateElement, type TemplateShape, TemplateTool, type TemplateToolOptions, type TextElement, TextTool, type TextToolOptions, type Tool, type ToolContext, ToolManager, type ToolName, VERSION, Viewport, type ViewportOptions, boundsIntersect, createArrow, createGrid, createHtmlElement, createImage, createNote, createShape, createStroke, createTemplate, createText, drawHexPath, exportImage, exportSvg, getActiveFormats, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, getElementBounds, getElementStyle, getElementsBoundingBox, getHexCellsInCone, getHexCellsInLine, getHexCellsInRadius, getHexCellsInSquare, getHexDistance, isNearBezier, setFontSize, smartSnap, snapPoint, snapToHexCenter, styleToPatch, toggleBold, toggleItalic, toggleStrikethrough, toggleUnderline };
package/dist/index.js CHANGED
@@ -831,6 +831,11 @@ var KeyboardActions = class {
831
831
  if (tool?.name !== "select") return null;
832
832
  return { tool, ctx };
833
833
  }
834
+ selectableElements(ctx) {
835
+ return ctx.store.getAll().filter(
836
+ (el) => !el.locked && (ctx.isLayerVisible?.(el.layerId) ?? true) && !(ctx.isLayerLocked?.(el.layerId) ?? false)
837
+ );
838
+ }
834
839
  nudge(dx, dy, byCell) {
835
840
  if (this.deps.isToolActive()) return false;
836
841
  const sel = this.selectTool();
@@ -973,12 +978,28 @@ var KeyboardActions = class {
973
978
  }
974
979
  const sel = this.selectTool();
975
980
  if (!sel) return;
976
- const ids = sel.ctx.store.getAll().filter(
977
- (el) => !el.locked && (sel.ctx.isLayerVisible?.(el.layerId) ?? true) && !(sel.ctx.isLayerLocked?.(el.layerId) ?? false)
978
- ).map((el) => el.id);
981
+ const ids = this.selectableElements(sel.ctx).map((el) => el.id);
979
982
  sel.tool.setSelection(ids);
980
983
  sel.ctx.requestRender();
981
984
  }
985
+ cycleSelection(direction) {
986
+ if (this.deps.isToolActive()) return;
987
+ const tm = this.deps.getToolManager();
988
+ const ctx = this.deps.getToolContext();
989
+ if (!tm || !ctx) return;
990
+ if (tm.activeTool?.name !== "select") ctx.switchTool?.("select");
991
+ const sel = this.selectTool();
992
+ if (!sel) return;
993
+ const eligible = this.selectableElements(sel.ctx).filter((el) => el.type !== "grid");
994
+ if (eligible.length === 0) return;
995
+ const idxs = sel.tool.selectedIds.map((id) => eligible.findIndex((e) => e.id === id)).filter((i) => i >= 0);
996
+ const anchor = idxs.length === 0 ? direction > 0 ? -1 : 0 : direction > 0 ? Math.max(...idxs) : Math.min(...idxs);
997
+ const next = (anchor + direction + eligible.length) % eligible.length;
998
+ const target = eligible[next];
999
+ if (!target) return;
1000
+ sel.tool.setSelection([target.id]);
1001
+ sel.ctx.requestRender();
1002
+ }
982
1003
  zoomToFit() {
983
1004
  if (this.deps.isToolActive()) return;
984
1005
  this.deps.fitToContent?.();
@@ -1083,6 +1104,8 @@ var DEFAULT_BINDINGS = [
1083
1104
  ["undo", ["mod+z"]],
1084
1105
  ["redo", ["mod+y", "mod+shift+z"]],
1085
1106
  ["select-all", ["mod+a"]],
1107
+ ["cycle-selection", ["tab"]],
1108
+ ["cycle-selection-reverse", ["shift+tab"]],
1086
1109
  ["copy", ["mod+c"]],
1087
1110
  ["duplicate", ["mod+d"]],
1088
1111
  ["z-forward", ["]"]],
@@ -1353,6 +1376,14 @@ var KeyboardHandler = class {
1353
1376
  e?.preventDefault();
1354
1377
  this.deps.actions.selectAll();
1355
1378
  return;
1379
+ case "cycle-selection":
1380
+ e?.preventDefault();
1381
+ this.deps.actions.cycleSelection(1);
1382
+ return;
1383
+ case "cycle-selection-reverse":
1384
+ e?.preventDefault();
1385
+ this.deps.actions.cycleSelection(-1);
1386
+ return;
1356
1387
  case "copy":
1357
1388
  e?.preventDefault();
1358
1389
  this.deps.actions.copy();
@@ -7691,6 +7722,17 @@ function renderArrowHandles(canvasCtx, arrow, zoom) {
7691
7722
  canvasCtx.stroke();
7692
7723
  }
7693
7724
  }
7725
+ function renderArrowHoverHandle(canvasCtx, arrow, zoom) {
7726
+ const mid = getArrowMidpoint(arrow.from, arrow.to, arrow.bend);
7727
+ const radius = HANDLE_RADIUS / zoom;
7728
+ canvasCtx.fillStyle = "#2196F3";
7729
+ canvasCtx.strokeStyle = "#2196F3";
7730
+ canvasCtx.lineWidth = 1.5 / zoom;
7731
+ canvasCtx.beginPath();
7732
+ canvasCtx.arc(mid.x, mid.y, radius, 0, Math.PI * 2);
7733
+ canvasCtx.fill();
7734
+ canvasCtx.stroke();
7735
+ }
7694
7736
 
7695
7737
  // src/elements/snap-guides.ts
7696
7738
  function xAnchors(b) {
@@ -8539,7 +8581,13 @@ var SelectTool = class {
8539
8581
  if (this.hoveredId && this.ctx && this.mode.type === "idle") {
8540
8582
  if (!this._selectedIds.includes(this.hoveredId)) {
8541
8583
  const el = this.ctx.store.getById(this.hoveredId);
8542
- if (el) {
8584
+ if (el?.type === "arrow") {
8585
+ canvasCtx.save();
8586
+ canvasCtx.globalAlpha = 0.35;
8587
+ canvasCtx.setLineDash([]);
8588
+ renderArrowHoverHandle(canvasCtx, el, this.ctx.camera.zoom);
8589
+ canvasCtx.restore();
8590
+ } else if (el) {
8543
8591
  const b = getElementBounds(el);
8544
8592
  if (b) {
8545
8593
  canvasCtx.save();
@@ -9510,8 +9558,118 @@ var TemplateTool = class {
9510
9558
  }
9511
9559
  };
9512
9560
 
9561
+ // src/tools/laser-tool.ts
9562
+ var DEFAULT_COLOR = "#ff3b30";
9563
+ var DEFAULT_WIDTH = 4;
9564
+ var DEFAULT_FADE_MS = 1200;
9565
+ var LaserTool = class {
9566
+ name;
9567
+ color;
9568
+ width;
9569
+ fadeMs;
9570
+ trail = [];
9571
+ rafId = null;
9572
+ drawing = false;
9573
+ optionListeners = /* @__PURE__ */ new Set();
9574
+ constructor(options = {}) {
9575
+ this.name = options.name ?? "laser";
9576
+ this.color = options.color ?? DEFAULT_COLOR;
9577
+ this.width = options.width ?? DEFAULT_WIDTH;
9578
+ this.fadeMs = options.fadeMs ?? DEFAULT_FADE_MS;
9579
+ }
9580
+ now() {
9581
+ return performance.now();
9582
+ }
9583
+ onActivate(ctx) {
9584
+ ctx.setCursor?.("crosshair");
9585
+ }
9586
+ onDeactivate(ctx) {
9587
+ if (this.rafId !== null) {
9588
+ cancelAnimationFrame(this.rafId);
9589
+ this.rafId = null;
9590
+ }
9591
+ this.trail = [];
9592
+ this.drawing = false;
9593
+ ctx.setCursor?.("default");
9594
+ ctx.requestRender();
9595
+ }
9596
+ getOptions() {
9597
+ return {
9598
+ name: this.name,
9599
+ color: this.color,
9600
+ width: this.width,
9601
+ fadeMs: this.fadeMs
9602
+ };
9603
+ }
9604
+ onOptionsChange(listener) {
9605
+ this.optionListeners.add(listener);
9606
+ return () => this.optionListeners.delete(listener);
9607
+ }
9608
+ setOptions(options) {
9609
+ if (options.color !== void 0) this.color = options.color;
9610
+ if (options.width !== void 0) this.width = options.width;
9611
+ if (options.fadeMs !== void 0) this.fadeMs = options.fadeMs;
9612
+ this.notifyOptionsChange();
9613
+ }
9614
+ onPointerDown(state, ctx) {
9615
+ this.drawing = true;
9616
+ const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
9617
+ this.trail.push({ x: world.x, y: world.y, t: this.now() });
9618
+ this.ensureAnimating(ctx);
9619
+ }
9620
+ onPointerMove(state, ctx) {
9621
+ if (!this.drawing) return;
9622
+ const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
9623
+ this.trail.push({ x: world.x, y: world.y, t: this.now() });
9624
+ this.ensureAnimating(ctx);
9625
+ }
9626
+ onPointerUp(_state, _ctx) {
9627
+ this.drawing = false;
9628
+ }
9629
+ renderOverlay(ctx) {
9630
+ if (this.trail.length < 2) return;
9631
+ ctx.save();
9632
+ ctx.strokeStyle = this.color;
9633
+ ctx.lineWidth = this.width;
9634
+ ctx.lineCap = "round";
9635
+ ctx.lineJoin = "round";
9636
+ const now = this.now();
9637
+ for (let i = 0; i < this.trail.length - 1; i++) {
9638
+ const a = this.trail[i];
9639
+ const b = this.trail[i + 1];
9640
+ if (!a || !b) continue;
9641
+ const age = now - b.t;
9642
+ ctx.globalAlpha = Math.max(0, 1 - age / this.fadeMs);
9643
+ ctx.beginPath();
9644
+ ctx.moveTo(a.x, a.y);
9645
+ ctx.lineTo(b.x, b.y);
9646
+ ctx.stroke();
9647
+ }
9648
+ ctx.restore();
9649
+ }
9650
+ ensureAnimating(ctx) {
9651
+ if (this.rafId === null) {
9652
+ this.rafId = requestAnimationFrame(() => this.tick(ctx));
9653
+ }
9654
+ }
9655
+ tick(ctx) {
9656
+ const cutoff = this.now() - this.fadeMs;
9657
+ this.trail = this.trail.filter((p) => p.t >= cutoff);
9658
+ if (this.trail.length > 0) {
9659
+ ctx.requestRender();
9660
+ this.rafId = requestAnimationFrame(() => this.tick(ctx));
9661
+ } else {
9662
+ ctx.requestRender();
9663
+ this.rafId = null;
9664
+ }
9665
+ }
9666
+ notifyOptionsChange() {
9667
+ for (const listener of this.optionListeners) listener();
9668
+ }
9669
+ };
9670
+
9513
9671
  // src/index.ts
9514
- var VERSION = "0.39.0";
9672
+ var VERSION = "0.40.0";
9515
9673
  export {
9516
9674
  ArrowTool,
9517
9675
  AutoSave,
@@ -9522,6 +9680,7 @@ export {
9522
9680
  HandTool,
9523
9681
  HistoryStack,
9524
9682
  ImageTool,
9683
+ LaserTool,
9525
9684
  LayerManager,
9526
9685
  MeasureTool,
9527
9686
  NoteTool,