@fieldnotes/core 0.46.1 → 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.cjs CHANGED
@@ -3324,6 +3324,48 @@ function drawHexPath(ctx, cx, cy, cellSize, orientation) {
3324
3324
  ctx.closePath();
3325
3325
  }
3326
3326
 
3327
+ // src/elements/renderers/template-measure.ts
3328
+ function renderTemplateFeetLabel(ctx, p) {
3329
+ if (p.feet <= 0) return;
3330
+ const directional = p.templateShape === "cone" || p.templateShape === "line";
3331
+ const dir = directional ? p.angle : 0;
3332
+ const length = p.templateShape === "square" ? p.radius / 2 : p.radius;
3333
+ const cos = Math.cos(dir);
3334
+ const sin = Math.sin(dir);
3335
+ const start = p.position;
3336
+ const end = { x: start.x + length * cos, y: start.y + length * sin };
3337
+ const midX = start.x + length / 2 * cos;
3338
+ const midY = start.y + length / 2 * sin;
3339
+ ctx.save();
3340
+ ctx.globalAlpha = 1;
3341
+ ctx.beginPath();
3342
+ ctx.setLineDash([4, 4]);
3343
+ ctx.strokeStyle = p.color;
3344
+ ctx.lineWidth = 1.5;
3345
+ ctx.moveTo(start.x, start.y);
3346
+ ctx.lineTo(end.x, end.y);
3347
+ ctx.stroke();
3348
+ ctx.setLineDash([]);
3349
+ const label = `${Math.round(p.feet)} ft`;
3350
+ const fontSize = Math.max(10, Math.min(14, p.radius * 0.15));
3351
+ ctx.font = `bold ${fontSize}px system-ui, sans-serif`;
3352
+ ctx.textAlign = "center";
3353
+ ctx.textBaseline = "bottom";
3354
+ const textY = midY - 4;
3355
+ const metrics = ctx.measureText(label);
3356
+ const padX = 4;
3357
+ const padY = 2;
3358
+ const textW = metrics.width + padX * 2;
3359
+ const textH = fontSize + padY * 2;
3360
+ ctx.fillStyle = "rgba(255, 255, 255, 0.85)";
3361
+ ctx.beginPath();
3362
+ ctx.roundRect(midX - textW / 2, textY - textH, textW, textH, 3);
3363
+ ctx.fill();
3364
+ ctx.fillStyle = p.color;
3365
+ ctx.fillText(label, midX, textY - padY);
3366
+ ctx.restore();
3367
+ }
3368
+
3327
3369
  // src/elements/renderers/template-renderer.ts
3328
3370
  function renderTemplate(ctx, template, store) {
3329
3371
  const grid = store?.getElementsByType("grid")[0];
@@ -3347,9 +3389,6 @@ function renderGeometricTemplate(ctx, template) {
3347
3389
  ctx.arc(cx, cy, r, 0, Math.PI * 2);
3348
3390
  ctx.fill();
3349
3391
  ctx.stroke();
3350
- if (template.radiusFeet != null && template.radiusFeet > 0) {
3351
- renderRadiusMarker(ctx, cx, cy, r, template.radiusFeet);
3352
- }
3353
3392
  break;
3354
3393
  case "square":
3355
3394
  ctx.fillRect(cx - r / 2, cy - r / 2, r, r);
@@ -3382,6 +3421,16 @@ function renderGeometricTemplate(ctx, template) {
3382
3421
  break;
3383
3422
  }
3384
3423
  }
3424
+ if (template.radiusFeet != null && template.radiusFeet > 0) {
3425
+ renderTemplateFeetLabel(ctx, {
3426
+ position: template.position,
3427
+ radius: template.radius,
3428
+ angle: template.angle,
3429
+ templateShape: template.templateShape,
3430
+ feet: template.radiusFeet,
3431
+ color: template.strokeColor
3432
+ });
3433
+ }
3385
3434
  ctx.restore();
3386
3435
  }
