@fieldnotes/core 0.6.1 → 0.7.1

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
@@ -52,6 +52,7 @@ __export(index_exports, {
52
52
  Viewport: () => Viewport,
53
53
  clearStaleBindings: () => clearStaleBindings,
54
54
  createArrow: () => createArrow,
55
+ createGrid: () => createGrid,
55
56
  createHtmlElement: () => createHtmlElement,
56
57
  createId: () => createId,
57
58
  createImage: () => createImage,
@@ -165,7 +166,7 @@ function validateState(data) {
165
166
  ];
166
167
  }
167
168
  }
168
- var VALID_TYPES = /* @__PURE__ */ new Set(["stroke", "note", "arrow", "image", "html", "text", "shape"]);
169
+ var VALID_TYPES = /* @__PURE__ */ new Set(["stroke", "note", "arrow", "image", "html", "text", "shape", "grid"]);
169
170
  function validateElement(el) {
170
171
  if (!el || typeof el !== "object") {
171
172
  throw new Error("Invalid element: expected an object");
@@ -1026,6 +1027,118 @@ function smoothToSegments(points) {
1026
1027
  return segments;
1027
1028
  }
1028
1029
 
1030
+ // src/elements/grid-renderer.ts
1031
+ function getSquareGridLines(bounds, cellSize) {
1032
+ if (cellSize <= 0) return { verticals: [], horizontals: [] };
1033
+ const verticals = [];
1034
+ const startX = Math.floor(bounds.minX / cellSize) * cellSize;
1035
+ const endX = Math.ceil(bounds.maxX / cellSize) * cellSize;
1036
+ for (let x = startX; x <= endX; x += cellSize) {
1037
+ verticals.push(x);
1038
+ }
1039
+ const horizontals = [];
1040
+ const startY = Math.floor(bounds.minY / cellSize) * cellSize;
1041
+ const endY = Math.ceil(bounds.maxY / cellSize) * cellSize;
1042
+ for (let y = startY; y <= endY; y += cellSize) {
1043
+ horizontals.push(y);
1044
+ }
1045
+ return { verticals, horizontals };
1046
+ }
1047
+ function getHexVertices(cx, cy, circumradius, orientation) {
1048
+ const vertices = [];
1049
+ const angleOffset = orientation === "pointy" ? -Math.PI / 2 : 0;
1050
+ for (let i = 0; i < 6; i++) {
1051
+ const angle = Math.PI / 3 * i + angleOffset;
1052
+ vertices.push({
1053
+ x: cx + circumradius * Math.cos(angle),
1054
+ y: cy + circumradius * Math.sin(angle)
1055
+ });
1056
+ }
1057
+ return vertices;
1058
+ }
1059
+ function getHexCenters(bounds, circumradius, orientation) {
1060
+ if (circumradius <= 0) return [];
1061
+ const centers = [];
1062
+ if (orientation === "pointy") {
1063
+ const hexW = Math.sqrt(3) * circumradius;
1064
+ const hexH = 2 * circumradius;
1065
+ const rowH = hexH * 0.75;
1066
+ const startRow = Math.floor((bounds.minY - circumradius) / rowH);
1067
+ const endRow = Math.ceil((bounds.maxY + circumradius) / rowH);
1068
+ const startCol = Math.floor((bounds.minX - hexW) / hexW);
1069
+ const endCol = Math.ceil((bounds.maxX + hexW) / hexW);
1070
+ for (let row = startRow; row <= endRow; row++) {
1071
+ const offsetX = row % 2 !== 0 ? hexW / 2 : 0;
1072
+ for (let col = startCol; col <= endCol; col++) {
1073
+ centers.push({
1074
+ x: col * hexW + offsetX,
1075
+ y: row * rowH
1076
+ });
1077
+ }
1078
+ }
1079
+ } else {
1080
+ const hexW = 2 * circumradius;
1081
+ const hexH = Math.sqrt(3) * circumradius;
1082
+ const colW = hexW * 0.75;
1083
+ const startCol = Math.floor((bounds.minX - circumradius) / colW);
1084
+ const endCol = Math.ceil((bounds.maxX + circumradius) / colW);
1085
+ const startRow = Math.floor((bounds.minY - hexH) / hexH);
1086
+ const endRow = Math.ceil((bounds.maxY + hexH) / hexH);
1087
+ for (let col = startCol; col <= endCol; col++) {
1088
+ const offsetY = col % 2 !== 0 ? hexH / 2 : 0;
1089
+ for (let row = startRow; row <= endRow; row++) {
1090
+ centers.push({
1091
+ x: col * colW,
1092
+ y: row * hexH + offsetY
1093
+ });
1094
+ }
1095
+ }
1096
+ }
1097
+ return centers;
1098
+ }
1099
+ function renderSquareGrid(ctx, bounds, cellSize, strokeColor, strokeWidth, opacity) {
1100
+ if (cellSize <= 0) return;
1101
+ const { verticals, horizontals } = getSquareGridLines(bounds, cellSize);
1102
+ ctx.save();
1103
+ ctx.strokeStyle = strokeColor;
1104
+ ctx.lineWidth = strokeWidth;
1105
+ ctx.globalAlpha = opacity;
1106
+ ctx.beginPath();
1107
+ for (const x of verticals) {
1108
+ ctx.moveTo(x, bounds.minY);
1109
+ ctx.lineTo(x, bounds.maxY);
1110
+ }
1111
+ for (const y of horizontals) {
1112
+ ctx.moveTo(bounds.minX, y);
1113
+ ctx.lineTo(bounds.maxX, y);
1114
+ }
1115
+ ctx.stroke();
1116
+ ctx.restore();
1117
+ }
1118
+ function renderHexGrid(ctx, bounds, cellSize, orientation, strokeColor, strokeWidth, opacity) {
1119
+ if (cellSize <= 0) return;
1120
+ const centers = getHexCenters(bounds, cellSize, orientation);
1121
+ ctx.save();
1122
+ ctx.strokeStyle = strokeColor;
1123
+ ctx.lineWidth = strokeWidth;
1124
+ ctx.globalAlpha = opacity;
1125
+ ctx.beginPath();
1126
+ for (const center of centers) {
1127
+ const verts = getHexVertices(center.x, center.y, cellSize, orientation);
1128
+ const first = verts[0];
1129
+ if (!first) continue;
1130
+ ctx.moveTo(first.x, first.y);
1131
+ for (let i = 1; i < verts.length; i++) {
1132
+ const v = verts[i];
1133
+ if (!v) continue;
1134
+ ctx.lineTo(v.x, v.y);
1135
+ }
1136
+ ctx.closePath();
1137
+ }
1138
+ ctx.stroke();
1139
+ ctx.restore();
1140
+ }
1141
+
1029
1142
  // src/elements/element-renderer.ts
1030
1143
  var DOM_ELEMENT_TYPES = /* @__PURE__ */ new Set(["note", "html", "text"]);
1031
1144
  var ARROWHEAD_LENGTH = 12;
@@ -1034,12 +1147,20 @@ var ElementRenderer = class {
1034
1147
  store = null;
1035
1148
  imageCache = /* @__PURE__ */ new Map();
1036
1149
  onImageLoad = null;
1150
+ camera = null;
1151
+ canvasSize = null;
1037
1152
  setStore(store) {
1038
1153
  this.store = store;
1039
1154
  }
1040
1155
  setOnImageLoad(callback) {
1041
1156
  this.onImageLoad = callback;
1042
1157
  }
1158
+ setCamera(camera) {
1159
+ this.camera = camera;
1160
+ }
1161
+ setCanvasSize(w, h) {
1162
+ this.canvasSize = { w, h };
1163
+ }
1043
1164
  isDomElement(element) {
1044
1165
  return DOM_ELEMENT_TYPES.has(element.type);
1045
1166
  }
@@ -1057,6 +1178,9 @@ var ElementRenderer = class {
1057
1178
  case "image":
1058
1179
  this.renderImage(ctx, element);
1059
1180
  break;
1181
+ case "grid":
1182
+ this.renderGrid(ctx, element);
1183
+ break;
1060
1184
  }
1061
1185
  }
1062
1186
  renderStroke(ctx, stroke) {
@@ -1192,6 +1316,42 @@ var ElementRenderer = class {
1192
1316
  }
1193
1317
  }
1194
1318
  }
1319
+ renderGrid(ctx, grid) {
1320
+ if (!this.canvasSize) return;
1321
+ const cam = this.camera;
1322
+ if (!cam) return;
1323
+ const topLeft = cam.screenToWorld({ x: 0, y: 0 });
1324
+ const bottomRight = cam.screenToWorld({
1325
+ x: this.canvasSize.w,
1326
+ y: this.canvasSize.h
1327
+ });
1328
+ const bounds = {
1329
+ minX: topLeft.x,
1330
+ minY: topLeft.y,
1331
+ maxX: bottomRight.x,
1332
+ maxY: bottomRight.y
1333
+ };
1334
+ if (grid.gridType === "hex") {
1335
+ renderHexGrid(
1336
+ ctx,
1337
+ bounds,
1338
+ grid.cellSize,
1339
+ grid.hexOrientation,
1340
+ grid.strokeColor,
1341
+ grid.strokeWidth,
1342
+ grid.opacity
1343
+ );
1344
+ } else {
1345
+ renderSquareGrid(
1346
+ ctx,
1347
+ bounds,
1348
+ grid.cellSize,
1349
+ grid.strokeColor,
1350
+ grid.strokeWidth,
1351
+ grid.opacity
1352
+ );
1353
+ }
1354
+ }
1195
1355
  renderImage(ctx, image) {
1196
1356
  const img = this.getImage(image.src);
1197
1357
  if (!img) return;
@@ -1609,7 +1769,7 @@ function createImage(input) {
1609
1769
  };
1610
1770
  }
1611
1771
  function createHtmlElement(input) {
1612
- return {
1772
+ const el = {
1613
1773
  id: createId("html"),
1614
1774
  type: "html",
1615
1775
  position: input.position,
@@ -1618,6 +1778,8 @@ function createHtmlElement(input) {
1618
1778
  layerId: input.layerId ?? "",
1619
1779
  size: input.size
1620
1780
  };
1781
+ if (input.domId) el.domId = input.domId;
1782
+ return el;
1621
1783
  }
1622
1784
  function createShape(input) {
1623
1785
  return {
@@ -1634,6 +1796,22 @@ function createShape(input) {
1634
1796
  fillColor: input.fillColor ?? "none"
1635
1797
  };
1636
1798
  }
1799
+ function createGrid(input) {
1800
+ return {
1801
+ id: createId("grid"),
1802
+ type: "grid",
1803
+ position: input.position ?? { x: 0, y: 0 },
1804
+ zIndex: input.zIndex ?? 0,
1805
+ locked: input.locked ?? false,
1806
+ layerId: input.layerId ?? "",
1807
+ gridType: input.gridType ?? "square",
1808
+ hexOrientation: input.hexOrientation ?? "pointy",
1809
+ cellSize: input.cellSize ?? 40,
1810
+ strokeColor: input.strokeColor ?? "#000000",
1811
+ strokeWidth: input.strokeWidth ?? 1,
1812
+ opacity: input.opacity ?? 1
1813
+ };
1814
+ }
1637
1815
  function createText(input) {
1638
1816
  return {
1639
1817
  id: createId("text"),
@@ -1811,6 +1989,7 @@ var Viewport = class {
1811
1989
  this.toolManager = new ToolManager();
1812
1990
  this.renderer = new ElementRenderer();
1813
1991
  this.renderer.setStore(this.store);
1992
+ this.renderer.setCamera(this.camera);
1814
1993
  this.renderer.setOnImageLoad(() => this.requestRender());
1815
1994
  this.noteEditor = new NoteEditor();
1816
1995
  this.noteEditor.setOnStop((id) => this.onTextEditStop(id));
@@ -1918,6 +2097,7 @@ var Viewport = class {
1918
2097
  if (state.layers && state.layers.length > 0) {
1919
2098
  this.layerManager.loadSnapshot(state.layers);
1920
2099
  }
2100
+ this.reattachHtmlContent();
1921
2101
  this.history.clear();
1922
2102
  this.historyRecorder.resume();
1923
2103
  this.camera.moveTo(state.camera.position.x, state.camera.position.y);
@@ -1949,7 +2129,8 @@ var Viewport = class {
1949
2129
  return image.id;
1950
2130
  }
1951
2131
  addHtmlElement(dom, position, size = { w: 200, h: 150 }) {
1952
- const el = createHtmlElement({ position, size, layerId: this.layerManager.activeLayerId });
2132
+ const domId = dom.id || void 0;
2133
+ const el = createHtmlElement({ position, size, domId, layerId: this.layerManager.activeLayerId });
1953
2134
  this.htmlContent.set(el.id, dom);
1954
2135
  this.historyRecorder.begin();
1955
2136
  this.store.add(el);
@@ -1957,6 +2138,34 @@ var Viewport = class {
1957
2138
  this.requestRender();
1958
2139
  return el.id;
1959
2140
  }
2141
+ addGrid(input) {
2142
+ const existing = this.store.getElementsByType("grid")[0];
2143
+ this.historyRecorder.begin();
2144
+ if (existing) {
2145
+ this.store.remove(existing.id);
2146
+ }
2147
+ const grid = createGrid({ ...input, layerId: this.layerManager.activeLayerId });
2148
+ this.store.add(grid);
2149
+ this.historyRecorder.commit();
2150
+ this.requestRender();
2151
+ return grid.id;
2152
+ }
2153
+ updateGrid(updates) {
2154
+ const grid = this.store.getElementsByType("grid")[0];
2155
+ if (!grid) return;
2156
+ this.historyRecorder.begin();
2157
+ this.store.update(grid.id, updates);
2158
+ this.historyRecorder.commit();
2159
+ this.requestRender();
2160
+ }
2161
+ removeGrid() {
2162
+ const grid = this.store.getElementsByType("grid")[0];
2163
+ if (!grid) return;
2164
+ this.historyRecorder.begin();
2165
+ this.store.remove(grid.id);
2166
+ this.historyRecorder.commit();
2167
+ this.requestRender();
2168
+ }
1960
2169
  destroy() {
1961
2170
  cancelAnimationFrame(this.animFrameId);
1962
2171
  this.stopInteracting();
@@ -1988,6 +2197,7 @@ var Viewport = class {
1988
2197
  const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
1989
2198
  ctx.save();
1990
2199
  ctx.scale(dpr, dpr);
2200
+ this.renderer.setCanvasSize(this.canvasEl.clientWidth, this.canvasEl.clientHeight);
1991
2201
  this.background.render(ctx, this.camera);
1992
2202
  ctx.save();
1993
2203
  ctx.translate(this.camera.position.x, this.camera.position.y);
@@ -2289,6 +2499,16 @@ var Viewport = class {
2289
2499
  this.htmlContent.clear();
2290
2500
  this.requestRender();
2291
2501
  }
2502
+ reattachHtmlContent() {
2503
+ for (const el of this.store.getElementsByType("html")) {
2504
+ if (el.domId) {
2505
+ const dom = document.getElementById(el.domId);
2506
+ if (dom) {
2507
+ this.htmlContent.set(el.id, dom);
2508
+ }
2509
+ }
2510
+ }
2511
+ }
2292
2512
  createWrapper() {
2293
2513
  const el = document.createElement("div");
2294
2514
  Object.assign(el.style, {
@@ -2547,10 +2767,11 @@ function applyArrowHandleDrag(handle, elementId, world, ctx) {
2547
2767
  const el = ctx.store.getById(elementId);
2548
2768
  if (!el || el.type !== "arrow") return;
2549
2769
  const threshold = BIND_THRESHOLD / ctx.camera.zoom;
2770
+ const layerFilter = (candidate) => candidate.layerId === el.layerId;
2550
2771
  switch (handle) {
2551
2772
  case "start": {
2552
2773
  const excludeId = el.toBinding?.elementId;
2553
- const target = findBindTarget(world, ctx.store, threshold, excludeId);
2774
+ const target = findBindTarget(world, ctx.store, threshold, excludeId, layerFilter);
2554
2775
  if (target) {
2555
2776
  const center = getElementCenter(target);
2556
2777
  ctx.store.update(elementId, {
@@ -2569,7 +2790,7 @@ function applyArrowHandleDrag(handle, elementId, world, ctx) {
2569
2790
  }
2570
2791
  case "end": {
2571
2792
  const excludeId = el.fromBinding?.elementId;
2572
- const target = findBindTarget(world, ctx.store, threshold, excludeId);
2793
+ const target = findBindTarget(world, ctx.store, threshold, excludeId, layerFilter);
2573
2794
  if (target) {
2574
2795
  const center = getElementCenter(target);
2575
2796
  ctx.store.update(elementId, {
@@ -2598,7 +2819,8 @@ function getArrowHandleDragTarget(handle, elementId, world, ctx) {
2598
2819
  if (!el || el.type !== "arrow") return null;
2599
2820
  const threshold = BIND_THRESHOLD / ctx.camera.zoom;
2600
2821
  const excludeId = handle === "start" ? el.toBinding?.elementId : el.fromBinding?.elementId;
2601
- const target = findBindTarget(world, ctx.store, threshold, excludeId);
2822
+ const layerFilter = (candidate) => candidate.layerId === el.layerId;
2823
+ const target = findBindTarget(world, ctx.store, threshold, excludeId, layerFilter);
2602
2824
  if (!target) return null;
2603
2825
  return getElementBounds(target);
2604
2826
  }
@@ -2974,6 +3196,7 @@ var SelectTool = class {
2974
3196
  for (const el of ctx.store.getAll()) {
2975
3197
  if (ctx.isLayerVisible && !ctx.isLayerVisible(el.layerId)) continue;
2976
3198
  if (ctx.isLayerLocked && ctx.isLayerLocked(el.layerId)) continue;
3199
+ if (el.type === "grid") continue;
2977
3200
  const bounds = this.getElementBounds(el);
2978
3201
  if (bounds && this.rectsOverlap(marquee, bounds)) {
2979
3202
  ids.push(el.id);
@@ -3010,11 +3233,13 @@ var SelectTool = class {
3010
3233
  for (const el of elements) {
3011
3234
  if (ctx.isLayerVisible && !ctx.isLayerVisible(el.layerId)) continue;
3012
3235
  if (ctx.isLayerLocked && ctx.isLayerLocked(el.layerId)) continue;
3236
+ if (el.type === "grid") continue;
3013
3237
  if (this.isInsideBounds(world, el)) return el;
3014
3238
  }
3015
3239
  return null;
3016
3240
  }
3017
3241
  isInsideBounds(point, el) {
3242
+ if (el.type === "grid") return false;
3018
3243
  if ("size" in el) {
3019
3244
  const s = el.size;
3020
3245
  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;
@@ -3055,8 +3280,10 @@ var ArrowTool = class {
3055
3280
  if (options.width !== void 0) this.width = options.width;
3056
3281
  }
3057
3282
  layerFilter(ctx) {
3058
- if (!ctx.isLayerVisible && !ctx.isLayerLocked) return void 0;
3283
+ const activeLayerId = ctx.activeLayerId;
3284
+ if (!activeLayerId && !ctx.isLayerVisible && !ctx.isLayerLocked) return void 0;
3059
3285
  return (el) => {
3286
+ if (activeLayerId && el.layerId !== activeLayerId) return false;
3060
3287
  if (ctx.isLayerVisible && !ctx.isLayerVisible(el.layerId)) return false;
3061
3288
  if (ctx.isLayerLocked && ctx.isLayerLocked(el.layerId)) return false;
3062
3289
  return true;
@@ -3440,7 +3667,7 @@ var UpdateLayerCommand = class {
3440
3667
  };
3441
3668
 
3442
3669
  // src/index.ts
3443
- var VERSION = "0.6.1";
3670
+ var VERSION = "0.7.0";
3444
3671
  // Annotate the CommonJS export names for ESM import in node:
3445
3672
  0 && (module.exports = {
3446
3673
  AddElementCommand,
@@ -3475,6 +3702,7 @@ var VERSION = "0.6.1";
3475
3702
  Viewport,
3476
3703
  clearStaleBindings,
3477
3704
  createArrow,
3705
+ createGrid,
3478
3706
  createHtmlElement,
3479
3707
  createId,
3480
3708
  createImage,