@fieldnotes/core 0.46.0 → 0.47.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
@@ -1057,6 +1057,7 @@ declare class TemplateTool implements Tool {
1057
1057
  private gridType;
1058
1058
  private hexOrientation;
1059
1059
  private snapEnabled;
1060
+ private feetScaleUnit;
1060
1061
  private templateShape;
1061
1062
  private fillColor;
1062
1063
  private strokeColor;
@@ -1078,6 +1079,7 @@ declare class TemplateTool implements Tool {
1078
1079
  private renderHexOverlay;
1079
1080
  private computeRadius;
1080
1081
  private computeAngle;
1082
+ private drawFeetLabel;
1081
1083
  private snapToGrid;
1082
1084
  private notifyOptionsChange;
1083
1085
  }
@@ -1113,6 +1115,6 @@ declare class LaserTool implements Tool {
1113
1115
  private notifyOptionsChange;
1114
1116
  }
1115
1117
 
1116
- declare const VERSION = "0.46.0";
1118
+ declare const VERSION = "0.47.0";
1117
1119
 
1118
1120
  export { type ActiveFormats, type AlignEdge, type ArrowElement, type ArrowStrokeStyle, 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, type ElementChangeMeta, 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, IndexedDBAdapter, type IndexedDBAdapterOptions, LaserTool, type LaserToolOptions, type Layer, LayerManager, LocalStorageAdapter, MeasureTool, type MeasureToolOptions, type Measurement, MemoryAdapter, 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 StorageAdapter, type StrokeElement, type StrokePoint, type TemplateElement, type TemplateRenderStyle, 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
@@ -1057,6 +1057,7 @@ declare class TemplateTool implements Tool {
1057
1057
  private gridType;
1058
1058
  private hexOrientation;
1059
1059
  private snapEnabled;
1060
+ private feetScaleUnit;
1060
1061
  private templateShape;
1061
1062
  private fillColor;
1062
1063
  private strokeColor;
@@ -1078,6 +1079,7 @@ declare class TemplateTool implements Tool {
1078
1079
  private renderHexOverlay;
1079
1080
  private computeRadius;
1080
1081
  private computeAngle;
1082
+ private drawFeetLabel;
1081
1083
  private snapToGrid;
1082
1084
  private notifyOptionsChange;
1083
1085
  }
@@ -1113,6 +1115,6 @@ declare class LaserTool implements Tool {
1113
1115
  private notifyOptionsChange;
1114
1116
  }
1115
1117
 
1116
- declare const VERSION = "0.46.0";
1118
+ declare const VERSION = "0.47.0";
1117
1119
 
1118
1120
  export { type ActiveFormats, type AlignEdge, type ArrowElement, type ArrowStrokeStyle, 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, type ElementChangeMeta, 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, IndexedDBAdapter, type IndexedDBAdapterOptions, LaserTool, type LaserToolOptions, type Layer, LayerManager, LocalStorageAdapter, MeasureTool, type MeasureToolOptions, type Measurement, MemoryAdapter, 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 StorageAdapter, type StrokeElement, type StrokePoint, type TemplateElement, type TemplateRenderStyle, 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
@@ -612,8 +612,20 @@ var InputFilter = class _InputFilter {
612
612
 
613
613
  // src/elements/create-id.ts
614
614
  var counter = 0;
615
+ function randomClientComponent() {
616
+ if (typeof crypto !== "undefined" && typeof crypto.getRandomValues === "function") {
617
+ const bytes = new Uint8Array(8);
618
+ crypto.getRandomValues(bytes);
619
+ return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
620
+ }
621
+ return Math.random().toString(36).slice(2, 14);
622
+ }
623
+ var CLIENT = randomClientComponent();
624
+ function formatId(prefix, time, counter2, client) {
625
+ return `${prefix}_${time}_${counter2}_${client}`;
626
+ }
615
627
  function createId(prefix) {
616
- return `${prefix}_${Date.now().toString(36)}_${(counter++).toString(36)}`;
628
+ return formatId(prefix, Date.now().toString(36), (counter++).toString(36), CLIENT);
617
629
  }
618
630
 
619
631
  // src/core/geometry.ts
@@ -3226,6 +3238,48 @@ function drawHexPath(ctx, cx, cy, cellSize, orientation) {
3226
3238
  ctx.closePath();
3227
3239
  }
3228
3240
 
3241
+ // src/elements/renderers/template-measure.ts
3242
+ function renderTemplateFeetLabel(ctx, p) {
3243
+ if (p.feet <= 0) return;
3244
+ const directional = p.templateShape === "cone" || p.templateShape === "line";
3245
+ const dir = directional ? p.angle : 0;
3246
+ const length = p.templateShape === "square" ? p.radius / 2 : p.radius;
3247
+ const cos = Math.cos(dir);
3248
+ const sin = Math.sin(dir);
3249
+ const start = p.position;
3250
+ const end = { x: start.x + length * cos, y: start.y + length * sin };
3251
+ const midX = start.x + length / 2 * cos;
3252
+ const midY = start.y + length / 2 * sin;
3253
+ ctx.save();
3254
+ ctx.globalAlpha = 1;
3255
+ ctx.beginPath();
3256
+ ctx.setLineDash([4, 4]);
3257
+ ctx.strokeStyle = p.color;
3258
+ ctx.lineWidth = 1.5;
3259
+ ctx.moveTo(start.x, start.y);
3260
+ ctx.lineTo(end.x, end.y);
3261
+ ctx.stroke();
3262
+ ctx.setLineDash([]);
3263
+ const label = `${Math.round(p.feet)} ft`;
3264
+ const fontSize = Math.max(10, Math.min(14, p.radius * 0.15));
3265
+ ctx.font = `bold ${fontSize}px system-ui, sans-serif`;
3266
+ ctx.textAlign = "center";
3267
+ ctx.textBaseline = "bottom";
3268
+ const textY = midY - 4;
3269
+ const metrics = ctx.measureText(label);
3270
+ const padX = 4;
3271
+ const padY = 2;
3272
+ const textW = metrics.width + padX * 2;
3273
+ const textH = fontSize + padY * 2;
3274
+ ctx.fillStyle = "rgba(255, 255, 255, 0.85)";
3275
+ ctx.beginPath();
3276
+ ctx.roundRect(midX - textW / 2, textY - textH, textW, textH, 3);
3277
+ ctx.fill();
3278
+ ctx.fillStyle = p.color;
3279
+ ctx.fillText(label, midX, textY - padY);
3280
+ ctx.restore();
3281
+ }
3282
+
3229
3283
  // src/elements/renderers/template-renderer.ts
3230
3284
  function renderTemplate(ctx, template, store) {
3231
3285
  const grid = store?.getElementsByType("grid")[0];
@@ -3249,9 +3303,6 @@ function renderGeometricTemplate(ctx, template) {
3249
3303
  ctx.arc(cx, cy, r, 0, Math.PI * 2);
3250
3304
  ctx.fill();
3251
3305
  ctx.stroke();
3252
- if (template.radiusFeet != null && template.radiusFeet > 0) {
3253
- renderRadiusMarker(ctx, cx, cy, r, template.radiusFeet);
3254
- }
3255
3306
  break;
3256
3307
  case "square":
3257
3308
  ctx.fillRect(cx - r / 2, cy - r / 2, r, r);
@@ -3284,6 +3335,16 @@ function renderGeometricTemplate(ctx, template) {
3284
3335
  break;
3285
3336
  }
3286
3337
  }
3338
+ if (template.radiusFeet != null && template.radiusFeet > 0) {
3339
+ renderTemplateFeetLabel(ctx, {
3340
+ position: template.position,
3341
+ radius: template.radius,
3342
+ angle: template.angle,
3343
+ templateShape: template.templateShape,
3344
+ feet: template.radiusFeet,
3345
+ color: template.strokeColor
3346
+ });
3347
+ }
3287
3348
  ctx.restore();
3288
3349
  }
3289
3350
  function renderHexTemplate(ctx, template, cellSize, orientation) {
@@ -3330,44 +3391,18 @@ function renderHexTemplate(ctx, template, cellSize, orientation) {
3330
3391
  ctx.lineWidth = template.strokeWidth;
3331
3392
  ctx.stroke();
3332
3393
  }
3333
- if (template.templateShape === "circle" && template.radiusFeet != null && template.radiusFeet > 0) {
3334
- const r = template.radius;
3335
- renderRadiusMarker(ctx, center2.x, center2.y, r, template.radiusFeet);
3394
+ if (template.radiusFeet != null && template.radiusFeet > 0) {
3395
+ renderTemplateFeetLabel(ctx, {
3396
+ position: template.position,
3397
+ radius: template.radius,
3398
+ angle: template.angle,
3399
+ templateShape: template.templateShape,
3400
+ feet: template.radiusFeet,
3401
+ color: template.strokeColor
3402
+ });
3336
3403
  }
3337
3404
  ctx.restore();
3338
3405
  }
3339
- function renderRadiusMarker(ctx, cx, cy, r, feet) {
3340
- const markerColor = ctx.strokeStyle;
3341
- ctx.save();
3342
- ctx.globalAlpha = 1;
3343
- ctx.beginPath();
3344
- ctx.setLineDash([4, 4]);
3345
- ctx.strokeStyle = markerColor;
3346
- ctx.lineWidth = 1.5;
3347
- ctx.moveTo(cx, cy);
3348
- ctx.lineTo(cx + r, cy);
3349
- ctx.stroke();
3350
- ctx.setLineDash([]);
3351
- const label = `${Math.round(feet)} ft`;
3352
- const fontSize = Math.max(10, Math.min(14, r * 0.15));
3353
- ctx.font = `bold ${fontSize}px system-ui, sans-serif`;
3354
- ctx.textAlign = "center";
3355
- ctx.textBaseline = "bottom";
3356
- const textX = cx + r / 2;
3357
- const textY = cy - 4;
3358
- const metrics = ctx.measureText(label);
3359
- const padX = 4;
3360
- const padY = 2;
3361
- const textW = metrics.width + padX * 2;
3362
- const textH = fontSize + padY * 2;
3363
- ctx.fillStyle = "rgba(255, 255, 255, 0.85)";
3364
- ctx.beginPath();
3365
- ctx.roundRect(textX - textW / 2, textY - textH, textW, textH, 3);
3366
- ctx.fill();
3367
- ctx.fillStyle = markerColor;
3368
- ctx.fillText(label, textX, textY - padY);
3369
- ctx.restore();
3370
- }
3371
3406
 
3372
3407
  // src/elements/grid-renderer.ts
3373
3408
  function getSquareGridLines(bounds, cellSize) {
@@ -8213,6 +8248,20 @@ function getOverlayLayout(el, zoom) {
8213
8248
  const rotateHandle = rotatePoint(topMid, center2, angle);
8214
8249
  return { center: center2, corners, rotateHandle, angle };
8215
8250
  }
8251
+ function templateAimKnob(el, zoom) {
8252
+ if (el.type !== "template") return null;
8253
+ if (el.templateShape !== "cone" && el.templateShape !== "line") return null;
8254
+ const gap = ROTATE_HANDLE_OFFSET / zoom;
8255
+ const dist = el.radius + gap;
8256
+ const origin = el.position;
8257
+ return {
8258
+ origin,
8259
+ knob: {
8260
+ x: origin.x + dist * Math.cos(el.angle),
8261
+ y: origin.y + dist * Math.sin(el.angle)
8262
+ }
8263
+ };
8264
+ }
8216
8265
  function getHandlePositions(bounds) {
8217
8266
  return [
8218
8267
  ["nw", { x: bounds.x, y: bounds.y }],
@@ -8361,6 +8410,22 @@ function renderSelectionBoxes(ctx, p) {
8361
8410
  handleWorldSize
8362
8411
  );
8363
8412
  ctx.setLineDash([4 / zoom, 4 / zoom]);
8413
+ if (p.selectedIds.length === 1 && (el.templateShape === "cone" || el.templateShape === "line")) {
8414
+ const aim = templateAimKnob(el, zoom);
8415
+ if (aim) {
8416
+ ctx.beginPath();
8417
+ ctx.moveTo(aim.origin.x, aim.origin.y);
8418
+ ctx.lineTo(aim.knob.x, aim.knob.y);
8419
+ ctx.stroke();
8420
+ ctx.setLineDash([]);
8421
+ ctx.fillStyle = "#ffffff";
8422
+ ctx.beginPath();
8423
+ ctx.arc(aim.knob.x, aim.knob.y, handleWorldSize / 2, 0, Math.PI * 2);
8424
+ ctx.fill();
8425
+ ctx.stroke();
8426
+ ctx.setLineDash([4 / zoom, 4 / zoom]);
8427
+ }
8428
+ }
8364
8429
  }
8365
8430
  if (p.selectedIds.length === 1 && ROTATABLE_TYPES.has(el.type)) {
8366
8431
  const stemStart = topMidpoint(layout);
@@ -8516,6 +8581,19 @@ function hitTestTemplateResizeHandle(world, ctx, selectedIds) {
8516
8581
  }
8517
8582
  return null;
8518
8583
  }
8584
+ function hitTestTemplateAimHandle(world, ctx, selectedIds) {
8585
+ if (selectedIds.length !== 1) return null;
8586
+ const id = selectedIds[0];
8587
+ if (!id) return null;
8588
+ const el = ctx.store.getById(id);
8589
+ if (!el || el.locked) return null;
8590
+ const knob = templateAimKnob(el, ctx.camera.zoom);
8591
+ if (!knob) return null;
8592
+ const r = (HANDLE_SIZE / 2 + HANDLE_HIT_PADDING2) / ctx.camera.zoom;
8593
+ const dx = world.x - knob.knob.x;
8594
+ const dy = world.y - knob.knob.y;
8595
+ return dx * dx + dy * dy <= r * r ? { elementId: id } : null;
8596
+ }
8519
8597
  function findElementsInRect(marquee, ctx) {
8520
8598
  const candidates = ctx.store.queryRect(marquee);
8521
8599
  const ids = [];
@@ -8765,6 +8843,12 @@ var SelectTool = class {
8765
8843
  ctx.requestRender();
8766
8844
  return;
8767
8845
  }
8846
+ const aimHit = hitTestTemplateAimHandle(world, ctx, this._selectedIds);
8847
+ if (aimHit) {
8848
+ this.mode = { type: "aiming-template", elementId: aimHit.elementId };
8849
+ ctx.requestRender();
8850
+ return;
8851
+ }
8768
8852
  const rotateHit = hitTestRotateHandle(world, ctx, this._selectedIds);
8769
8853
  if (rotateHit) {
8770
8854
  const el = ctx.store.getById(rotateHit.elementId);
@@ -8846,6 +8930,19 @@ var SelectTool = class {
8846
8930
  this.handleTemplateResize(world, ctx);
8847
8931
  return;
8848
8932
  }
8933
+ if (this.mode.type === "aiming-template") {
8934
+ const el = ctx.store.getById(this.mode.elementId);
8935
+ if (el && el.type === "template" && !el.locked) {
8936
+ let a = Math.atan2(world.y - el.position.y, world.x - el.position.x);
8937
+ if (state.shiftKey) {
8938
+ const snap = ctx.gridType === "hex" ? Math.PI / 3 : ROTATE_SNAP;
8939
+ a = Math.round(a / snap) * snap;
8940
+ }
8941
+ ctx.store.update(this.mode.elementId, { angle: normalizeAngle(a) });
8942
+ ctx.requestRender();
8943
+ }
8944
+ return;
8945
+ }
8849
8946
  if (this.mode.type === "rotating") {
8850
8947
  const { elementId, center: center2, startPointerAngle, startRotation } = this.mode;
8851
8948
  const a = Math.atan2(world.y - center2.y, world.x - center2.x);
@@ -9052,6 +9149,10 @@ var SelectTool = class {
9052
9149
  ctx.setCursor?.("nwse-resize");
9053
9150
  return null;
9054
9151
  }
9152
+ if (hitTestTemplateAimHandle(world, ctx, this._selectedIds)) {
9153
+ ctx.setCursor?.("grab");
9154
+ return null;
9155
+ }
9055
9156
  if (hitTestRotateHandle(world, ctx, this._selectedIds)) {
9056
9157
  ctx.setCursor?.("grab");
9057
9158
  return null;
@@ -9721,6 +9822,7 @@ var TemplateTool = class {
9721
9822
  gridType;
9722
9823
  hexOrientation;
9723
9824
  snapEnabled = false;
9825
+ feetScaleUnit = 0;
9724
9826
  templateShape;
9725
9827
  fillColor;
9726
9828
  strokeColor;
@@ -9769,6 +9871,7 @@ var TemplateTool = class {
9769
9871
  this.gridType = ctx.gridType;
9770
9872
  this.hexOrientation = ctx.hexOrientation;
9771
9873
  this.snapEnabled = !!ctx.gridType || (ctx.snapToGrid ?? false);
9874
+ this.feetScaleUnit = ctx.gridSize && ctx.gridSize > 0 ? ctx.gridType === "hex" ? Math.sqrt(3) * ctx.gridSize : ctx.gridSize : 0;
9772
9875
  const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
9773
9876
  this.origin = this.snapToGrid(world, ctx);
9774
9877
  this.current = { ...this.origin };
@@ -9868,6 +9971,7 @@ var TemplateTool = class {
9868
9971
  break;
9869
9972
  }
9870
9973
  }
9974
+ this.drawFeetLabel(ctx, radius);
9871
9975
  ctx.restore();
9872
9976
  }
9873
9977
  renderHexOverlay(ctx, radius) {
@@ -9918,30 +10022,7 @@ var TemplateTool = class {
9918
10022
  ctx.lineWidth = this.strokeWidth;
9919
10023
  ctx.stroke();
9920
10024
  }
9921
- if (this.templateShape === "circle") {
9922
- const feet = radiusCells * this.feetPerCell;
9923
- if (feet > 0) {
9924
- ctx.globalAlpha = 1;
9925
- const label = `${Math.round(feet)} ft`;
9926
- const fontSize = Math.max(10, Math.min(14, radius * 0.15));
9927
- ctx.font = `bold ${fontSize}px system-ui, sans-serif`;
9928
- ctx.textAlign = "center";
9929
- ctx.textBaseline = "bottom";
9930
- const textX = center2.x;
9931
- const textY = center2.y - 4;
9932
- const metrics = ctx.measureText(label);
9933
- const padX = 4;
9934
- const padY = 2;
9935
- const textW = metrics.width + padX * 2;
9936
- const textH = fontSize + padY * 2;
9937
- ctx.fillStyle = "rgba(255, 255, 255, 0.85)";
9938
- ctx.beginPath();
9939
- ctx.roundRect(textX - textW / 2, textY - textH, textW, textH, 3);
9940
- ctx.fill();
9941
- ctx.fillStyle = this.strokeColor;
9942
- ctx.fillText(label, textX, textY - padY);
9943
- }
9944
- }
10025
+ this.drawFeetLabel(ctx, radius);
9945
10026
  ctx.restore();
9946
10027
  }
9947
10028
  computeRadius() {
@@ -9959,6 +10040,17 @@ var TemplateTool = class {
9959
10040
  const dy = this.current.y - this.origin.y;
9960
10041
  return Math.atan2(dy, dx);
9961
10042
  }
10043
+ drawFeetLabel(ctx, radius) {
10044
+ const feet = this.feetScaleUnit > 0 ? radius / this.feetScaleUnit * this.feetPerCell : 0;
10045
+ renderTemplateFeetLabel(ctx, {
10046
+ position: this.origin,
10047
+ radius,
10048
+ angle: this.computeAngle(),
10049
+ templateShape: this.templateShape,
10050
+ feet,
10051
+ color: this.strokeColor
10052
+ });
10053
+ }
9962
10054
  snapToGrid(point, ctx) {
9963
10055
  if (!ctx.gridSize) return point;
9964
10056
  if (ctx.gridType === "hex" && ctx.hexOrientation) {
@@ -10088,7 +10180,7 @@ var LaserTool = class {
10088
10180
  };
10089
10181
 
10090
10182
  // src/index.ts
10091
- var VERSION = "0.46.0";
10183
+ var VERSION = "0.47.0";
10092
10184
  export {
10093
10185
  ArrowTool,
10094
10186
  AutoSave,