3387
3436
  function renderHexTemplate(ctx, template, cellSize, orientation) {
@@ -3428,44 +3477,18 @@ function renderHexTemplate(ctx, template, cellSize, orientation) {
3428
3477
  ctx.lineWidth = template.strokeWidth;
3429
3478
  ctx.stroke();
3430
3479
  }
3431
- if (template.templateShape === "circle" && template.radiusFeet != null && template.radiusFeet > 0) {
3432
- const r = template.radius;
3433
- renderRadiusMarker(ctx, center2.x, center2.y, r, template.radiusFeet);
3480
+ if (template.radiusFeet != null && template.radiusFeet > 0) {
3481
+ renderTemplateFeetLabel(ctx, {
3482
+ position: template.position,
3483
+ radius: template.radius,
3484
+ angle: template.angle,
3485
+ templateShape: template.templateShape,
3486
+ feet: template.radiusFeet,
3487
+ color: template.strokeColor
3488
+ });
3434
3489
  }
3435
3490
  ctx.restore();
3436
3491
  }
3437
- function renderRadiusMarker(ctx, cx, cy, r, feet) {
3438
- const markerColor = ctx.strokeStyle;
3439
- ctx.save();
3440
- ctx.globalAlpha = 1;
3441
- ctx.beginPath();
3442
- ctx.setLineDash([4, 4]);
3443
- ctx.strokeStyle = markerColor;
3444
- ctx.lineWidth = 1.5;
3445
- ctx.moveTo(cx, cy);
3446
- ctx.lineTo(cx + r, cy);
3447
- ctx.stroke();
3448
- ctx.setLineDash([]);
3449
- const label = `${Math.round(feet)} ft`;
3450
- const fontSize = Math.max(10, Math.min(14, r * 0.15));
3451
- ctx.font = `bold ${fontSize}px system-ui, sans-serif`;
3452
- ctx.textAlign = "center";
3453
- ctx.textBaseline = "bottom";
3454
- const textX = cx + r / 2;
3455
- const textY = cy - 4;
3456
- const metrics = ctx.measureText(label);
3457
- const padX = 4;
3458
- const padY = 2;
3459
- const textW = metrics.width + padX * 2;
3460
- const textH = fontSize + padY * 2;
3461
- ctx.fillStyle = "rgba(255, 255, 255, 0.85)";
3462
- ctx.beginPath();
3463
- ctx.roundRect(textX - textW / 2, textY - textH, textW, textH, 3);
3464
- ctx.fill();
3465
- ctx.fillStyle = markerColor;
3466
- ctx.fillText(label, textX, textY - padY);
3467
- ctx.restore();
3468
- }
3469
3492
 
3470
3493
  // src/elements/grid-renderer.ts
3471
3494
  function getSquareGridLines(bounds, cellSize) {
@@ -8311,6 +8334,20 @@ function getOverlayLayout(el, zoom) {
8311
8334
  const rotateHandle = rotatePoint(topMid, center2, angle);
8312
8335
  return { center: center2, corners, rotateHandle, angle };
8313
8336
  }
8337
+ function templateAimKnob(el, zoom) {
8338
+ if (el.type !== "template") return null;
8339
+ if (el.templateShape !== "cone" && el.templateShape !== "line") return null;
8340
+ const gap = ROTATE_HANDLE_OFFSET / zoom;
8341
+ const dist = el.radius + gap;
8342
+ const origin = el.position;
8343
+ return {
8344
+ origin,
8345
+ knob: {
8346
+ x: origin.x + dist * Math.cos(el.angle),
8347
+ y: origin.y + dist * Math.sin(el.angle)
8348
+ }
8349
+ };
8350
+ }
8314
8351
  function getHandlePositions(bounds) {
8315
8352
  return [
8316
8353
  ["nw", { x: bounds.x, y: bounds.y }],
@@ -8459,6 +8496,22 @@ function renderSelectionBoxes(ctx, p) {
8459
8496
  handleWorldSize
8460
8497
  );
8461
8498
  ctx.setLineDash([4 / zoom, 4 / zoom]);
8499
+ if (p.selectedIds.length === 1 && (el.templateShape === "cone" || el.templateShape === "line")) {
8500
+ const aim = templateAimKnob(el, zoom);
8501
+ if (aim) {
8502
+ ctx.beginPath();
8503
+ ctx.moveTo(aim.origin.x, aim.origin.y);
8504
+ ctx.lineTo(aim.knob.x, aim.knob.y);
8505
+ ctx.stroke();
8506
+ ctx.setLineDash([]);
8507
+ ctx.fillStyle = "#ffffff";
8508
+ ctx.beginPath();
8509
+ ctx.arc(aim.knob.x, aim.knob.y, handleWorldSize / 2, 0, Math.PI * 2);
8510
+ ctx.fill();
8511
+ ctx.stroke();
8512
+ ctx.setLineDash([4 / zoom, 4 / zoom]);
8513
+ }
8514
+ }
8462
8515
  }
8463
8516
  if (p.selectedIds.length === 1 && ROTATABLE_TYPES.has(el.type)) {
8464
8517
  const stemStart = topMidpoint(layout);
@@ -8614,6 +8667,19 @@ function hitTestTemplateResizeHandle(world, ctx, selectedIds) {
8614
8667
  }
8615
8668
  return null;
8616
8669
  }
8670
+ function hitTestTemplateAimHandle(world, ctx, selectedIds) {
8671
+ if (selectedIds.length !== 1) return null;
8672
+ const id = selectedIds[0];
8673
+ if (!id) return null;
8674
+ const el = ctx.store.getById(id);
8675
+ if (!el || el.locked) return null;
8676
+ const knob = templateAimKnob(el, ctx.camera.zoom);
8677
+ if (!knob) return null;
8678
+ const r = (HANDLE_SIZE / 2 + HANDLE_HIT_PADDING2) / ctx.camera.zoom;
8679
+ const dx = world.x - knob.knob.x;
8680
+ const dy = world.y - knob.knob.y;
8681
+ return dx * dx + dy * dy <= r * r ? { elementId: id } : null;
8682
+ }
8617
8683
  function findElementsInRect(marquee, ctx) {
8618
8684
  const candidates = ctx.store.queryRect(marquee);
8619
8685
  const ids = [];
@@ -8863,6 +8929,12 @@ var SelectTool = class {
8863
8929
  ctx.requestRender();
8864
8930
  return;
8865
8931
  }
8932
+ const aimHit = hitTestTemplateAimHandle(world, ctx, this._selectedIds);
8933
+ if (aimHit) {
8934
+ this.mode = { type: "aiming-template", elementId: aimHit.elementId };
8935
+ ctx.requestRender();
8936
+ return;
8937
+ }
8866
8938
  const rotateHit = hitTestRotateHandle(world, ctx, this._selectedIds);
8867
8939
  if (rotateHit) {
8868
8940
  const el = ctx.store.getById(rotateHit.elementId);
@@ -8944,6 +9016,19 @@ var SelectTool = class {
8944
9016
  this.handleTemplateResize(world, ctx);
8945
9017
  return;
8946
9018
  }
9019
+ if (this.mode.type === "aiming-template") {
9020
+ const el = ctx.store.getById(this.mode.elementId);
9021
+ if (el && el.type === "template" && !el.locked) {
9022
+ let a = Math.atan2(world.y - el.position.y, world.x - el.position.x);
9023
+ if (state.shiftKey) {
9024
+ const snap = ctx.gridType === "hex" ? Math.PI / 3 : ROTATE_SNAP;
9025
+ a = Math.round(a / snap) * snap;
9026
+ }
9027
+ ctx.store.update(this.mode.elementId, { angle: normalizeAngle(a) });
9028
+ ctx.requestRender();
9029
+ }
9030
+ return;
9031
+ }
8947
9032
  if (this.mode.type === "rotating") {
8948
9033
  const { elementId, center: center2, startPointerAngle, startRotation } = this.mode;
8949
9034
  const a = Math.atan2(world.y - center2.y, world.x - center2.x);
@@ -9150,6 +9235,10 @@ var SelectTool = class {
9150
9235
  ctx.setCursor?.("nwse-resize");
9151
9236
  return null;
9152
9237
  }
9238
+ if (hitTestTemplateAimHandle(world, ctx, this._selectedIds)) {
9239
+ ctx.setCursor?.("grab");
9240
+ return null;
9241
+ }
9153
9242
  if (hitTestRotateHandle(world, ctx, this._selectedIds)) {
9154
9243
  ctx.setCursor?.("grab");
9155
9244
  return null;
@@ -9819,6 +9908,7 @@ var TemplateTool = class {
9819
9908
  gridType;
9820
9909
  hexOrientation;
9821
9910
  snapEnabled = false;
9911
+ feetScaleUnit = 0;
9822
9912
  templateShape;
9823
9913
  fillColor;
9824
9914
  strokeColor;
@@ -9867,6 +9957,7 @@ var TemplateTool = class {
9867
9957
  this.gridType = ctx.gridType;
9868
9958
  this.hexOrientation = ctx.hexOrientation;
9869
9959
  this.snapEnabled = !!ctx.gridType || (ctx.snapToGrid ?? false);
9960
+ this.feetScaleUnit = ctx.gridSize && ctx.gridSize > 0 ? ctx.gridType === "hex" ? Math.sqrt(3) * ctx.gridSize : ctx.gridSize : 0;
9870
9961
  const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
9871
9962
  this.origin = this.snapToGrid(world, ctx);
9872
9963
  this.current = { ...this.origin };
@@ -9966,6 +10057,7 @@ var TemplateTool = class {
9966
10057
  break;
9967
10058
  }
9968
10059
  }
10060
+ this.drawFeetLabel(ctx, radius);
9969
10061
  ctx.restore();
9970
10062
  }
9971
10063
  renderHexOverlay(ctx, radius) {
@@ -10016,30 +10108,7 @@ var TemplateTool = class {
10016
10108
  ctx.lineWidth = this.strokeWidth;
10017
10109
  ctx.stroke();
10018
10110
  }
10019
- if (this.templateShape === "circle") {
10020
- const feet = radiusCells * this.feetPerCell;
10021
- if (feet > 0) {
10022
- ctx.globalAlpha = 1;
10023
- const label = `${Math.round(feet)} ft`;
10024
- const fontSize = Math.max(10, Math.min(14, radius * 0.15));
10025
- ctx.font = `bold ${fontSize}px system-ui, sans-serif`;
10026
- ctx.textAlign = "center";
10027
- ctx.textBaseline = "bottom";
10028
- const textX = center2.x;
10029
- const textY = center2.y - 4;
10030
- const metrics = ctx.measureText(label);
10031
- const padX = 4;
10032
- const padY = 2;
10033
- const textW = metrics.width + padX * 2;
10034
- const textH = fontSize + padY * 2;
10035
- ctx.fillStyle = "rgba(255, 255, 255, 0.85)";
10036
- ctx.beginPath();
10037
- ctx.roundRect(textX - textW / 2, textY - textH, textW, textH, 3);
10038
- ctx.fill();
10039
- ctx.fillStyle = this.strokeColor;
10040
- ctx.fillText(label, textX, textY - padY);
10041
- }
10042
- }
10111
+ this.drawFeetLabel(ctx, radius);
10043
10112
  ctx.restore();
10044
10113
  }
10045
10114
  computeRadius() {
@@ -10057,6 +10126,17 @@ var TemplateTool = class {
10057
10126
  const dy = this.current.y - this.origin.y;
10058
10127
  return Math.atan2(dy, dx);
10059
10128
  }
10129
+ drawFeetLabel(ctx, radius) {
10130
+ const feet = this.feetScaleUnit > 0 ? radius / this.feetScaleUnit * this.feetPerCell : 0;
10131
+ renderTemplateFeetLabel(ctx, {
10132
+ position: this.origin,
10133
+ radius,
10134
+ angle: this.computeAngle(),
10135
+ templateShape: this.templateShape,
10136
+ feet,
10137
+ color: this.strokeColor
10138
+ });
10139
+ }
10060
10140
  snapToGrid(point, ctx) {
10061
10141
  if (!ctx.gridSize) return point;
10062
10142
  if (ctx.gridType === "hex" && ctx.hexOrientation) {
@@ -10186,7 +10266,7 @@ var LaserTool = class {
10186
10266
  };
10187
10267
 
10188
10268
  // src/index.ts
10189
- var VERSION = "0.46.1";
10269
+ var VERSION = "0.47.0";
10190
10270
  // Annotate the CommonJS export names for ESM import in node:
10191
10271
  0 && (module.exports = {
10192
10272
  ArrowTool,