@fieldnotes/core 0.6.1 → 0.7.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
@@ -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;
@@ -1634,6 +1794,22 @@ function createShape(input) {
1634
1794
  fillColor: input.fillColor ?? "none"
1635
1795
  };
1636
1796
  }
1797
+ function createGrid(input) {
1798
+ return {
1799
+ id: createId("grid"),
1800
+ type: "grid",
1801
+ position: input.position ?? { x: 0, y: 0 },
1802
+ zIndex: input.zIndex ?? 0,
1803
+ locked: input.locked ?? false,
1804
+ layerId: input.layerId ?? "",
1805
+ gridType: input.gridType ?? "square",
1806
+ hexOrientation: input.hexOrientation ?? "pointy",
1807
+ cellSize: input.cellSize ?? 40,
1808
+ strokeColor: input.strokeColor ?? "#000000",
1809
+ strokeWidth: input.strokeWidth ?? 1,
1810
+ opacity: input.opacity ?? 1
1811
+ };
1812
+ }
1637
1813
  function createText(input) {
1638
1814
  return {
1639
1815
  id: createId("text"),
@@ -1811,6 +1987,7 @@ var Viewport = class {
1811
1987
  this.toolManager = new ToolManager();
1812
1988
  this.renderer = new ElementRenderer();
1813
1989
  this.renderer.setStore(this.store);
1990
+ this.renderer.setCamera(this.camera);
1814
1991
  this.renderer.setOnImageLoad(() => this.requestRender());
1815
1992
  this.noteEditor = new NoteEditor();
1816
1993
  this.noteEditor.setOnStop((id) => this.onTextEditStop(id));
@@ -1957,6 +2134,34 @@ var Viewport = class {
1957
2134
  this.requestRender();
1958
2135
  return el.id;
1959
2136
  }
2137
+ addGrid(input) {
2138
+ const existing = this.store.getElementsByType("grid")[0];
2139
+ this.historyRecorder.begin();
2140
+ if (existing) {
2141
+ this.store.remove(existing.id);
2142
+ }
2143
+ const grid = createGrid({ ...input, layerId: this.layerManager.activeLayerId });
2144
+ this.store.add(grid);
2145
+ this.historyRecorder.commit();
2146
+ this.requestRender();
2147
+ return grid.id;
2148
+ }
2149
+ updateGrid(updates) {
2150
+ const grid = this.store.getElementsByType("grid")[0];
2151
+ if (!grid) return;
2152
+ this.historyRecorder.begin();
2153
+ this.store.update(grid.id, updates);
2154
+ this.historyRecorder.commit();
2155
+ this.requestRender();
2156
+ }
2157
+ removeGrid() {
2158
+ const grid = this.store.getElementsByType("grid")[0];
2159
+ if (!grid) return;
2160
+ this.historyRecorder.begin();
2161
+ this.store.remove(grid.id);
2162
+ this.historyRecorder.commit();
2163
+ this.requestRender();
2164
+ }
1960
2165
  destroy() {
1961
2166
  cancelAnimationFrame(this.animFrameId);
1962
2167
  this.stopInteracting();
@@ -1988,6 +2193,7 @@ var Viewport = class {
1988
2193
  const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
1989
2194
  ctx.save();
1990
2195
  ctx.scale(dpr, dpr);
2196
+ this.renderer.setCanvasSize(this.canvasEl.clientWidth, this.canvasEl.clientHeight);
1991
2197
  this.background.render(ctx, this.camera);
1992
2198
  ctx.save();
1993
2199
  ctx.translate(this.camera.position.x, this.camera.position.y);
@@ -2974,6 +3180,7 @@ var SelectTool = class {
2974
3180
  for (const el of ctx.store.getAll()) {
2975
3181
  if (ctx.isLayerVisible && !ctx.isLayerVisible(el.layerId)) continue;
2976
3182
  if (ctx.isLayerLocked && ctx.isLayerLocked(el.layerId)) continue;
3183
+ if (el.type === "grid") continue;
2977
3184
  const bounds = this.getElementBounds(el);
2978
3185
  if (bounds && this.rectsOverlap(marquee, bounds)) {
2979
3186
  ids.push(el.id);
@@ -3010,11 +3217,13 @@ var SelectTool = class {
3010
3217
  for (const el of elements) {
3011
3218
  if (ctx.isLayerVisible && !ctx.isLayerVisible(el.layerId)) continue;
3012
3219
  if (ctx.isLayerLocked && ctx.isLayerLocked(el.layerId)) continue;
3220
+ if (el.type === "grid") continue;
3013
3221
  if (this.isInsideBounds(world, el)) return el;
3014
3222
  }
3015
3223
  return null;
3016
3224
  }
3017
3225
  isInsideBounds(point, el) {
3226
+ if (el.type === "grid") return false;
3018
3227
  if ("size" in el) {
3019
3228
  const s = el.size;
3020
3229
  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;
@@ -3475,6 +3684,7 @@ var VERSION = "0.6.1";
3475
3684
  Viewport,
3476
3685
  clearStaleBindings,
3477
3686
  createArrow,
3687
+ createGrid,
3478
3688
  createHtmlElement,
3479
3689
  createId,
3480
3690
  createImage,