@fieldnotes/core 0.8.11 → 0.10.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
@@ -27,6 +27,8 @@ __export(index_exports, {
27
27
  BatchCommand: () => BatchCommand,
28
28
  Camera: () => Camera,
29
29
  CreateLayerCommand: () => CreateLayerCommand,
30
+ DEFAULT_FONT_SIZE_PRESETS: () => DEFAULT_FONT_SIZE_PRESETS,
31
+ DEFAULT_NOTE_FONT_SIZE: () => DEFAULT_NOTE_FONT_SIZE,
30
32
  ElementRenderer: () => ElementRenderer,
31
33
  ElementStore: () => ElementStore,
32
34
  EraserTool: () => EraserTool,
@@ -37,14 +39,17 @@ __export(index_exports, {
37
39
  ImageTool: () => ImageTool,
38
40
  InputHandler: () => InputHandler,
39
41
  LayerManager: () => LayerManager,
42
+ MeasureTool: () => MeasureTool,
40
43
  NoteEditor: () => NoteEditor,
41
44
  NoteTool: () => NoteTool,
45
+ NoteToolbar: () => NoteToolbar,
42
46
  PencilTool: () => PencilTool,
43
47
  Quadtree: () => Quadtree,
44
48
  RemoveElementCommand: () => RemoveElementCommand,
45
49
  RemoveLayerCommand: () => RemoveLayerCommand,
46
50
  SelectTool: () => SelectTool,
47
51
  ShapeTool: () => ShapeTool,
52
+ TemplateTool: () => TemplateTool,
48
53
  TextTool: () => TextTool,
49
54
  ToolManager: () => ToolManager,
50
55
  UpdateElementCommand: () => UpdateElementCommand,
@@ -61,11 +66,14 @@ __export(index_exports, {
61
66
  createNote: () => createNote,
62
67
  createShape: () => createShape,
63
68
  createStroke: () => createStroke,
69
+ createTemplate: () => createTemplate,
64
70
  createText: () => createText,
71
+ drawHexPath: () => drawHexPath,
65
72
  exportImage: () => exportImage,
66
73
  exportState: () => exportState,
67
74
  findBindTarget: () => findBindTarget,
68
75
  findBoundArrows: () => findBoundArrows,
76
+ getActiveFormats: () => getActiveFormats,
69
77
  getArrowBounds: () => getArrowBounds,
70
78
  getArrowControlPoint: () => getArrowControlPoint,
71
79
  getArrowMidpoint: () => getArrowMidpoint,
@@ -74,10 +82,23 @@ __export(index_exports, {
74
82
  getEdgeIntersection: () => getEdgeIntersection,
75
83
  getElementBounds: () => getElementBounds,
76
84
  getElementCenter: () => getElementCenter,
85
+ getHexCellsInCone: () => getHexCellsInCone,
86
+ getHexCellsInLine: () => getHexCellsInLine,
87
+ getHexCellsInRadius: () => getHexCellsInRadius,
88
+ getHexCellsInSquare: () => getHexCellsInSquare,
89
+ getHexDistance: () => getHexDistance,
77
90
  isBindable: () => isBindable,
78
91
  isNearBezier: () => isNearBezier,
79
92
  parseState: () => parseState,
93
+ sanitizeNoteHtml: () => sanitizeNoteHtml,
94
+ setFontSize: () => setFontSize,
95
+ smartSnap: () => smartSnap,
80
96
  snapPoint: () => snapPoint,
97
+ snapToHexCenter: () => snapToHexCenter,
98
+ toggleBold: () => toggleBold,
99
+ toggleItalic: () => toggleItalic,
100
+ toggleStrikethrough: () => toggleStrikethrough,
101
+ toggleUnderline: () => toggleUnderline,
81
102
  unbindArrow: () => unbindArrow,
82
103
  updateBoundArrow: () => updateBoundArrow
83
104
  });
@@ -254,6 +275,120 @@ var Quadtree = class {
254
275
  }
255
276
  };
256
277
 
278
+ // src/elements/note-sanitizer.ts
279
+ var BOLD_TAGS = /* @__PURE__ */ new Set(["b", "strong"]);
280
+ var ITALIC_TAGS = /* @__PURE__ */ new Set(["i", "em"]);
281
+ var UNDERLINE_TAGS = /* @__PURE__ */ new Set(["u"]);
282
+ var STRIKE_TAGS = /* @__PURE__ */ new Set(["s", "strike", "del"]);
283
+ var BLOCK_TAGS = /* @__PURE__ */ new Set(["div"]);
284
+ function parseStyledRuns(html, baseFontSize) {
285
+ if (!html) return [];
286
+ const doc = new DOMParser().parseFromString(html, "text/html");
287
+ const runs = [];
288
+ const baseStyle = {
289
+ bold: false,
290
+ italic: false,
291
+ underline: false,
292
+ strikethrough: false,
293
+ fontSize: baseFontSize
294
+ };
295
+ walkNodes(doc.body, baseStyle, runs);
296
+ return runs;
297
+ }
298
+ function walkNodes(node, style, runs) {
299
+ for (const child of Array.from(node.childNodes)) {
300
+ if (child.nodeType === Node.TEXT_NODE) {
301
+ const text = child.textContent ?? "";
302
+ if (text) {
303
+ runs.push({ text, ...style });
304
+ }
305
+ continue;
306
+ }
307
+ if (child.nodeType !== Node.ELEMENT_NODE) continue;
308
+ const el = child;
309
+ const tag = el.tagName.toLowerCase();
310
+ if (tag === "br") {
311
+ runs.push({ text: "\n", ...style });
312
+ continue;
313
+ }
314
+ if (BLOCK_TAGS.has(tag) && runs.length > 0) {
315
+ const lastRun = runs[runs.length - 1];
316
+ if (lastRun && !lastRun.text.endsWith("\n")) {
317
+ runs.push({ text: "\n", ...style });
318
+ }
319
+ }
320
+ const childStyle = { ...style };
321
+ if (BOLD_TAGS.has(tag)) childStyle.bold = true;
322
+ if (ITALIC_TAGS.has(tag)) childStyle.italic = true;
323
+ if (UNDERLINE_TAGS.has(tag)) childStyle.underline = true;
324
+ if (STRIKE_TAGS.has(tag)) childStyle.strikethrough = true;
325
+ if (tag === "span") {
326
+ const fontSize = el.style.fontSize;
327
+ if (fontSize) {
328
+ childStyle.fontSize = parseInt(fontSize, 10) || style.fontSize;
329
+ }
330
+ }
331
+ walkNodes(el, childStyle, runs);
332
+ }
333
+ }
334
+ var ALLOWED_TAGS = /* @__PURE__ */ new Set([
335
+ "b",
336
+ "strong",
337
+ "i",
338
+ "em",
339
+ "u",
340
+ "s",
341
+ "strike",
342
+ "del",
343
+ "span",
344
+ "br",
345
+ "div"
346
+ ]);
347
+ function sanitizeNoteHtml(html) {
348
+ if (!html) return "";
349
+ const doc = new DOMParser().parseFromString(html, "text/html");
350
+ sanitizeNode(doc.body);
351
+ return doc.body.innerHTML;
352
+ }
353
+ function sanitizeNode(node) {
354
+ const children = Array.from(node.childNodes);
355
+ for (const child of children) {
356
+ if (child.nodeType === Node.TEXT_NODE) continue;
357
+ if (child.nodeType !== Node.ELEMENT_NODE) {
358
+ child.remove();
359
+ continue;
360
+ }
361
+ const el = child;
362
+ const tag = el.tagName.toLowerCase();
363
+ if (!ALLOWED_TAGS.has(tag)) {
364
+ const fragment = document.createDocumentFragment();
365
+ while (el.firstChild) {
366
+ fragment.appendChild(el.firstChild);
367
+ }
368
+ node.replaceChild(fragment, el);
369
+ sanitizeNode(node);
370
+ return;
371
+ }
372
+ sanitizeAttributes(el, tag);
373
+ sanitizeNode(el);
374
+ }
375
+ }
376
+ function sanitizeAttributes(el, tag) {
377
+ const attrs = Array.from(el.attributes);
378
+ for (const attr of attrs) {
379
+ if (tag === "span" && attr.name === "style") {
380
+ const fontSize = el.style.fontSize;
381
+ if (fontSize) {
382
+ el.setAttribute("style", `font-size: ${fontSize};`);
383
+ } else {
384
+ el.removeAttribute("style");
385
+ }
386
+ continue;
387
+ }
388
+ el.removeAttribute(attr.name);
389
+ }
390
+ }
391
+
257
392
  // src/core/state-serializer.ts
258
393
  var CURRENT_VERSION = 2;
259
394
  function exportState(elements, camera, layers = []) {
@@ -316,7 +451,17 @@ function validateState(data) {
316
451
  ];
317
452
  }
318
453
  }
319
- var VALID_TYPES = /* @__PURE__ */ new Set(["stroke", "note", "arrow", "image", "html", "text", "shape", "grid"]);
454
+ var VALID_TYPES = /* @__PURE__ */ new Set([
455
+ "stroke",
456
+ "note",
457
+ "arrow",
458
+ "image",
459
+ "html",
460
+ "text",
461
+ "shape",
462
+ "grid",
463
+ "template"
464
+ ]);
320
465
  function validateElement(el) {
321
466
  if (!el || typeof el !== "object") {
322
467
  throw new Error("Invalid element: expected an object");
@@ -366,6 +511,9 @@ function migrateElement(obj) {
366
511
  if (obj["type"] === "note" && typeof obj["textColor"] !== "string") {
367
512
  obj["textColor"] = "#000000";
368
513
  }
514
+ if (obj["type"] === "note" && typeof obj["text"] === "string") {
515
+ obj["text"] = sanitizeNoteHtml(obj["text"]);
516
+ }
369
517
  }
370
518
 
371
519
  // src/core/snap.ts
@@ -375,6 +523,30 @@ function snapPoint(point, gridSize) {
375
523
  y: Math.round(point.y / gridSize) * gridSize || 0
376
524
  };
377
525
  }
526
+ function snapToHexCenter(point, cellSize, orientation) {
527
+ if (orientation === "pointy") {
528
+ const hexW = Math.sqrt(3) * cellSize;
529
+ const rowH = 1.5 * cellSize;
530
+ const row = Math.round(point.y / rowH);
531
+ const offsetX = row % 2 !== 0 ? hexW / 2 : 0;
532
+ const col = Math.round((point.x - offsetX) / hexW);
533
+ return { x: col * hexW + offsetX || 0, y: row * rowH || 0 };
534
+ } else {
535
+ const hexH = Math.sqrt(3) * cellSize;
536
+ const colW = 1.5 * cellSize;
537
+ const col = Math.round(point.x / colW);
538
+ const offsetY = col % 2 !== 0 ? hexH / 2 : 0;
539
+ const row = Math.round((point.y - offsetY) / hexH);
540
+ return { x: col * colW || 0, y: row * hexH + offsetY || 0 };
541
+ }
542
+ }
543
+ function smartSnap(point, ctx) {
544
+ if (!ctx.snapToGrid || !ctx.gridSize) return point;
545
+ if (ctx.gridType === "hex" && ctx.hexOrientation) {
546
+ return snapToHexCenter(point, ctx.gridSize, ctx.hexOrientation);
547
+ }
548
+ return snapPoint(point, ctx.gridSize);
549
+ }
378
550
 
379
551
  // src/core/auto-save.ts
380
552
  var DEFAULT_KEY = "fieldnotes-autosave";
@@ -1027,6 +1199,9 @@ function getElementBounds(element) {
1027
1199
  if (element.type === "arrow") {
1028
1200
  return getArrowBoundsAnalytical(element.from, element.to, element.bend);
1029
1201
  }
1202
+ if (element.type === "template") {
1203
+ return getTemplateBounds(element);
1204
+ }
1030
1205
  return null;
1031
1206
  }
1032
1207
  function getArrowBoundsAnalytical(from, to, bend) {
@@ -1067,6 +1242,62 @@ function getArrowBoundsAnalytical(from, to, bend) {
1067
1242
  }
1068
1243
  return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
1069
1244
  }
1245
+ function getTemplateBounds(el) {
1246
+ const { x: cx, y: cy } = el.position;
1247
+ const r = el.radius;
1248
+ switch (el.templateShape) {
1249
+ case "circle":
1250
+ return { x: cx - r, y: cy - r, w: 2 * r, h: 2 * r };
1251
+ case "square":
1252
+ return { x: cx - r / 2, y: cy - r / 2, w: r, h: r };
1253
+ case "cone": {
1254
+ const halfAngle = Math.atan(0.5);
1255
+ const tipX = cx;
1256
+ const tipY = cy;
1257
+ const leftX = cx + r * Math.cos(el.angle - halfAngle);
1258
+ const leftY = cy + r * Math.sin(el.angle - halfAngle);
1259
+ const rightX = cx + r * Math.cos(el.angle + halfAngle);
1260
+ const rightY = cy + r * Math.sin(el.angle + halfAngle);
1261
+ const farX = cx + r * Math.cos(el.angle);
1262
+ const farY = cy + r * Math.sin(el.angle);
1263
+ const xs = [tipX, leftX, rightX, farX];
1264
+ const ys = [tipY, leftY, rightY, farY];
1265
+ let minX = Infinity;
1266
+ let minY = Infinity;
1267
+ let maxX = -Infinity;
1268
+ let maxY = -Infinity;
1269
+ for (let i = 0; i < xs.length; i++) {
1270
+ const px = xs[i];
1271
+ const py = ys[i];
1272
+ if (px !== void 0 && px < minX) minX = px;
1273
+ if (px !== void 0 && px > maxX) maxX = px;
1274
+ if (py !== void 0 && py < minY) minY = py;
1275
+ if (py !== void 0 && py > maxY) maxY = py;
1276
+ }
1277
+ return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
1278
+ }
1279
+ case "line": {
1280
+ const halfW = r / 12;
1281
+ const cos = Math.cos(el.angle);
1282
+ const sin = Math.sin(el.angle);
1283
+ const perpX = -sin * halfW;
1284
+ const perpY = cos * halfW;
1285
+ const x0 = cx + perpX;
1286
+ const y0 = cy + perpY;
1287
+ const x1 = cx + r * cos + perpX;
1288
+ const y1 = cy + r * sin + perpY;
1289
+ const x2 = cx + r * cos - perpX;
1290
+ const y2 = cy + r * sin - perpY;
1291
+ const x3 = cx - perpX;
1292
+ const y3 = cy - perpY;
1293
+ const minX = Math.min(x0, x1, x2, x3);
1294
+ const minY = Math.min(y0, y1, y2, y3);
1295
+ const maxX = Math.max(x0, x1, x2, x3);
1296
+ const maxY = Math.max(y0, y1, y2, y3);
1297
+ return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
1298
+ }
1299
+ }
1300
+ }
1070
1301
  function boundsIntersect(a, b) {
1071
1302
  return a.x <= b.x + b.w && a.x + a.w >= b.x && a.y <= b.y + b.h && a.y + a.h >= b.y;
1072
1303
  }
@@ -1587,6 +1818,190 @@ function renderHexGridTiled(ctx, bounds, cellSize, tile) {
1587
1818
  }
1588
1819
  }
1589
1820
 
1821
+ // src/elements/hex-fill.ts
1822
+ function offsetToCube(col, row, orientation) {
1823
+ if (orientation === "pointy") {
1824
+ return { q: col - (row - (row & 1)) / 2, r: row };
1825
+ }
1826
+ return { q: col, r: row - (col - (col & 1)) / 2 };
1827
+ }
1828
+ function cubeToOffset(q, r, orientation) {
1829
+ if (orientation === "pointy") {
1830
+ return { col: q + (r - (r & 1)) / 2, row: r };
1831
+ }
1832
+ return { col: q, row: r + (q - (q & 1)) / 2 };
1833
+ }
1834
+ function offsetToPixel(col, row, cellSize, orientation) {
1835
+ if (orientation === "pointy") {
1836
+ const hexW = Math.sqrt(3) * cellSize;
1837
+ const rowH = 1.5 * cellSize;
1838
+ const offsetX = row % 2 !== 0 ? hexW / 2 : 0;
1839
+ return { x: col * hexW + offsetX, y: row * rowH };
1840
+ }
1841
+ const hexH = Math.sqrt(3) * cellSize;
1842
+ const colW = 1.5 * cellSize;
1843
+ const offsetY = col % 2 !== 0 ? hexH / 2 : 0;
1844
+ return { x: col * colW, y: row * hexH + offsetY };
1845
+ }
1846
+ function pixelToOffset(x, y, cellSize, orientation) {
1847
+ if (orientation === "pointy") {
1848
+ const hexW = Math.sqrt(3) * cellSize;
1849
+ const rowH = 1.5 * cellSize;
1850
+ const row = Math.round(y / rowH);
1851
+ const offsetX = row % 2 !== 0 ? hexW / 2 : 0;
1852
+ return { col: Math.round((x - offsetX) / hexW), row };
1853
+ }
1854
+ const hexH = Math.sqrt(3) * cellSize;
1855
+ const colW = 1.5 * cellSize;
1856
+ const col = Math.round(x / colW);
1857
+ const offsetY = col % 2 !== 0 ? hexH / 2 : 0;
1858
+ return { col, row: Math.round((y - offsetY) / hexH) };
1859
+ }
1860
+ function enumerateHexRing(centerQ, centerR, n, orientation, cellSize) {
1861
+ const cells = [];
1862
+ for (let dq = -n; dq <= n; dq++) {
1863
+ const rMin = Math.max(-n, -dq - n);
1864
+ const rMax = Math.min(n, -dq + n);
1865
+ for (let dr = rMin; dr <= rMax; dr++) {
1866
+ const absQ = centerQ + dq;
1867
+ const absR = centerR + dr;
1868
+ const off = cubeToOffset(absQ, absR, orientation);
1869
+ cells.push(offsetToPixel(off.col, off.row, cellSize, orientation));
1870
+ }
1871
+ }
1872
+ return cells;
1873
+ }
1874
+ function getHexDistance(a, b, cellSize, orientation) {
1875
+ const offA = pixelToOffset(a.x, a.y, cellSize, orientation);
1876
+ const offB = pixelToOffset(b.x, b.y, cellSize, orientation);
1877
+ const cubeA = offsetToCube(offA.col, offA.row, orientation);
1878
+ const cubeB = offsetToCube(offB.col, offB.row, orientation);
1879
+ const dq = cubeA.q - cubeB.q;
1880
+ const dr = cubeA.r - cubeB.r;
1881
+ const ds = -dq - dr;
1882
+ return Math.max(Math.abs(dq), Math.abs(dr), Math.abs(ds));
1883
+ }
1884
+ function getHexCellsInRadius(center, radiusCells, cellSize, orientation) {
1885
+ const n = Math.round(radiusCells);
1886
+ const off = pixelToOffset(center.x, center.y, cellSize, orientation);
1887
+ const cube = offsetToCube(off.col, off.row, orientation);
1888
+ if (n <= 0) {
1889
+ return [offsetToPixel(off.col, off.row, cellSize, orientation)];
1890
+ }
1891
+ return enumerateHexRing(cube.q, cube.r, n, orientation, cellSize);
1892
+ }
1893
+ function getHexCellsInCone(center, angle, radiusCells, cellSize, orientation) {
1894
+ const n = Math.round(radiusCells);
1895
+ const off = pixelToOffset(center.x, center.y, cellSize, orientation);
1896
+ const cube = offsetToCube(off.col, off.row, orientation);
1897
+ const centerPixel = offsetToPixel(off.col, off.row, cellSize, orientation);
1898
+ if (n <= 0) return [centerPixel];
1899
+ const vertexOffset = orientation === "pointy" ? Math.PI / 6 : 0;
1900
+ const step = Math.PI / 3;
1901
+ const snappedAngle = Math.round((angle - vertexOffset) / step) * step + vertexOffset;
1902
+ const halfAngle = Math.PI / 6 + 1e-6;
1903
+ const cells = [centerPixel];
1904
+ for (let dq = -n; dq <= n; dq++) {
1905
+ const rMin = Math.max(-n, -dq - n);
1906
+ const rMax = Math.min(n, -dq + n);
1907
+ for (let dr = rMin; dr <= rMax; dr++) {
1908
+ if (dq === 0 && dr === 0) continue;
1909
+ const absQ = cube.q + dq;
1910
+ const absR = cube.r + dr;
1911
+ const pixel = offsetToPixel(
1912
+ cubeToOffset(absQ, absR, orientation).col,
1913
+ cubeToOffset(absQ, absR, orientation).row,
1914
+ cellSize,
1915
+ orientation
1916
+ );
1917
+ const dx = pixel.x - centerPixel.x;
1918
+ const dy = pixel.y - centerPixel.y;
1919
+ let diff = Math.atan2(dy, dx) - snappedAngle;
1920
+ if (diff > Math.PI) diff -= 2 * Math.PI;
1921
+ if (diff < -Math.PI) diff += 2 * Math.PI;
1922
+ if (Math.abs(diff) <= halfAngle) {
1923
+ cells.push(pixel);
1924
+ }
1925
+ }
1926
+ }
1927
+ return cells;
1928
+ }
1929
+ function getHexCellsInLine(center, angle, radiusCells, cellSize, orientation) {
1930
+ const n = Math.round(radiusCells);
1931
+ const off = pixelToOffset(center.x, center.y, cellSize, orientation);
1932
+ const cube = offsetToCube(off.col, off.row, orientation);
1933
+ const centerPixel = offsetToPixel(off.col, off.row, cellSize, orientation);
1934
+ if (n <= 0) return [centerPixel];
1935
+ const vertexOffset = orientation === "pointy" ? Math.PI / 6 : 0;
1936
+ const step = Math.PI / 3;
1937
+ const snappedAngle = Math.round((angle - vertexOffset) / step) * step + vertexOffset;
1938
+ const cos = Math.cos(snappedAngle);
1939
+ const sin = Math.sin(snappedAngle);
1940
+ const snapUnit = Math.sqrt(3) * cellSize;
1941
+ const lineLength = n * snapUnit;
1942
+ const halfWidth = snapUnit * 0.5 + 1e-6;
1943
+ const cells = [];
1944
+ for (let dq = -n; dq <= n; dq++) {
1945
+ const rMin = Math.max(-n, -dq - n);
1946
+ const rMax = Math.min(n, -dq + n);
1947
+ for (let dr = rMin; dr <= rMax; dr++) {
1948
+ const absQ = cube.q + dq;
1949
+ const absR = cube.r + dr;
1950
+ const pixel = offsetToPixel(
1951
+ cubeToOffset(absQ, absR, orientation).col,
1952
+ cubeToOffset(absQ, absR, orientation).row,
1953
+ cellSize,
1954
+ orientation
1955
+ );
1956
+ const dx = pixel.x - centerPixel.x;
1957
+ const dy = pixel.y - centerPixel.y;
1958
+ const along = dx * cos + dy * sin;
1959
+ const perp = Math.abs(-dx * sin + dy * cos);
1960
+ if (along >= -snapUnit * 0.1 && along <= lineLength + snapUnit * 0.1 && perp <= halfWidth) {
1961
+ cells.push(pixel);
1962
+ }
1963
+ }
1964
+ }
1965
+ return cells;
1966
+ }
1967
+ function getHexCellsInSquare(center, radiusCells, cellSize, orientation) {
1968
+ const n = Math.round(radiusCells);
1969
+ const off = pixelToOffset(center.x, center.y, cellSize, orientation);
1970
+ const cube = offsetToCube(off.col, off.row, orientation);
1971
+ const centerPixel = offsetToPixel(off.col, off.row, cellSize, orientation);
1972
+ if (n <= 0) return [centerPixel];
1973
+ const snapUnit = Math.sqrt(3) * cellSize;
1974
+ const halfSide = n * snapUnit / 2;
1975
+ const cells = [];
1976
+ for (let dq = -n; dq <= n; dq++) {
1977
+ const rMin = Math.max(-n, -dq - n);
1978
+ const rMax = Math.min(n, -dq + n);
1979
+ for (let dr = rMin; dr <= rMax; dr++) {
1980
+ const absQ = cube.q + dq;
1981
+ const absR = cube.r + dr;
1982
+ const pixel = offsetToPixel(
1983
+ cubeToOffset(absQ, absR, orientation).col,
1984
+ cubeToOffset(absQ, absR, orientation).row,
1985
+ cellSize,
1986
+ orientation
1987
+ );
1988
+ if (Math.abs(pixel.x - centerPixel.x) <= halfSide && Math.abs(pixel.y - centerPixel.y) <= halfSide) {
1989
+ cells.push(pixel);
1990
+ }
1991
+ }
1992
+ }
1993
+ return cells;
1994
+ }
1995
+ function drawHexPath(ctx, cx, cy, cellSize, orientation) {
1996
+ const angleOffset = orientation === "pointy" ? Math.PI / 6 : 0;
1997
+ ctx.moveTo(cx + cellSize * Math.cos(angleOffset), cy + cellSize * Math.sin(angleOffset));
1998
+ for (let i = 1; i < 6; i++) {
1999
+ const a = angleOffset + Math.PI / 3 * i;
2000
+ ctx.lineTo(cx + cellSize * Math.cos(a), cy + cellSize * Math.sin(a));
2001
+ }
2002
+ ctx.closePath();
2003
+ }
2004
+
1590
2005
  // src/elements/element-renderer.ts
1591
2006
  var DOM_ELEMENT_TYPES = /* @__PURE__ */ new Set(["note", "html", "text"]);
1592
2007
  var ARROWHEAD_LENGTH = 12;
@@ -1631,6 +2046,9 @@ var ElementRenderer = class {
1631
2046
  case "grid":
1632
2047
  this.renderGrid(ctx, element);
1633
2048
  break;
2049
+ case "template":
2050
+ this.renderTemplate(ctx, element);
2051
+ break;
1634
2052
  }
1635
2053
  }
1636
2054
  renderStroke(ctx, stroke) {
@@ -1818,6 +2236,147 @@ var ElementRenderer = class {
1818
2236
  );
1819
2237
  }
1820
2238
  }
2239
+ renderTemplate(ctx, template) {
2240
+ const grid = this.store?.getElementsByType("grid")[0];
2241
+ if (grid && grid.gridType === "hex") {
2242
+ this.renderHexTemplate(ctx, template, grid.cellSize, grid.hexOrientation);
2243
+ return;
2244
+ }
2245
+ this.renderGeometricTemplate(ctx, template);
2246
+ }
2247
+ renderGeometricTemplate(ctx, template) {
2248
+ const { x: cx, y: cy } = template.position;
2249
+ const r = template.radius;
2250
+ ctx.save();
2251
+ ctx.globalAlpha = template.opacity;
2252
+ ctx.fillStyle = template.fillColor;
2253
+ ctx.strokeStyle = template.strokeColor;
2254
+ ctx.lineWidth = template.strokeWidth;
2255
+ switch (template.templateShape) {
2256
+ case "circle":
2257
+ ctx.beginPath();
2258
+ ctx.arc(cx, cy, r, 0, Math.PI * 2);
2259
+ ctx.fill();
2260
+ ctx.stroke();
2261
+ if (template.radiusFeet != null && template.radiusFeet > 0) {
2262
+ this.renderRadiusMarker(ctx, cx, cy, r, template.radiusFeet);
2263
+ }
2264
+ break;
2265
+ case "square":
2266
+ ctx.fillRect(cx - r / 2, cy - r / 2, r, r);
2267
+ ctx.strokeRect(cx - r / 2, cy - r / 2, r, r);
2268
+ break;
2269
+ case "cone": {
2270
+ const halfAngle = Math.atan(0.5);
2271
+ ctx.beginPath();
2272
+ ctx.moveTo(cx, cy);
2273
+ ctx.arc(cx, cy, r, template.angle - halfAngle, template.angle + halfAngle);
2274
+ ctx.closePath();
2275
+ ctx.fill();
2276
+ ctx.stroke();
2277
+ break;
2278
+ }
2279
+ case "line": {
2280
+ const halfW = r / 12;
2281
+ const cos = Math.cos(template.angle);
2282
+ const sin = Math.sin(template.angle);
2283
+ const perpX = -sin * halfW;
2284
+ const perpY = cos * halfW;
2285
+ ctx.beginPath();
2286
+ ctx.moveTo(cx + perpX, cy + perpY);
2287
+ ctx.lineTo(cx + r * cos + perpX, cy + r * sin + perpY);
2288
+ ctx.lineTo(cx + r * cos - perpX, cy + r * sin - perpY);
2289
+ ctx.lineTo(cx - perpX, cy - perpY);
2290
+ ctx.closePath();
2291
+ ctx.fill();
2292
+ ctx.stroke();
2293
+ break;
2294
+ }
2295
+ }
2296
+ ctx.restore();
2297
+ }
2298
+ renderHexTemplate(ctx, template, cellSize, orientation) {
2299
+ const snapUnit = Math.sqrt(3) * cellSize;
2300
+ const radiusCells = template.radius / snapUnit;
2301
+ const center = template.position;
2302
+ let cells;
2303
+ switch (template.templateShape) {
2304
+ case "circle":
2305
+ cells = getHexCellsInRadius(center, radiusCells, cellSize, orientation);
2306
+ break;
2307
+ case "cone":
2308
+ cells = getHexCellsInCone(center, template.angle, radiusCells, cellSize, orientation);
2309
+ break;
2310
+ case "line":
2311
+ cells = getHexCellsInLine(center, template.angle, radiusCells, cellSize, orientation);
2312
+ break;
2313
+ case "square":
2314
+ cells = getHexCellsInSquare(center, radiusCells, cellSize, orientation);
2315
+ break;
2316
+ }
2317
+ ctx.save();
2318
+ ctx.globalAlpha = template.opacity;
2319
+ ctx.beginPath();
2320
+ for (const cell of cells) {
2321
+ drawHexPath(ctx, cell.x, cell.y, cellSize, orientation);
2322
+ }
2323
+ ctx.fillStyle = template.fillColor;
2324
+ ctx.fill();
2325
+ ctx.beginPath();
2326
+ for (const cell of cells) {
2327
+ drawHexPath(ctx, cell.x, cell.y, cellSize, orientation);
2328
+ }
2329
+ ctx.strokeStyle = template.strokeColor;
2330
+ ctx.lineWidth = template.strokeWidth;
2331
+ ctx.stroke();
2332
+ {
2333
+ ctx.globalAlpha = Math.min(template.opacity + 0.1, 1);
2334
+ ctx.beginPath();
2335
+ drawHexPath(ctx, center.x, center.y, cellSize, orientation);
2336
+ ctx.fillStyle = template.strokeColor;
2337
+ ctx.fill();
2338
+ ctx.strokeStyle = template.strokeColor;
2339
+ ctx.lineWidth = template.strokeWidth;
2340
+ ctx.stroke();
2341
+ }
2342
+ if (template.templateShape === "circle" && template.radiusFeet != null && template.radiusFeet > 0) {
2343
+ const r = template.radius;
2344
+ this.renderRadiusMarker(ctx, center.x, center.y, r, template.radiusFeet);
2345
+ }
2346
+ ctx.restore();
2347
+ }
2348
+ renderRadiusMarker(ctx, cx, cy, r, feet) {
2349
+ const markerColor = ctx.strokeStyle;
2350
+ ctx.save();
2351
+ ctx.globalAlpha = 1;
2352
+ ctx.beginPath();
2353
+ ctx.setLineDash([4, 4]);
2354
+ ctx.strokeStyle = markerColor;
2355
+ ctx.lineWidth = 1.5;
2356
+ ctx.moveTo(cx, cy);
2357
+ ctx.lineTo(cx + r, cy);
2358
+ ctx.stroke();
2359
+ ctx.setLineDash([]);
2360
+ const label = `${Math.round(feet)} ft`;
2361
+ const fontSize = Math.max(10, Math.min(14, r * 0.15));
2362
+ ctx.font = `bold ${fontSize}px system-ui, sans-serif`;
2363
+ ctx.textAlign = "center";
2364
+ ctx.textBaseline = "bottom";
2365
+ const textX = cx + r / 2;
2366
+ const textY = cy - 4;
2367
+ const metrics = ctx.measureText(label);
2368
+ const padX = 4;
2369
+ const padY = 2;
2370
+ const textW = metrics.width + padX * 2;
2371
+ const textH = fontSize + padY * 2;
2372
+ ctx.fillStyle = "rgba(255, 255, 255, 0.85)";
2373
+ ctx.beginPath();
2374
+ ctx.roundRect(textX - textW / 2, textY - textH, textW, textH, 3);
2375
+ ctx.fill();
2376
+ ctx.fillStyle = markerColor;
2377
+ ctx.fillText(label, textX, textY - padY);
2378
+ ctx.restore();
2379
+ }
1821
2380
  renderImage(ctx, image) {
1822
2381
  const img = this.getImage(image.src);
1823
2382
  if (!img) return;
@@ -1864,7 +2423,359 @@ var ElementRenderer = class {
1864
2423
  }
1865
2424
  };
1866
2425
 
2426
+ // src/elements/create-id.ts
2427
+ var counter = 0;
2428
+ function createId(prefix) {
2429
+ return `${prefix}_${Date.now().toString(36)}_${(counter++).toString(36)}`;
2430
+ }
2431
+
2432
+ // src/elements/element-factory.ts
2433
+ var DEFAULT_NOTE_FONT_SIZE = 18;
2434
+ function createStroke(input) {
2435
+ return {
2436
+ id: createId("stroke"),
2437
+ type: "stroke",
2438
+ position: input.position ?? { x: 0, y: 0 },
2439
+ zIndex: input.zIndex ?? 0,
2440
+ locked: input.locked ?? false,
2441
+ layerId: input.layerId ?? "",
2442
+ points: input.points,
2443
+ color: input.color ?? "#000000",
2444
+ width: input.width ?? 2,
2445
+ opacity: input.opacity ?? 1
2446
+ };
2447
+ }
2448
+ function createNote(input) {
2449
+ return {
2450
+ id: createId("note"),
2451
+ type: "note",
2452
+ position: input.position,
2453
+ zIndex: input.zIndex ?? 0,
2454
+ locked: input.locked ?? false,
2455
+ layerId: input.layerId ?? "",
2456
+ size: input.size ?? { w: 200, h: 100 },
2457
+ text: input.text ?? "",
2458
+ backgroundColor: input.backgroundColor ?? "#ffeb3b",
2459
+ textColor: input.textColor ?? "#000000",
2460
+ fontSize: input.fontSize ?? DEFAULT_NOTE_FONT_SIZE
2461
+ };
2462
+ }
2463
+ function createArrow(input) {
2464
+ const bend = input.bend ?? 0;
2465
+ const result = {
2466
+ id: createId("arrow"),
2467
+ type: "arrow",
2468
+ position: input.position ?? { x: 0, y: 0 },
2469
+ zIndex: input.zIndex ?? 0,
2470
+ locked: input.locked ?? false,
2471
+ layerId: input.layerId ?? "",
2472
+ from: input.from,
2473
+ to: input.to,
2474
+ bend,
2475
+ color: input.color ?? "#000000",
2476
+ width: input.width ?? 2,
2477
+ cachedControlPoint: getArrowControlPoint(input.from, input.to, bend)
2478
+ };
2479
+ if (input.fromBinding) result.fromBinding = input.fromBinding;
2480
+ if (input.toBinding) result.toBinding = input.toBinding;
2481
+ return result;
2482
+ }
2483
+ function createImage(input) {
2484
+ return {
2485
+ id: createId("image"),
2486
+ type: "image",
2487
+ position: input.position,
2488
+ zIndex: input.zIndex ?? 0,
2489
+ locked: input.locked ?? false,
2490
+ layerId: input.layerId ?? "",
2491
+ size: input.size,
2492
+ src: input.src
2493
+ };
2494
+ }
2495
+ function createHtmlElement(input) {
2496
+ const el = {
2497
+ id: createId("html"),
2498
+ type: "html",
2499
+ position: input.position,
2500
+ zIndex: input.zIndex ?? 0,
2501
+ locked: input.locked ?? false,
2502
+ layerId: input.layerId ?? "",
2503
+ size: input.size
2504
+ };
2505
+ if (input.domId) el.domId = input.domId;
2506
+ return el;
2507
+ }
2508
+ function createShape(input) {
2509
+ return {
2510
+ id: createId("shape"),
2511
+ type: "shape",
2512
+ position: input.position,
2513
+ zIndex: input.zIndex ?? 0,
2514
+ locked: input.locked ?? false,
2515
+ layerId: input.layerId ?? "",
2516
+ shape: input.shape ?? "rectangle",
2517
+ size: input.size,
2518
+ strokeColor: input.strokeColor ?? "#000000",
2519
+ strokeWidth: input.strokeWidth ?? 2,
2520
+ fillColor: input.fillColor ?? "none"
2521
+ };
2522
+ }
2523
+ function createGrid(input) {
2524
+ return {
2525
+ id: createId("grid"),
2526
+ type: "grid",
2527
+ position: input.position ?? { x: 0, y: 0 },
2528
+ zIndex: input.zIndex ?? 0,
2529
+ locked: input.locked ?? false,
2530
+ layerId: input.layerId ?? "",
2531
+ gridType: input.gridType ?? "square",
2532
+ hexOrientation: input.hexOrientation ?? "pointy",
2533
+ cellSize: input.cellSize ?? 40,
2534
+ strokeColor: input.strokeColor ?? "#000000",
2535
+ strokeWidth: input.strokeWidth ?? 1,
2536
+ opacity: input.opacity ?? 1
2537
+ };
2538
+ }
2539
+ function createText(input) {
2540
+ return {
2541
+ id: createId("text"),
2542
+ type: "text",
2543
+ position: input.position,
2544
+ zIndex: input.zIndex ?? 0,
2545
+ locked: input.locked ?? false,
2546
+ layerId: input.layerId ?? "",
2547
+ size: input.size ?? { w: 200, h: 28 },
2548
+ text: input.text ?? "",
2549
+ fontSize: input.fontSize ?? 16,
2550
+ color: input.color ?? "#1a1a1a",
2551
+ textAlign: input.textAlign ?? "left"
2552
+ };
2553
+ }
2554
+ function createTemplate(input) {
2555
+ return {
2556
+ id: createId("template"),
2557
+ type: "template",
2558
+ position: input.position,
2559
+ zIndex: input.zIndex ?? 0,
2560
+ locked: input.locked ?? false,
2561
+ layerId: input.layerId ?? "",
2562
+ templateShape: input.templateShape,
2563
+ radius: input.radius,
2564
+ angle: input.angle ?? 0,
2565
+ fillColor: input.fillColor ?? "rgba(255, 87, 34, 0.2)",
2566
+ strokeColor: input.strokeColor ?? "#FF5722",
2567
+ strokeWidth: input.strokeWidth ?? 2,
2568
+ opacity: input.opacity ?? 0.6,
2569
+ feetPerCell: input.feetPerCell,
2570
+ radiusFeet: input.radiusFeet
2571
+ };
2572
+ }
2573
+
2574
+ // src/elements/note-formatting.ts
2575
+ function toggleBold() {
2576
+ document.execCommand("bold");
2577
+ }
2578
+ function toggleItalic() {
2579
+ document.execCommand("italic");
2580
+ }
2581
+ function toggleUnderline() {
2582
+ document.execCommand("underline");
2583
+ }
2584
+ function toggleStrikethrough() {
2585
+ document.execCommand("strikeThrough");
2586
+ }
2587
+ function setFontSize(size) {
2588
+ const sel = window.getSelection();
2589
+ if (!sel || sel.rangeCount === 0) return;
2590
+ const range = sel.getRangeAt(0);
2591
+ if (range.collapsed) return;
2592
+ const span = document.createElement("span");
2593
+ span.style.fontSize = `${size}px`;
2594
+ try {
2595
+ range.surroundContents(span);
2596
+ } catch {
2597
+ span.appendChild(range.extractContents());
2598
+ range.insertNode(span);
2599
+ }
2600
+ }
2601
+ function getActiveFormats() {
2602
+ const query = (cmd) => {
2603
+ try {
2604
+ return document.queryCommandState(cmd);
2605
+ } catch {
2606
+ return false;
2607
+ }
2608
+ };
2609
+ return {
2610
+ bold: query("bold"),
2611
+ italic: query("italic"),
2612
+ underline: query("underline"),
2613
+ strikethrough: query("strikeThrough")
2614
+ };
2615
+ }
2616
+
2617
+ // src/elements/note-toolbar.ts
2618
+ var TOOLBAR_HEIGHT = 32;
2619
+ var TOOLBAR_GAP = 4;
2620
+ var FORMAT_BUTTONS = [
2621
+ { label: "B", format: "bold", command: "bold" },
2622
+ { label: "I", format: "italic", command: "italic" },
2623
+ { label: "U", format: "underline", command: "underline" },
2624
+ { label: "S", format: "strikethrough", command: "strikeThrough" }
2625
+ ];
2626
+ var DEFAULT_FONT_SIZE_PRESETS = [
2627
+ { label: "Small", size: 14 },
2628
+ { label: "Normal", size: 18 },
2629
+ { label: "Large", size: 24 },
2630
+ { label: "Heading", size: 32 }
2631
+ ];
2632
+ var NoteToolbar = class {
2633
+ el = null;
2634
+ anchor = null;
2635
+ selectionListener = null;
2636
+ fontSizePresets;
2637
+ constructor(fontSizePresets) {
2638
+ this.fontSizePresets = fontSizePresets ?? DEFAULT_FONT_SIZE_PRESETS;
2639
+ }
2640
+ show(anchor) {
2641
+ this.hide();
2642
+ this.anchor = anchor;
2643
+ this.el = this.createToolbarElement();
2644
+ document.body.appendChild(this.el);
2645
+ this.positionToolbar(anchor);
2646
+ this.selectionListener = () => this.updateActiveStates();
2647
+ document.addEventListener("selectionchange", this.selectionListener);
2648
+ }
2649
+ hide() {
2650
+ if (this.selectionListener) {
2651
+ document.removeEventListener("selectionchange", this.selectionListener);
2652
+ this.selectionListener = null;
2653
+ }
2654
+ if (this.el) {
2655
+ this.el.remove();
2656
+ this.el = null;
2657
+ }
2658
+ this.anchor = null;
2659
+ }
2660
+ getElement() {
2661
+ return this.el;
2662
+ }
2663
+ updatePosition(anchor) {
2664
+ if (this.el) {
2665
+ this.positionToolbar(anchor);
2666
+ }
2667
+ }
2668
+ createToolbarElement() {
2669
+ const toolbar = document.createElement("div");
2670
+ toolbar.dataset["noteToolbar"] = "";
2671
+ Object.assign(toolbar.style, {
2672
+ position: "fixed",
2673
+ display: "flex",
2674
+ alignItems: "center",
2675
+ gap: "2px",
2676
+ padding: "2px 4px",
2677
+ background: "#fff",
2678
+ border: "1px solid #ccc",
2679
+ borderRadius: "4px",
2680
+ boxShadow: "0 2px 8px rgba(0,0,0,0.15)",
2681
+ zIndex: "10000",
2682
+ height: `${TOOLBAR_HEIGHT}px`,
2683
+ userSelect: "none"
2684
+ });
2685
+ for (const btn of FORMAT_BUTTONS) {
2686
+ toolbar.appendChild(this.createFormatButton(btn));
2687
+ }
2688
+ toolbar.appendChild(this.createFontSizeSelect());
2689
+ return toolbar;
2690
+ }
2691
+ createFormatButton(config) {
2692
+ const btn = document.createElement("button");
2693
+ btn.dataset["format"] = config.format;
2694
+ btn.textContent = config.label;
2695
+ Object.assign(btn.style, {
2696
+ border: "1px solid transparent",
2697
+ borderRadius: "3px",
2698
+ background: "none",
2699
+ cursor: "pointer",
2700
+ padding: "2px 6px",
2701
+ fontSize: "13px",
2702
+ fontWeight: config.format === "bold" ? "bold" : "normal",
2703
+ fontStyle: config.format === "italic" ? "italic" : "normal",
2704
+ textDecoration: config.format === "underline" ? "underline" : config.format === "strikethrough" ? "line-through" : "none",
2705
+ minWidth: "24px",
2706
+ height: "24px",
2707
+ lineHeight: "24px"
2708
+ });
2709
+ btn.addEventListener("pointerdown", (e) => {
2710
+ e.preventDefault();
2711
+ document.execCommand(config.command);
2712
+ this.updateActiveStates();
2713
+ });
2714
+ return btn;
2715
+ }
2716
+ createFontSizeSelect() {
2717
+ const select = document.createElement("select");
2718
+ Object.assign(select.style, {
2719
+ border: "1px solid #ccc",
2720
+ borderRadius: "3px",
2721
+ background: "#fff",
2722
+ cursor: "pointer",
2723
+ padding: "2px",
2724
+ fontSize: "12px",
2725
+ height: "24px",
2726
+ marginLeft: "4px"
2727
+ });
2728
+ for (const preset of this.fontSizePresets) {
2729
+ const option = document.createElement("option");
2730
+ option.value = String(preset.size);
2731
+ option.textContent = preset.label;
2732
+ select.appendChild(option);
2733
+ }
2734
+ select.value = String(DEFAULT_NOTE_FONT_SIZE);
2735
+ select.addEventListener("pointerdown", (e) => {
2736
+ e.stopPropagation();
2737
+ });
2738
+ select.addEventListener("change", () => {
2739
+ setFontSize(Number(select.value));
2740
+ this.updateActiveStates();
2741
+ this.anchor?.focus();
2742
+ });
2743
+ return select;
2744
+ }
2745
+ positionToolbar(anchor) {
2746
+ if (!this.el) return;
2747
+ const rect = anchor.getBoundingClientRect();
2748
+ const toolbarWidth = this.el.offsetWidth || 200;
2749
+ let top = rect.top - TOOLBAR_HEIGHT - TOOLBAR_GAP;
2750
+ if (top < 0) {
2751
+ top = rect.bottom + TOOLBAR_GAP;
2752
+ }
2753
+ let left = rect.left + (rect.width - toolbarWidth) / 2;
2754
+ left = Math.max(4, left);
2755
+ Object.assign(this.el.style, {
2756
+ top: `${top}px`,
2757
+ left: `${left}px`
2758
+ });
2759
+ }
2760
+ updateActiveStates() {
2761
+ if (!this.el) return;
2762
+ const active = getActiveFormats();
2763
+ for (const config of FORMAT_BUTTONS) {
2764
+ const btn = this.el.querySelector(`[data-format="${config.format}"]`);
2765
+ if (!btn) continue;
2766
+ const isActive = active[config.format] ?? false;
2767
+ btn.style.background = isActive ? "#e0e0e0" : "none";
2768
+ btn.style.borderColor = isActive ? "#bbb" : "transparent";
2769
+ }
2770
+ }
2771
+ };
2772
+
1867
2773
  // src/elements/note-editor.ts
2774
+ var FORMAT_SHORTCUTS = {
2775
+ b: toggleBold,
2776
+ i: toggleItalic,
2777
+ u: toggleUnderline
2778
+ };
1868
2779
  var NoteEditor = class {
1869
2780
  editingId = null;
1870
2781
  editingNode = null;
@@ -1873,6 +2784,10 @@ var NoteEditor = class {
1873
2784
  pointerHandler = null;
1874
2785
  pendingEditId = null;
1875
2786
  onStopCallback = null;
2787
+ toolbar;
2788
+ constructor(options) {
2789
+ this.toolbar = options?.toolbar === false ? null : new NoteToolbar(options?.fontSizePresets);
2790
+ }
1876
2791
  get isEditing() {
1877
2792
  return this.editingId !== null;
1878
2793
  }
@@ -1897,13 +2812,6 @@ var NoteEditor = class {
1897
2812
  stopEditing(store) {
1898
2813
  this.pendingEditId = null;
1899
2814
  if (!this.editingId || !this.editingNode) return;
1900
- const text = this.editingNode.textContent ?? "";
1901
- store.update(this.editingId, { text });
1902
- this.editingNode.contentEditable = "false";
1903
- Object.assign(this.editingNode.style, {
1904
- userSelect: "none",
1905
- cursor: "default"
1906
- });
1907
2815
  if (this.blurHandler) {
1908
2816
  this.editingNode.removeEventListener("blur", this.blurHandler);
1909
2817
  }
@@ -1913,6 +2821,14 @@ var NoteEditor = class {
1913
2821
  if (this.pointerHandler) {
1914
2822
  this.editingNode.removeEventListener("pointerdown", this.pointerHandler);
1915
2823
  }
2824
+ const text = sanitizeNoteHtml(this.editingNode.innerHTML);
2825
+ store.update(this.editingId, { text });
2826
+ this.editingNode.contentEditable = "false";
2827
+ Object.assign(this.editingNode.style, {
2828
+ userSelect: "none",
2829
+ cursor: "default"
2830
+ });
2831
+ this.toolbar?.hide();
1916
2832
  if (this.editingId && this.onStopCallback) {
1917
2833
  this.onStopCallback(this.editingId);
1918
2834
  }
@@ -1928,6 +2844,11 @@ var NoteEditor = class {
1928
2844
  this.stopEditing(store);
1929
2845
  }
1930
2846
  }
2847
+ updateToolbarPosition() {
2848
+ if (this.editingNode) {
2849
+ this.toolbar?.updatePosition(this.editingNode);
2850
+ }
2851
+ }
1931
2852
  activateEditing(node, elementId, store) {
1932
2853
  this.editingId = elementId;
1933
2854
  this.editingNode = node;
@@ -1946,8 +2867,21 @@ var NoteEditor = class {
1946
2867
  selection.removeAllRanges();
1947
2868
  selection.addRange(range);
1948
2869
  }
1949
- this.blurHandler = () => this.stopEditing(store);
2870
+ this.toolbar?.show(node);
2871
+ this.blurHandler = (e) => {
2872
+ const related = e.relatedTarget;
2873
+ if (related && this.toolbar?.getElement()?.contains(related)) return;
2874
+ this.stopEditing(store);
2875
+ };
1950
2876
  this.keyHandler = (e) => {
2877
+ if ((e.ctrlKey || e.metaKey) && !e.shiftKey && !e.altKey) {
2878
+ const action = FORMAT_SHORTCUTS[e.key.toLowerCase()];
2879
+ if (action) {
2880
+ e.preventDefault();
2881
+ action();
2882
+ return;
2883
+ }
2884
+ }
1951
2885
  if (e.key === "Escape") {
1952
2886
  node.blur();
1953
2887
  }
@@ -2199,131 +3133,86 @@ var HistoryRecorder = class {
2199
3133
  }
2200
3134
  };
2201
3135
 
2202
- // src/elements/create-id.ts
2203
- var counter = 0;
2204
- function createId(prefix) {
2205
- return `${prefix}_${Date.now().toString(36)}_${(counter++).toString(36)}`;
2206
- }
2207
-
2208
- // src/elements/element-factory.ts
2209
- function createStroke(input) {
2210
- return {
2211
- id: createId("stroke"),
2212
- type: "stroke",
2213
- position: input.position ?? { x: 0, y: 0 },
2214
- zIndex: input.zIndex ?? 0,
2215
- locked: input.locked ?? false,
2216
- layerId: input.layerId ?? "",
2217
- points: input.points,
2218
- color: input.color ?? "#000000",
2219
- width: input.width ?? 2,
2220
- opacity: input.opacity ?? 1
2221
- };
2222
- }
2223
- function createNote(input) {
2224
- return {
2225
- id: createId("note"),
2226
- type: "note",
2227
- position: input.position,
2228
- zIndex: input.zIndex ?? 0,
2229
- locked: input.locked ?? false,
2230
- layerId: input.layerId ?? "",
2231
- size: input.size ?? { w: 200, h: 100 },
2232
- text: input.text ?? "",
2233
- backgroundColor: input.backgroundColor ?? "#ffeb3b",
2234
- textColor: input.textColor ?? "#000000"
2235
- };
2236
- }
2237
- function createArrow(input) {
2238
- const bend = input.bend ?? 0;
2239
- const result = {
2240
- id: createId("arrow"),
2241
- type: "arrow",
2242
- position: input.position ?? { x: 0, y: 0 },
2243
- zIndex: input.zIndex ?? 0,
2244
- locked: input.locked ?? false,
2245
- layerId: input.layerId ?? "",
2246
- from: input.from,
2247
- to: input.to,
2248
- bend,
2249
- color: input.color ?? "#000000",
2250
- width: input.width ?? 2,
2251
- cachedControlPoint: getArrowControlPoint(input.from, input.to, bend)
2252
- };
2253
- if (input.fromBinding) result.fromBinding = input.fromBinding;
2254
- if (input.toBinding) result.toBinding = input.toBinding;
2255
- return result;
2256
- }
2257
- function createImage(input) {
2258
- return {
2259
- id: createId("image"),
2260
- type: "image",
2261
- position: input.position,
2262
- zIndex: input.zIndex ?? 0,
2263
- locked: input.locked ?? false,
2264
- layerId: input.layerId ?? "",
2265
- size: input.size,
2266
- src: input.src
2267
- };
2268
- }
2269
- function createHtmlElement(input) {
2270
- const el = {
2271
- id: createId("html"),
2272
- type: "html",
2273
- position: input.position,
2274
- zIndex: input.zIndex ?? 0,
2275
- locked: input.locked ?? false,
2276
- layerId: input.layerId ?? "",
2277
- size: input.size
2278
- };
2279
- if (input.domId) el.domId = input.domId;
2280
- return el;
2281
- }
2282
- function createShape(input) {
2283
- return {
2284
- id: createId("shape"),
2285
- type: "shape",
2286
- position: input.position,
2287
- zIndex: input.zIndex ?? 0,
2288
- locked: input.locked ?? false,
2289
- layerId: input.layerId ?? "",
2290
- shape: input.shape ?? "rectangle",
2291
- size: input.size,
2292
- strokeColor: input.strokeColor ?? "#000000",
2293
- strokeWidth: input.strokeWidth ?? 2,
2294
- fillColor: input.fillColor ?? "none"
2295
- };
2296
- }
2297
- function createGrid(input) {
2298
- return {
2299
- id: createId("grid"),
2300
- type: "grid",
2301
- position: input.position ?? { x: 0, y: 0 },
2302
- zIndex: input.zIndex ?? 0,
2303
- locked: input.locked ?? false,
2304
- layerId: input.layerId ?? "",
2305
- gridType: input.gridType ?? "square",
2306
- hexOrientation: input.hexOrientation ?? "pointy",
2307
- cellSize: input.cellSize ?? 40,
2308
- strokeColor: input.strokeColor ?? "#000000",
2309
- strokeWidth: input.strokeWidth ?? 1,
2310
- opacity: input.opacity ?? 1
2311
- };
2312
- }
2313
- function createText(input) {
2314
- return {
2315
- id: createId("text"),
2316
- type: "text",
2317
- position: input.position,
2318
- zIndex: input.zIndex ?? 0,
2319
- locked: input.locked ?? false,
2320
- layerId: input.layerId ?? "",
2321
- size: input.size ?? { w: 200, h: 28 },
2322
- text: input.text ?? "",
2323
- fontSize: input.fontSize ?? 16,
2324
- color: input.color ?? "#1a1a1a",
2325
- textAlign: input.textAlign ?? "left"
2326
- };
3136
+ // src/canvas/note-canvas-renderer.ts
3137
+ function renderNoteOnCanvas(ctx, note) {
3138
+ const { x, y } = note.position;
3139
+ const { w, h } = note.size;
3140
+ const r = 4;
3141
+ const pad = 8;
3142
+ const baseFontSize = note.fontSize ?? DEFAULT_NOTE_FONT_SIZE;
3143
+ ctx.save();
3144
+ ctx.fillStyle = note.backgroundColor;
3145
+ ctx.beginPath();
3146
+ ctx.moveTo(x + r, y);
3147
+ ctx.lineTo(x + w - r, y);
3148
+ ctx.arcTo(x + w, y, x + w, y + r, r);
3149
+ ctx.lineTo(x + w, y + h - r);
3150
+ ctx.arcTo(x + w, y + h, x + w - r, y + h, r);
3151
+ ctx.lineTo(x + r, y + h);
3152
+ ctx.arcTo(x, y + h, x, y + h - r, r);
3153
+ ctx.lineTo(x, y + r);
3154
+ ctx.arcTo(x, y, x + r, y, r);
3155
+ ctx.closePath();
3156
+ ctx.fill();
3157
+ if (note.text) {
3158
+ ctx.fillStyle = note.textColor;
3159
+ const runs = parseStyledRuns(note.text, baseFontSize);
3160
+ renderStyledRuns(ctx, runs, x + pad, y + pad, w - pad * 2);
3161
+ }
3162
+ ctx.restore();
3163
+ }
3164
+ function buildFontString(run) {
3165
+ const style = run.italic ? "italic" : "normal";
3166
+ const weight = run.bold ? "bold" : "normal";
3167
+ return `${style} ${weight} ${run.fontSize}px system-ui, sans-serif`;
3168
+ }
3169
+ function renderStyledRuns(ctx, runs, startX, startY, maxWidth) {
3170
+ ctx.textBaseline = "top";
3171
+ let cursorX = startX;
3172
+ let cursorY = startY;
3173
+ let lineHeight = 0;
3174
+ for (const run of runs) {
3175
+ ctx.font = buildFontString(run);
3176
+ const runLineHeight = run.fontSize * 1.3;
3177
+ lineHeight = Math.max(lineHeight, runLineHeight);
3178
+ const words = run.text.split(/(\n| )/);
3179
+ for (const word of words) {
3180
+ if (word === "\n") {
3181
+ cursorX = startX;
3182
+ cursorY += lineHeight;
3183
+ lineHeight = runLineHeight;
3184
+ continue;
3185
+ }
3186
+ if (word === " ") {
3187
+ const spaceWidth = ctx.measureText(" ").width;
3188
+ if (cursorX + spaceWidth > startX + maxWidth && cursorX > startX) {
3189
+ cursorX = startX;
3190
+ cursorY += lineHeight;
3191
+ lineHeight = runLineHeight;
3192
+ } else {
3193
+ cursorX += spaceWidth;
3194
+ }
3195
+ continue;
3196
+ }
3197
+ if (!word) continue;
3198
+ const metrics = ctx.measureText(word);
3199
+ if (cursorX + metrics.width > startX + maxWidth && cursorX > startX) {
3200
+ cursorX = startX;
3201
+ cursorY += lineHeight;
3202
+ lineHeight = runLineHeight;
3203
+ }
3204
+ ctx.fillText(word, cursorX, cursorY);
3205
+ if (run.underline) {
3206
+ const underY = cursorY + run.fontSize + 1;
3207
+ ctx.fillRect(cursorX, underY, metrics.width, 1);
3208
+ }
3209
+ if (run.strikethrough) {
3210
+ const strikeY = cursorY + run.fontSize * 0.55;
3211
+ ctx.fillRect(cursorX, strikeY, metrics.width, 1);
3212
+ }
3213
+ cursorX += metrics.width;
3214
+ }
3215
+ }
2327
3216
  }
2328
3217
 
2329
3218
  // src/canvas/export-image.ts
@@ -2361,6 +3250,11 @@ function getElementRect(el) {
2361
3250
  }
2362
3251
  case "grid":
2363
3252
  return null;
3253
+ case "template": {
3254
+ const bounds = getElementBounds(el);
3255
+ if (!bounds) return null;
3256
+ return bounds;
3257
+ }
2364
3258
  case "note":
2365
3259
  case "image":
2366
3260
  case "html":
@@ -2397,33 +3291,6 @@ function computeBounds(elements, padding) {
2397
3291
  h: maxY - minY + padding * 2
2398
3292
  };
2399
3293
  }
2400
- function renderNoteOnCanvas(ctx, note) {
2401
- const { x, y } = note.position;
2402
- const { w, h } = note.size;
2403
- const r = 4;
2404
- const pad = 8;
2405
- ctx.save();
2406
- ctx.fillStyle = note.backgroundColor;
2407
- ctx.beginPath();
2408
- ctx.moveTo(x + r, y);
2409
- ctx.lineTo(x + w - r, y);
2410
- ctx.arcTo(x + w, y, x + w, y + r, r);
2411
- ctx.lineTo(x + w, y + h - r);
2412
- ctx.arcTo(x + w, y + h, x + w - r, y + h, r);
2413
- ctx.lineTo(x + r, y + h);
2414
- ctx.arcTo(x, y + h, x, y + h - r, r);
2415
- ctx.lineTo(x, y + r);
2416
- ctx.arcTo(x, y, x + r, y, r);
2417
- ctx.closePath();
2418
- ctx.fill();
2419
- if (note.text) {
2420
- ctx.fillStyle = note.textColor;
2421
- ctx.font = "14px system-ui, sans-serif";
2422
- ctx.textBaseline = "top";
2423
- wrapText(ctx, note.text, x + pad, y + pad, w - pad * 2, 18);
2424
- }
2425
- ctx.restore();
2426
- }
2427
3294
  function renderTextOnCanvas(ctx, text) {
2428
3295
  if (!text.text) return;
2429
3296
  ctx.save();
@@ -2448,25 +3315,6 @@ function renderTextOnCanvas(ctx, text) {
2448
3315
  }
2449
3316
  ctx.restore();
2450
3317
  }
2451
- function wrapText(ctx, text, x, y, maxWidth, lineHeight) {
2452
- const words = text.split(" ");
2453
- let line = "";
2454
- let offsetY = 0;
2455
- for (const word of words) {
2456
- const testLine = line ? `${line} ${word}` : word;
2457
- const metrics = ctx.measureText(testLine);
2458
- if (metrics.width > maxWidth && line) {
2459
- ctx.fillText(line, x, y + offsetY);
2460
- line = word;
2461
- offsetY += lineHeight;
2462
- } else {
2463
- line = testLine;
2464
- }
2465
- }
2466
- if (line) {
2467
- ctx.fillText(line, x, y + offsetY);
2468
- }
2469
- }
2470
3318
  function renderGridForBounds(ctx, grid, bounds) {
2471
3319
  const visibleBounds = {
2472
3320
  minX: bounds.x,
@@ -2865,13 +3713,13 @@ var DomNodeManager = class {
2865
3713
  padding: "8px",
2866
3714
  borderRadius: "4px",
2867
3715
  boxShadow: "0 2px 8px rgba(0,0,0,0.15)",
2868
- fontSize: "14px",
3716
+ fontSize: `${element.fontSize ?? DEFAULT_NOTE_FONT_SIZE}px`,
2869
3717
  overflow: "hidden",
2870
3718
  cursor: "default",
2871
3719
  userSelect: "none",
2872
3720
  wordWrap: "break-word"
2873
3721
  });
2874
- node.textContent = element.text || "";
3722
+ node.innerHTML = element.text || "";
2875
3723
  node.addEventListener("dblclick", (e) => {
2876
3724
  e.stopPropagation();
2877
3725
  const id = node.dataset["elementId"];
@@ -2879,11 +3727,13 @@ var DomNodeManager = class {
2879
3727
  });
2880
3728
  }
2881
3729
  if (!this.isEditingElement(element.id)) {
2882
- if (node.textContent !== element.text) {
2883
- node.textContent = element.text || "";
3730
+ const text = element.text || "";
3731
+ if (node.innerHTML !== text) {
3732
+ node.innerHTML = text;
2884
3733
  }
2885
3734
  node.style.backgroundColor = element.backgroundColor;
2886
3735
  node.style.color = element.textColor;
3736
+ node.style.fontSize = `${element.fontSize ?? DEFAULT_NOTE_FONT_SIZE}px`;
2887
3737
  }
2888
3738
  }
2889
3739
  if (element.type === "html" && !node.dataset["initialized"]) {
@@ -3105,7 +3955,15 @@ var RenderLoop = class {
3105
3955
  ctx.save();
3106
3956
  ctx.scale(dpr, dpr);
3107
3957
  this.renderer.setCanvasSize(cssWidth, cssHeight);
3108
- this.background.render(ctx, this.camera);
3958
+ const hasGridElement = this.store.getElementsByType("grid").length > 0;
3959
+ if (hasGridElement) {
3960
+ ctx.save();
3961
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
3962
+ ctx.clearRect(0, 0, cssWidth, cssHeight);
3963
+ ctx.restore();
3964
+ } else {
3965
+ this.background.render(ctx, this.camera);
3966
+ }
3109
3967
  ctx.save();
3110
3968
  ctx.translate(this.camera.position.x, this.camera.position.y);
3111
3969
  ctx.scale(this.camera.zoom, this.camera.zoom);
@@ -3306,7 +4164,10 @@ var Viewport = class {
3306
4164
  this.renderLoop.markAllLayersDirty();
3307
4165
  this.requestRender();
3308
4166
  });
3309
- this.noteEditor = new NoteEditor();
4167
+ this.noteEditor = new NoteEditor({
4168
+ fontSizePresets: options.fontSizePresets,
4169
+ toolbar: options.toolbar
4170
+ });
3310
4171
  this.noteEditor.setOnStop((id) => this.onTextEditStop(id));
3311
4172
  this.history = new HistoryStack();
3312
4173
  this.historyRecorder = new HistoryRecorder(this.store, this.history);
@@ -3362,20 +4223,24 @@ var Viewport = class {
3362
4223
  });
3363
4224
  this.unsubCamera = this.camera.onChange(() => {
3364
4225
  this.applyCameraTransform();
4226
+ this.noteEditor.updateToolbarPosition();
3365
4227
  this.requestRender();
3366
4228
  });
3367
4229
  this.unsubStore = [
3368
4230
  this.store.on("add", (el) => {
4231
+ if (el.type === "grid") this.syncGridContext();
3369
4232
  this.renderLoop.markLayerDirty(el.layerId);
3370
4233
  this.requestRender();
3371
4234
  }),
3372
4235
  this.store.on("remove", (el) => {
4236
+ if (el.type === "grid") this.syncGridContext();
3373
4237
  this.unbindArrowsFrom(el);
3374
4238
  this.domNodeManager.removeDomNode(el.id);
3375
4239
  this.renderLoop.markLayerDirty(el.layerId);
3376
4240
  this.requestRender();
3377
4241
  }),
3378
4242
  this.store.on("update", ({ previous, current }) => {
4243
+ if (current.type === "grid") this.syncGridContext();
3379
4244
  this.renderLoop.markLayerDirty(current.layerId);
3380
4245
  if (previous.layerId !== current.layerId) {
3381
4246
  this.renderLoop.markLayerDirty(previous.layerId);
@@ -3385,6 +4250,7 @@ var Viewport = class {
3385
4250
  this.store.on("clear", () => {
3386
4251
  this.domNodeManager.clearDomNodes();
3387
4252
  this.renderLoop.markAllLayersDirty();
4253
+ this.syncGridContext();
3388
4254
  this.requestRender();
3389
4255
  })
3390
4256
  ];
@@ -3398,6 +4264,7 @@ var Viewport = class {
3398
4264
  this.observeResize();
3399
4265
  this.syncCanvasSize();
3400
4266
  this.renderLoop.start();
4267
+ this.syncGridContext();
3401
4268
  }
3402
4269
  camera;
3403
4270
  store;
@@ -3714,6 +4581,18 @@ var Viewport = class {
3714
4581
  this.renderLoop.setCanvasSize(rect.width * dpr, rect.height * dpr);
3715
4582
  this.requestRender();
3716
4583
  }
4584
+ syncGridContext() {
4585
+ const grid = this.store.getElementsByType("grid")[0];
4586
+ if (grid) {
4587
+ this.toolContext.gridSize = grid.cellSize;
4588
+ this.toolContext.gridType = grid.gridType;
4589
+ this.toolContext.hexOrientation = grid.hexOrientation;
4590
+ } else {
4591
+ this.toolContext.gridSize = this._gridSize;
4592
+ this.toolContext.gridType = void 0;
4593
+ this.toolContext.hexOrientation = void 0;
4594
+ }
4595
+ }
3717
4596
  observeResize() {
3718
4597
  if (typeof ResizeObserver === "undefined") return;
3719
4598
  this.resizeObserver = new ResizeObserver(() => this.syncCanvasSize());
@@ -4088,7 +4967,7 @@ var SelectTool = class {
4088
4967
  ctx.setCursor?.("default");
4089
4968
  }
4090
4969
  snap(point, ctx) {
4091
- return ctx.snapToGrid && ctx.gridSize ? snapPoint(point, ctx.gridSize) : point;
4970
+ return smartSnap(point, ctx);
4092
4971
  }
4093
4972
  onPointerDown(state, ctx) {
4094
4973
  this.ctx = ctx;
@@ -4105,6 +4984,12 @@ var SelectTool = class {
4105
4984
  ctx.requestRender();
4106
4985
  return;
4107
4986
  }
4987
+ const templateResizeHit = this.hitTestTemplateResizeHandle(world, ctx);
4988
+ if (templateResizeHit) {
4989
+ this.mode = { type: "resizing-template", elementId: templateResizeHit };
4990
+ ctx.requestRender();
4991
+ return;
4992
+ }
4108
4993
  const resizeHit = this.hitTestResizeHandle(world, ctx);
4109
4994
  if (resizeHit) {
4110
4995
  const el = ctx.store.getById(resizeHit.elementId);
@@ -4139,6 +5024,11 @@ var SelectTool = class {
4139
5024
  applyArrowHandleDrag(this.mode.handle, this.mode.elementId, world, ctx);
4140
5025
  return;
4141
5026
  }
5027
+ if (this.mode.type === "resizing-template") {
5028
+ ctx.setCursor?.("nwse-resize");
5029
+ this.handleTemplateResize(world, ctx);
5030
+ return;
5031
+ }
4142
5032
  if (this.mode.type === "resizing") {
4143
5033
  ctx.setCursor?.(HANDLE_CURSORS[this.mode.handle]);
4144
5034
  this.handleResize(world, ctx);
@@ -4162,6 +5052,16 @@ var SelectTool = class {
4162
5052
  from: { x: el.from.x + dx, y: el.from.y + dy },
4163
5053
  to: { x: el.to.x + dx, y: el.to.y + dy }
4164
5054
  });
5055
+ } else if (ctx.gridType && "size" in el) {
5056
+ const centerX = el.position.x + el.size.w / 2 + dx;
5057
+ const centerY = el.position.y + el.size.h / 2 + dy;
5058
+ const snappedCenter = this.snap({ x: centerX, y: centerY }, ctx);
5059
+ ctx.store.update(id, {
5060
+ position: {
5061
+ x: snappedCenter.x - el.size.w / 2,
5062
+ y: snappedCenter.y - el.size.h / 2
5063
+ }
5064
+ });
4165
5065
  } else {
4166
5066
  ctx.store.update(id, {
4167
5067
  position: { x: el.position.x + dx, y: el.position.y + dy }
@@ -4236,6 +5136,11 @@ var SelectTool = class {
4236
5136
  ctx.setCursor?.(getArrowHandleCursor(arrowHit.handle, false));
4237
5137
  return;
4238
5138
  }
5139
+ const templateResizeHit = this.hitTestTemplateResizeHandle(world, ctx);
5140
+ if (templateResizeHit) {
5141
+ ctx.setCursor?.("nwse-resize");
5142
+ return;
5143
+ }
4239
5144
  const resizeHit = this.hitTestResizeHandle(world, ctx);
4240
5145
  if (resizeHit) {
4241
5146
  ctx.setCursor?.(HANDLE_CURSORS[resizeHit.handle]);
@@ -4372,6 +5277,24 @@ var SelectTool = class {
4372
5277
  );
4373
5278
  }
4374
5279
  canvasCtx.setLineDash([4 / zoom, 4 / zoom]);
5280
+ } else if (el.type === "template") {
5281
+ canvasCtx.setLineDash([]);
5282
+ canvasCtx.fillStyle = "#ffffff";
5283
+ const hx = bounds.x + bounds.w;
5284
+ const hy = bounds.y + bounds.h;
5285
+ canvasCtx.fillRect(
5286
+ hx - handleWorldSize / 2,
5287
+ hy - handleWorldSize / 2,
5288
+ handleWorldSize,
5289
+ handleWorldSize
5290
+ );
5291
+ canvasCtx.strokeRect(
5292
+ hx - handleWorldSize / 2,
5293
+ hy - handleWorldSize / 2,
5294
+ handleWorldSize,
5295
+ handleWorldSize
5296
+ );
5297
+ canvasCtx.setLineDash([4 / zoom, 4 / zoom]);
4375
5298
  }
4376
5299
  }
4377
5300
  canvasCtx.restore();
@@ -4396,6 +5319,43 @@ var SelectTool = class {
4396
5319
  }
4397
5320
  canvasCtx.restore();
4398
5321
  }
5322
+ hitTestTemplateResizeHandle(world, ctx) {
5323
+ if (this._selectedIds.length === 0) return null;
5324
+ const zoom = ctx.camera.zoom;
5325
+ const handleHalf = (HANDLE_SIZE / 2 + HANDLE_HIT_PADDING2) / zoom;
5326
+ for (const id of this._selectedIds) {
5327
+ const el = ctx.store.getById(id);
5328
+ if (!el || el.type !== "template") continue;
5329
+ const bounds = getElementBounds(el);
5330
+ if (!bounds) continue;
5331
+ const hx = bounds.x + bounds.w;
5332
+ const hy = bounds.y + bounds.h;
5333
+ if (Math.abs(world.x - hx) <= handleHalf && Math.abs(world.y - hy) <= handleHalf) {
5334
+ return id;
5335
+ }
5336
+ }
5337
+ return null;
5338
+ }
5339
+ handleTemplateResize(world, ctx) {
5340
+ if (this.mode.type !== "resizing-template") return;
5341
+ const el = ctx.store.getById(this.mode.elementId);
5342
+ if (!el || el.type !== "template" || el.locked) return;
5343
+ const dx = world.x - el.position.x;
5344
+ const dy = world.y - el.position.y;
5345
+ let newRadius = Math.sqrt(dx * dx + dy * dy);
5346
+ if (ctx.snapToGrid && ctx.gridSize && ctx.gridSize > 0) {
5347
+ const snapUnit = ctx.gridType === "hex" ? Math.sqrt(3) * ctx.gridSize : ctx.gridSize;
5348
+ newRadius = Math.max(snapUnit, Math.round(newRadius / snapUnit) * snapUnit);
5349
+ }
5350
+ newRadius = Math.max(MIN_ELEMENT_SIZE, newRadius);
5351
+ const updates = { radius: newRadius };
5352
+ if (el.feetPerCell != null && ctx.gridSize && ctx.gridSize > 0) {
5353
+ const snapUnit = ctx.gridType === "hex" ? Math.sqrt(3) * ctx.gridSize : ctx.gridSize;
5354
+ updates.radiusFeet = newRadius / snapUnit * el.feetPerCell;
5355
+ }
5356
+ ctx.store.update(this.mode.elementId, updates);
5357
+ ctx.requestRender();
5358
+ }
4399
5359
  getMarqueeRect() {
4400
5360
  if (this.mode.type !== "marquee") return null;
4401
5361
  const { start } = this.mode;
@@ -4452,6 +5412,11 @@ var SelectTool = class {
4452
5412
  if (el.type === "arrow") {
4453
5413
  return isNearBezier(point, el.from, el.to, el.bend, 10);
4454
5414
  }
5415
+ if (el.type === "template") {
5416
+ const bounds = getElementBounds(el);
5417
+ if (!bounds) return false;
5418
+ return point.x >= bounds.x && point.x <= bounds.x + bounds.w && point.y >= bounds.y && point.y <= bounds.y + bounds.h;
5419
+ }
4455
5420
  return false;
4456
5421
  }
4457
5422
  };
@@ -4509,7 +5474,7 @@ var ArrowTool = class {
4509
5474
  this.fromBinding = { elementId: target.id };
4510
5475
  this.fromTarget = target;
4511
5476
  } else {
4512
- this.start = ctx.snapToGrid && ctx.gridSize ? snapPoint(world, ctx.gridSize) : world;
5477
+ this.start = smartSnap(world, ctx);
4513
5478
  this.fromBinding = void 0;
4514
5479
  this.fromTarget = null;
4515
5480
  }
@@ -4527,7 +5492,7 @@ var ArrowTool = class {
4527
5492
  this.end = getElementCenter(target);
4528
5493
  this.toTarget = target;
4529
5494
  } else {
4530
- this.end = ctx.snapToGrid && ctx.gridSize ? snapPoint(world, ctx.gridSize) : world;
5495
+ this.end = smartSnap(world, ctx);
4531
5496
  this.toTarget = null;
4532
5497
  }
4533
5498
  ctx.requestRender();
@@ -4608,17 +5573,20 @@ var NoteTool = class {
4608
5573
  backgroundColor;
4609
5574
  textColor;
4610
5575
  size;
5576
+ fontSize;
4611
5577
  optionListeners = /* @__PURE__ */ new Set();
4612
5578
  constructor(options = {}) {
4613
5579
  this.backgroundColor = options.backgroundColor ?? "#ffeb3b";
4614
5580
  this.textColor = options.textColor ?? "#000000";
4615
5581
  this.size = options.size ?? { w: 200, h: 100 };
5582
+ this.fontSize = options.fontSize ?? DEFAULT_NOTE_FONT_SIZE;
4616
5583
  }
4617
5584
  getOptions() {
4618
5585
  return {
4619
5586
  backgroundColor: this.backgroundColor,
4620
5587
  textColor: this.textColor,
4621
- size: { ...this.size }
5588
+ size: { ...this.size },
5589
+ fontSize: this.fontSize
4622
5590
  };
4623
5591
  }
4624
5592
  onOptionsChange(listener) {
@@ -4629,6 +5597,7 @@ var NoteTool = class {
4629
5597
  if (options.backgroundColor !== void 0) this.backgroundColor = options.backgroundColor;
4630
5598
  if (options.textColor !== void 0) this.textColor = options.textColor;
4631
5599
  if (options.size !== void 0) this.size = options.size;
5600
+ if (options.fontSize !== void 0) this.fontSize = options.fontSize;
4632
5601
  this.notifyOptionsChange();
4633
5602
  }
4634
5603
  notifyOptionsChange() {
@@ -4640,14 +5609,13 @@ var NoteTool = class {
4640
5609
  }
4641
5610
  onPointerUp(state, ctx) {
4642
5611
  let world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
4643
- if (ctx.snapToGrid && ctx.gridSize) {
4644
- world = snapPoint(world, ctx.gridSize);
4645
- }
5612
+ world = smartSnap(world, ctx);
4646
5613
  const note = createNote({
4647
5614
  position: world,
4648
5615
  size: { ...this.size },
4649
5616
  backgroundColor: this.backgroundColor,
4650
5617
  textColor: this.textColor,
5618
+ fontSize: this.fontSize,
4651
5619
  layerId: ctx.activeLayerId ?? ""
4652
5620
  });
4653
5621
  ctx.store.add(note);
@@ -4697,9 +5665,7 @@ var TextTool = class {
4697
5665
  }
4698
5666
  onPointerUp(state, ctx) {
4699
5667
  let world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
4700
- if (ctx.snapToGrid && ctx.gridSize) {
4701
- world = snapPoint(world, ctx.gridSize);
4702
- }
5668
+ world = smartSnap(world, ctx);
4703
5669
  const textEl = createText({
4704
5670
  position: world,
4705
5671
  fontSize: this.fontSize,
@@ -4732,8 +5698,12 @@ var ImageTool = class {
4732
5698
  onPointerUp(state, ctx) {
4733
5699
  if (!this.src) return;
4734
5700
  const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
5701
+ const snapped = smartSnap(world, ctx);
4735
5702
  const image = createImage({
4736
- position: world,
5703
+ position: {
5704
+ x: snapped.x - this.size.w / 2,
5705
+ y: snapped.y - this.size.h / 2
5706
+ },
4737
5707
  size: { ...this.size },
4738
5708
  src: this.src
4739
5709
  });
@@ -4870,7 +5840,7 @@ var ShapeTool = class {
4870
5840
  for (const listener of this.optionListeners) listener();
4871
5841
  }
4872
5842
  snap(point, ctx) {
4873
- return ctx.snapToGrid && ctx.gridSize ? snapPoint(point, ctx.gridSize) : point;
5843
+ return smartSnap(point, ctx);
4874
5844
  }
4875
5845
  onKeyDown = (e) => {
4876
5846
  if (e.key === "Shift") this.shiftHeld = true;
@@ -4880,6 +5850,398 @@ var ShapeTool = class {
4880
5850
  };
4881
5851
  };
4882
5852
 
5853
+ // src/tools/measure-tool.ts
5854
+ var MeasureTool = class {
5855
+ name = "measure";
5856
+ start = null;
5857
+ end = null;
5858
+ gridSize = 1;
5859
+ gridType;
5860
+ hexOrientation;
5861
+ feetPerCell;
5862
+ optionListeners = /* @__PURE__ */ new Set();
5863
+ constructor(options = {}) {
5864
+ this.feetPerCell = options.feetPerCell ?? 5;
5865
+ }
5866
+ getOptions() {
5867
+ return { feetPerCell: this.feetPerCell };
5868
+ }
5869
+ setOptions(options) {
5870
+ if (options.feetPerCell !== void 0) this.feetPerCell = options.feetPerCell;
5871
+ this.notifyOptionsChange();
5872
+ }
5873
+ onOptionsChange(listener) {
5874
+ this.optionListeners.add(listener);
5875
+ return () => this.optionListeners.delete(listener);
5876
+ }
5877
+ onPointerDown(state, ctx) {
5878
+ this.gridSize = ctx.gridSize ?? 1;
5879
+ this.gridType = ctx.gridType;
5880
+ this.hexOrientation = ctx.hexOrientation;
5881
+ const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
5882
+ this.start = this.snapToGrid(world, ctx);
5883
+ this.end = { ...this.start };
5884
+ }
5885
+ onPointerMove(state, ctx) {
5886
+ if (!this.start) return;
5887
+ const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
5888
+ this.end = this.snapToGrid(world, ctx);
5889
+ ctx.requestRender();
5890
+ }
5891
+ onPointerUp(_state, ctx) {
5892
+ if (!this.start) return;
5893
+ this.start = null;
5894
+ this.end = null;
5895
+ ctx.requestRender();
5896
+ }
5897
+ onDeactivate(_ctx) {
5898
+ this.start = null;
5899
+ this.end = null;
5900
+ }
5901
+ getMeasurement() {
5902
+ if (!this.start || !this.end) return null;
5903
+ const dx = this.end.x - this.start.x;
5904
+ const dy = this.end.y - this.start.y;
5905
+ const worldDistance = Math.sqrt(dx * dx + dy * dy);
5906
+ let cells;
5907
+ if (this.gridType === "hex" && this.hexOrientation) {
5908
+ cells = getHexDistance(this.start, this.end, this.gridSize, this.hexOrientation);
5909
+ } else {
5910
+ const snapUnit = this.gridSize;
5911
+ cells = worldDistance / snapUnit;
5912
+ }
5913
+ const feet = cells * this.feetPerCell;
5914
+ return {
5915
+ start: { ...this.start },
5916
+ end: { ...this.end },
5917
+ worldDistance,
5918
+ cells,
5919
+ feet
5920
+ };
5921
+ }
5922
+ renderOverlay(ctx) {
5923
+ const m = this.getMeasurement();
5924
+ if (!m) return;
5925
+ ctx.save();
5926
+ ctx.strokeStyle = "#FF5722";
5927
+ ctx.setLineDash([8, 4]);
5928
+ ctx.lineWidth = 2;
5929
+ ctx.beginPath();
5930
+ ctx.moveTo(m.start.x, m.start.y);
5931
+ ctx.lineTo(m.end.x, m.end.y);
5932
+ ctx.stroke();
5933
+ ctx.setLineDash([]);
5934
+ ctx.fillStyle = "#FF5722";
5935
+ const dotRadius = 4;
5936
+ ctx.beginPath();
5937
+ ctx.arc(m.start.x, m.start.y, dotRadius, 0, Math.PI * 2);
5938
+ ctx.fill();
5939
+ ctx.beginPath();
5940
+ ctx.arc(m.end.x, m.end.y, dotRadius, 0, Math.PI * 2);
5941
+ ctx.fill();
5942
+ const label = `${Math.round(m.feet)} ft`;
5943
+ const midX = (m.start.x + m.end.x) / 2;
5944
+ const midY = (m.start.y + m.end.y) / 2;
5945
+ ctx.font = "14px sans-serif";
5946
+ const metrics = ctx.measureText(label);
5947
+ const padX = 6;
5948
+ const padY = 4;
5949
+ const textH = 14;
5950
+ ctx.fillStyle = "rgba(0, 0, 0, 0.75)";
5951
+ ctx.beginPath();
5952
+ ctx.roundRect(
5953
+ midX - metrics.width / 2 - padX,
5954
+ midY - textH / 2 - padY,
5955
+ metrics.width + padX * 2,
5956
+ textH + padY * 2,
5957
+ 4
5958
+ );
5959
+ ctx.fill();
5960
+ ctx.fillStyle = "#FFFFFF";
5961
+ ctx.textAlign = "center";
5962
+ ctx.textBaseline = "middle";
5963
+ ctx.fillText(label, midX, midY);
5964
+ ctx.restore();
5965
+ }
5966
+ snapToGrid(point, ctx) {
5967
+ if (!ctx.gridSize) return point;
5968
+ if (ctx.gridType === "hex" && ctx.hexOrientation) {
5969
+ return snapToHexCenter(point, ctx.gridSize, ctx.hexOrientation);
5970
+ }
5971
+ if (ctx.gridType === "square") {
5972
+ return snapPoint(point, ctx.gridSize);
5973
+ }
5974
+ if (ctx.snapToGrid) {
5975
+ return snapPoint(point, ctx.gridSize);
5976
+ }
5977
+ return point;
5978
+ }
5979
+ notifyOptionsChange() {
5980
+ for (const listener of this.optionListeners) listener();
5981
+ }
5982
+ };
5983
+
5984
+ // src/tools/template-tool.ts
5985
+ var TemplateTool = class {
5986
+ name = "template";
5987
+ drawing = false;
5988
+ origin = { x: 0, y: 0 };
5989
+ current = { x: 0, y: 0 };
5990
+ gridSize = 1;
5991
+ gridType;
5992
+ hexOrientation;
5993
+ snapEnabled = false;
5994
+ templateShape;
5995
+ fillColor;
5996
+ strokeColor;
5997
+ strokeWidth;
5998
+ opacity;
5999
+ feetPerCell;
6000
+ optionListeners = /* @__PURE__ */ new Set();
6001
+ constructor(options = {}) {
6002
+ this.templateShape = options.templateShape ?? "circle";
6003
+ this.fillColor = options.fillColor ?? "rgba(255, 87, 34, 0.2)";
6004
+ this.strokeColor = options.strokeColor ?? "#FF5722";
6005
+ this.strokeWidth = options.strokeWidth ?? 2;
6006
+ this.opacity = options.opacity ?? 0.6;
6007
+ this.feetPerCell = options.feetPerCell ?? 5;
6008
+ }
6009
+ getOptions() {
6010
+ return {
6011
+ templateShape: this.templateShape,
6012
+ fillColor: this.fillColor,
6013
+ strokeColor: this.strokeColor,
6014
+ strokeWidth: this.strokeWidth,
6015
+ opacity: this.opacity,
6016
+ feetPerCell: this.feetPerCell
6017
+ };
6018
+ }
6019
+ setOptions(options) {
6020
+ if (options.templateShape !== void 0) this.templateShape = options.templateShape;
6021
+ if (options.fillColor !== void 0) this.fillColor = options.fillColor;
6022
+ if (options.strokeColor !== void 0) this.strokeColor = options.strokeColor;
6023
+ if (options.strokeWidth !== void 0) this.strokeWidth = options.strokeWidth;
6024
+ if (options.opacity !== void 0) this.opacity = options.opacity;
6025
+ if (options.feetPerCell !== void 0) this.feetPerCell = options.feetPerCell;
6026
+ this.notifyOptionsChange();
6027
+ }
6028
+ onOptionsChange(listener) {
6029
+ this.optionListeners.add(listener);
6030
+ return () => this.optionListeners.delete(listener);
6031
+ }
6032
+ onPointerDown(state, ctx) {
6033
+ this.drawing = true;
6034
+ this.gridSize = ctx.gridSize ?? 1;
6035
+ this.gridType = ctx.gridType;
6036
+ this.hexOrientation = ctx.hexOrientation;
6037
+ this.snapEnabled = !!ctx.gridType || (ctx.snapToGrid ?? false);
6038
+ const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
6039
+ this.origin = this.snapToGrid(world, ctx);
6040
+ this.current = { ...this.origin };
6041
+ }
6042
+ onPointerMove(state, ctx) {
6043
+ if (!this.drawing) return;
6044
+ this.current = ctx.camera.screenToWorld({ x: state.x, y: state.y });
6045
+ ctx.requestRender();
6046
+ }
6047
+ onPointerUp(_state, ctx) {
6048
+ if (!this.drawing) return;
6049
+ this.drawing = false;
6050
+ const radius = this.computeRadius();
6051
+ if (radius <= 0) return;
6052
+ const angle = this.computeAngle();
6053
+ const gridSize = ctx.gridSize;
6054
+ const snapUnit = gridSize && gridSize > 0 ? ctx.gridType === "hex" ? Math.sqrt(3) * gridSize : gridSize : 0;
6055
+ const cells = snapUnit > 0 ? radius / snapUnit : 0;
6056
+ const radiusFeet = cells * this.feetPerCell;
6057
+ const element = createTemplate({
6058
+ position: { ...this.origin },
6059
+ templateShape: this.templateShape,
6060
+ radius,
6061
+ angle,
6062
+ fillColor: this.fillColor,
6063
+ strokeColor: this.strokeColor,
6064
+ strokeWidth: this.strokeWidth,
6065
+ opacity: this.opacity,
6066
+ feetPerCell: this.feetPerCell,
6067
+ radiusFeet: radiusFeet > 0 ? radiusFeet : void 0,
6068
+ layerId: ctx.activeLayerId ?? ""
6069
+ });
6070
+ ctx.store.add(element);
6071
+ ctx.requestRender();
6072
+ ctx.switchTool?.("select");
6073
+ }
6074
+ onDeactivate(_ctx) {
6075
+ this.drawing = false;
6076
+ this.origin = { x: 0, y: 0 };
6077
+ this.current = { x: 0, y: 0 };
6078
+ }
6079
+ renderOverlay(ctx) {
6080
+ if (!this.drawing) return;
6081
+ const radius = this.computeRadius();
6082
+ if (radius <= 0) return;
6083
+ if (this.gridType === "hex" && this.hexOrientation) {
6084
+ this.renderHexOverlay(ctx, radius);
6085
+ return;
6086
+ }
6087
+ this.renderGeometricOverlay(ctx, radius);
6088
+ }
6089
+ renderGeometricOverlay(ctx, radius) {
6090
+ const cx = this.origin.x;
6091
+ const cy = this.origin.y;
6092
+ const angle = this.computeAngle();
6093
+ ctx.save();
6094
+ ctx.globalAlpha = 0.4;
6095
+ ctx.fillStyle = this.fillColor;
6096
+ ctx.strokeStyle = this.strokeColor;
6097
+ ctx.lineWidth = this.strokeWidth;
6098
+ switch (this.templateShape) {
6099
+ case "circle":
6100
+ ctx.beginPath();
6101
+ ctx.arc(cx, cy, radius, 0, Math.PI * 2);
6102
+ ctx.fill();
6103
+ ctx.stroke();
6104
+ break;
6105
+ case "square":
6106
+ ctx.fillRect(cx - radius / 2, cy - radius / 2, radius, radius);
6107
+ ctx.strokeRect(cx - radius / 2, cy - radius / 2, radius, radius);
6108
+ break;
6109
+ case "cone": {
6110
+ const halfAngle = Math.atan(0.5);
6111
+ ctx.beginPath();
6112
+ ctx.moveTo(cx, cy);
6113
+ ctx.arc(cx, cy, radius, angle - halfAngle, angle + halfAngle);
6114
+ ctx.closePath();
6115
+ ctx.fill();
6116
+ ctx.stroke();
6117
+ break;
6118
+ }
6119
+ case "line": {
6120
+ const halfW = radius / 12;
6121
+ const cos = Math.cos(angle);
6122
+ const sin = Math.sin(angle);
6123
+ const perpX = -sin * halfW;
6124
+ const perpY = cos * halfW;
6125
+ ctx.beginPath();
6126
+ ctx.moveTo(cx + perpX, cy + perpY);
6127
+ ctx.lineTo(cx + radius * cos + perpX, cy + radius * sin + perpY);
6128
+ ctx.lineTo(cx + radius * cos - perpX, cy + radius * sin - perpY);
6129
+ ctx.lineTo(cx - perpX, cy - perpY);
6130
+ ctx.closePath();
6131
+ ctx.fill();
6132
+ ctx.stroke();
6133
+ break;
6134
+ }
6135
+ }
6136
+ ctx.restore();
6137
+ }
6138
+ renderHexOverlay(ctx, radius) {
6139
+ const orientation = this.hexOrientation;
6140
+ if (!orientation) return;
6141
+ const cellSize = this.gridSize;
6142
+ const snapUnit = Math.sqrt(3) * cellSize;
6143
+ const radiusCells = radius / snapUnit;
6144
+ const angle = this.computeAngle();
6145
+ const center = this.origin;
6146
+ let hexCells;
6147
+ switch (this.templateShape) {
6148
+ case "circle":
6149
+ hexCells = getHexCellsInRadius(center, radiusCells, cellSize, orientation);
6150
+ break;
6151
+ case "cone":
6152
+ hexCells = getHexCellsInCone(center, angle, radiusCells, cellSize, orientation);
6153
+ break;
6154
+ case "line":
6155
+ hexCells = getHexCellsInLine(center, angle, radiusCells, cellSize, orientation);
6156
+ break;
6157
+ case "square":
6158
+ hexCells = getHexCellsInSquare(center, radiusCells, cellSize, orientation);
6159
+ break;
6160
+ }
6161
+ ctx.save();
6162
+ ctx.globalAlpha = 0.4;
6163
+ ctx.beginPath();
6164
+ for (const cell of hexCells) {
6165
+ drawHexPath(ctx, cell.x, cell.y, cellSize, orientation);
6166
+ }
6167
+ ctx.fillStyle = this.fillColor;
6168
+ ctx.fill();
6169
+ ctx.beginPath();
6170
+ for (const cell of hexCells) {
6171
+ drawHexPath(ctx, cell.x, cell.y, cellSize, orientation);
6172
+ }
6173
+ ctx.strokeStyle = this.strokeColor;
6174
+ ctx.lineWidth = this.strokeWidth;
6175
+ ctx.stroke();
6176
+ if (this.templateShape === "cone" || this.templateShape === "line" || this.templateShape === "circle" || this.templateShape === "square") {
6177
+ ctx.globalAlpha = 0.5;
6178
+ ctx.beginPath();
6179
+ drawHexPath(ctx, center.x, center.y, cellSize, orientation);
6180
+ ctx.fillStyle = this.strokeColor;
6181
+ ctx.fill();
6182
+ ctx.strokeStyle = this.strokeColor;
6183
+ ctx.lineWidth = this.strokeWidth;
6184
+ ctx.stroke();
6185
+ }
6186
+ if (this.templateShape === "circle") {
6187
+ const feet = radiusCells * this.feetPerCell;
6188
+ if (feet > 0) {
6189
+ ctx.globalAlpha = 1;
6190
+ const label = `${Math.round(feet)} ft`;
6191
+ const fontSize = Math.max(10, Math.min(14, radius * 0.15));
6192
+ ctx.font = `bold ${fontSize}px system-ui, sans-serif`;
6193
+ ctx.textAlign = "center";
6194
+ ctx.textBaseline = "bottom";
6195
+ const textX = center.x;
6196
+ const textY = center.y - 4;
6197
+ const metrics = ctx.measureText(label);
6198
+ const padX = 4;
6199
+ const padY = 2;
6200
+ const textW = metrics.width + padX * 2;
6201
+ const textH = fontSize + padY * 2;
6202
+ ctx.fillStyle = "rgba(255, 255, 255, 0.85)";
6203
+ ctx.beginPath();
6204
+ ctx.roundRect(textX - textW / 2, textY - textH, textW, textH, 3);
6205
+ ctx.fill();
6206
+ ctx.fillStyle = this.strokeColor;
6207
+ ctx.fillText(label, textX, textY - padY);
6208
+ }
6209
+ }
6210
+ ctx.restore();
6211
+ }
6212
+ computeRadius() {
6213
+ const dx = this.current.x - this.origin.x;
6214
+ const dy = this.current.y - this.origin.y;
6215
+ const raw = Math.sqrt(dx * dx + dy * dy);
6216
+ if (this.snapEnabled && this.gridSize > 0) {
6217
+ const snapUnit = this.gridType === "hex" ? Math.sqrt(3) * this.gridSize : this.gridSize;
6218
+ return Math.max(snapUnit, Math.round(raw / snapUnit) * snapUnit);
6219
+ }
6220
+ return raw;
6221
+ }
6222
+ computeAngle() {
6223
+ const dx = this.current.x - this.origin.x;
6224
+ const dy = this.current.y - this.origin.y;
6225
+ return Math.atan2(dy, dx);
6226
+ }
6227
+ snapToGrid(point, ctx) {
6228
+ if (!ctx.gridSize) return point;
6229
+ if (ctx.gridType === "hex" && ctx.hexOrientation) {
6230
+ return snapToHexCenter(point, ctx.gridSize, ctx.hexOrientation);
6231
+ }
6232
+ if (ctx.gridType === "square") {
6233
+ return snapPoint(point, ctx.gridSize);
6234
+ }
6235
+ if (ctx.snapToGrid) {
6236
+ return snapPoint(point, ctx.gridSize);
6237
+ }
6238
+ return point;
6239
+ }
6240
+ notifyOptionsChange() {
6241
+ for (const listener of this.optionListeners) listener();
6242
+ }
6243
+ };
6244
+
4883
6245
  // src/history/layer-commands.ts
4884
6246
  var CreateLayerCommand = class {
4885
6247
  constructor(manager, layer) {
@@ -4921,7 +6283,7 @@ var UpdateLayerCommand = class {
4921
6283
  };
4922
6284
 
4923
6285
  // src/index.ts
4924
- var VERSION = "0.8.11";
6286
+ var VERSION = "0.10.0";
4925
6287
  // Annotate the CommonJS export names for ESM import in node:
4926
6288
  0 && (module.exports = {
4927
6289
  AddElementCommand,
@@ -4931,6 +6293,8 @@ var VERSION = "0.8.11";
4931
6293
  BatchCommand,
4932
6294
  Camera,
4933
6295
  CreateLayerCommand,
6296
+ DEFAULT_FONT_SIZE_PRESETS,
6297
+ DEFAULT_NOTE_FONT_SIZE,
4934
6298
  ElementRenderer,
4935
6299
  ElementStore,
4936
6300
  EraserTool,
@@ -4941,14 +6305,17 @@ var VERSION = "0.8.11";
4941
6305
  ImageTool,
4942
6306
  InputHandler,
4943
6307
  LayerManager,
6308
+ MeasureTool,
4944
6309
  NoteEditor,
4945
6310
  NoteTool,
6311
+ NoteToolbar,
4946
6312
  PencilTool,
4947
6313
  Quadtree,
4948
6314
  RemoveElementCommand,
4949
6315
  RemoveLayerCommand,
4950
6316
  SelectTool,
4951
6317
  ShapeTool,
6318
+ TemplateTool,
4952
6319
  TextTool,
4953
6320
  ToolManager,
4954
6321
  UpdateElementCommand,
@@ -4965,11 +6332,14 @@ var VERSION = "0.8.11";
4965
6332
  createNote,
4966
6333
  createShape,
4967
6334
  createStroke,
6335
+ createTemplate,
4968
6336
  createText,
6337
+ drawHexPath,
4969
6338
  exportImage,
4970
6339
  exportState,
4971
6340
  findBindTarget,
4972
6341
  findBoundArrows,
6342
+ getActiveFormats,
4973
6343
  getArrowBounds,
4974
6344
  getArrowControlPoint,
4975
6345
  getArrowMidpoint,
@@ -4978,10 +6348,23 @@ var VERSION = "0.8.11";
4978
6348
  getEdgeIntersection,
4979
6349
  getElementBounds,
4980
6350
  getElementCenter,
6351
+ getHexCellsInCone,
6352
+ getHexCellsInLine,
6353
+ getHexCellsInRadius,
6354
+ getHexCellsInSquare,
6355
+ getHexDistance,
4981
6356
  isBindable,
4982
6357
  isNearBezier,
4983
6358
  parseState,
6359
+ sanitizeNoteHtml,
6360
+ setFontSize,
6361
+ smartSnap,
4984
6362
  snapPoint,
6363
+ snapToHexCenter,
6364
+ toggleBold,
6365
+ toggleItalic,
6366
+ toggleStrikethrough,
6367
+ toggleUnderline,
4985
6368
  unbindArrow,
4986
6369
  updateBoundArrow
4987
6370
  });