@fieldnotes/core 0.18.0 → 0.20.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
@@ -92,6 +92,7 @@ __export(index_exports, {
92
92
  getHexDistance: () => getHexDistance,
93
93
  isBindable: () => isBindable,
94
94
  isNearBezier: () => isNearBezier,
95
+ isNoteContentEmpty: () => isNoteContentEmpty,
95
96
  parseState: () => parseState,
96
97
  sanitizeNoteHtml: () => sanitizeNoteHtml,
97
98
  setFontSize: () => setFontSize,
@@ -382,6 +383,12 @@ function sanitizeNode(node) {
382
383
  sanitizeNode(el);
383
384
  }
384
385
  }
386
+ function isNoteContentEmpty(html) {
387
+ if (!html) return true;
388
+ const doc = new DOMParser().parseFromString(html, "text/html");
389
+ const text = doc.body.textContent ?? "";
390
+ return text.replace(/\u00a0/g, " ").trim().length === 0;
391
+ }
385
392
  function sanitizeAttributes(el, tag) {
386
393
  const attrs = Array.from(el.attributes);
387
394
  for (const attr of attrs) {
@@ -1150,14 +1157,158 @@ var KeyboardActions = class {
1150
1157
  }
1151
1158
  };
1152
1159
 
1160
+ // src/canvas/shortcut-map.ts
1161
+ var DEFAULT_BINDINGS = [
1162
+ ["delete", ["delete", "backspace"]],
1163
+ ["deselect", ["escape"]],
1164
+ ["undo", ["mod+z"]],
1165
+ ["redo", ["mod+y", "mod+shift+z"]],
1166
+ ["select-all", ["mod+a"]],
1167
+ ["copy", ["mod+c"]],
1168
+ ["paste", ["mod+v"]],
1169
+ ["duplicate", ["mod+d"]],
1170
+ ["z-forward", ["]"]],
1171
+ ["z-backward", ["["]],
1172
+ ["z-front", ["mod+]"]],
1173
+ ["z-back", ["mod+["]],
1174
+ ["zoom-fit", ["shift+1"]],
1175
+ ["nudge-left", ["arrowleft"]],
1176
+ ["nudge-right", ["arrowright"]],
1177
+ ["nudge-up", ["arrowup"]],
1178
+ ["nudge-down", ["arrowdown"]],
1179
+ ["tool:select", ["v"]],
1180
+ ["tool:hand", ["h"]],
1181
+ ["tool:pencil", ["p"]],
1182
+ ["tool:eraser", ["e"]],
1183
+ ["tool:arrow", ["a"]],
1184
+ ["tool:note", ["n"]],
1185
+ ["tool:text", ["t"]],
1186
+ ["tool:shape", ["s"]],
1187
+ ["tool:measure", ["m"]],
1188
+ ["tool:template", ["g"]]
1189
+ ];
1190
+ var ALLOW_SHIFT = /* @__PURE__ */ new Set(["nudge-left", "nudge-right", "nudge-up", "nudge-down"]);
1191
+ var MODIFIERS = /* @__PURE__ */ new Set(["mod", "ctrl", "meta", "shift", "alt"]);
1192
+ function parseBinding(binding) {
1193
+ const parts = binding.toLowerCase().split("+");
1194
+ const key = parts.pop();
1195
+ if (key === void 0 || key.length === 0 || MODIFIERS.has(key)) {
1196
+ throw new Error(`Invalid shortcut binding "${binding}": missing key`);
1197
+ }
1198
+ const normalizedKey = key === "space" ? " " : key;
1199
+ const parsed = {
1200
+ mod: false,
1201
+ ctrl: false,
1202
+ meta: false,
1203
+ shift: false,
1204
+ alt: false,
1205
+ key: normalizedKey,
1206
+ digit: /^[0-9]$/.test(normalizedKey)
1207
+ };
1208
+ for (const part of parts) {
1209
+ switch (part) {
1210
+ case "mod":
1211
+ parsed.mod = true;
1212
+ break;
1213
+ case "ctrl":
1214
+ parsed.ctrl = true;
1215
+ break;
1216
+ case "meta":
1217
+ parsed.meta = true;
1218
+ break;
1219
+ case "shift":
1220
+ parsed.shift = true;
1221
+ break;
1222
+ case "alt":
1223
+ parsed.alt = true;
1224
+ break;
1225
+ default:
1226
+ throw new Error(`Invalid shortcut binding "${binding}": unknown modifier "${part}"`);
1227
+ }
1228
+ }
1229
+ return parsed;
1230
+ }
1231
+ function bindingMatches(p, e, allowShift) {
1232
+ if (p.mod) {
1233
+ if (!e.ctrlKey && !e.metaKey) return false;
1234
+ } else if (e.ctrlKey !== p.ctrl || e.metaKey !== p.meta) {
1235
+ return false;
1236
+ }
1237
+ if (!allowShift && e.shiftKey !== p.shift) return false;
1238
+ if (e.altKey !== p.alt) return false;
1239
+ return p.digit ? e.code === `Digit${p.key}` : e.key.toLowerCase() === p.key;
1240
+ }
1241
+ function toArray(bindings) {
1242
+ if (bindings === null) return [];
1243
+ return Array.isArray(bindings) ? [...bindings] : [bindings];
1244
+ }
1245
+ var ShortcutMap = class {
1246
+ raw = /* @__PURE__ */ new Map();
1247
+ parsed = /* @__PURE__ */ new Map();
1248
+ constructor(overrides) {
1249
+ this.applyDefaults();
1250
+ if (overrides) {
1251
+ for (const [action, bindings] of Object.entries(overrides)) {
1252
+ this.rebind(action, bindings);
1253
+ }
1254
+ }
1255
+ }
1256
+ /** First matching action in registration order wins when bindings conflict. */
1257
+ match(e) {
1258
+ for (const [action, parsedList] of this.parsed) {
1259
+ const allowShift = ALLOW_SHIFT.has(action);
1260
+ for (const p of parsedList) {
1261
+ if (bindingMatches(p, e, allowShift)) return action;
1262
+ }
1263
+ }
1264
+ return null;
1265
+ }
1266
+ rebind(action, bindings) {
1267
+ const list = toArray(bindings);
1268
+ const parsedList = list.map(parseBinding);
1269
+ this.raw.set(action, list);
1270
+ this.parsed.set(action, parsedList);
1271
+ }
1272
+ disable(action) {
1273
+ this.rebind(action, null);
1274
+ }
1275
+ reset(action) {
1276
+ if (action === void 0) {
1277
+ this.raw.clear();
1278
+ this.parsed.clear();
1279
+ this.applyDefaults();
1280
+ return;
1281
+ }
1282
+ const def = DEFAULT_BINDINGS.find(([name]) => name === action);
1283
+ if (def) {
1284
+ this.rebind(action, [...def[1]]);
1285
+ } else if (this.raw.has(action)) {
1286
+ this.raw.delete(action);
1287
+ this.parsed.delete(action);
1288
+ }
1289
+ }
1290
+ getBindings() {
1291
+ const out = {};
1292
+ for (const [action, list] of this.raw) {
1293
+ out[action] = [...list];
1294
+ }
1295
+ return out;
1296
+ }
1297
+ applyDefaults() {
1298
+ for (const [action, bindings] of DEFAULT_BINDINGS) {
1299
+ this.rebind(action, [...bindings]);
1300
+ }
1301
+ }
1302
+ };
1303
+
1153
1304
  // src/canvas/input-handler.ts
1154
1305
  var ZOOM_SENSITIVITY = 1e-3;
1155
1306
  var MIDDLE_BUTTON = 1;
1156
- var NUDGE_KEYS = {
1157
- ArrowLeft: [-1, 0],
1158
- ArrowRight: [1, 0],
1159
- ArrowUp: [0, -1],
1160
- ArrowDown: [0, 1]
1307
+ var NUDGE_DELTAS = {
1308
+ "nudge-left": [-1, 0],
1309
+ "nudge-right": [1, 0],
1310
+ "nudge-up": [0, -1],
1311
+ "nudge-down": [0, 1]
1161
1312
  };
1162
1313
  var InputHandler = class {
1163
1314
  constructor(element, camera, options = {}) {
@@ -1175,7 +1326,13 @@ var InputHandler = class {
1175
1326
  isToolActive: () => this.isToolActive,
1176
1327
  fitToContent: options.fitToContent
1177
1328
  });
1329
+ this.shortcutMap = new ShortcutMap(options.shortcuts?.bindings);
1330
+ this.scope = options.shortcuts?.scope ?? "focus";
1178
1331
  this.element.style.touchAction = "none";
1332
+ if (this.scope === "focus") {
1333
+ this.element.tabIndex = 0;
1334
+ this.element.style.outline = "none";
1335
+ }
1179
1336
  this.bind();
1180
1337
  }
1181
1338
  isPanning = false;
@@ -1194,6 +1351,8 @@ var InputHandler = class {
1194
1351
  deferredDown = null;
1195
1352
  abortController = new AbortController();
1196
1353
  actions;
1354
+ shortcutMap;
1355
+ scope;
1197
1356
  setToolManager(toolManager, toolContext) {
1198
1357
  this.toolManager = toolManager;
1199
1358
  this.toolContext = toolContext;
@@ -1201,6 +1360,9 @@ var InputHandler = class {
1201
1360
  flushPendingHistory() {
1202
1361
  this.actions.flushPendingNudge();
1203
1362
  }
1363
+ get shortcuts() {
1364
+ return this.shortcutMap;
1365
+ }
1204
1366
  destroy() {
1205
1367
  this.actions.dispose();
1206
1368
  this.abortController.abort();
@@ -1230,6 +1392,7 @@ var InputHandler = class {
1230
1392
  });
1231
1393
  };
1232
1394
  onPointerDown = (e) => {
1395
+ this.focusSelf();
1233
1396
  this.activePointers.set(e.pointerId, { x: e.clientX, y: e.clientY });
1234
1397
  this.element.setPointerCapture?.(e.pointerId);
1235
1398
  if (this.activePointers.size === 2) {
@@ -1312,57 +1475,13 @@ var InputHandler = class {
1312
1475
  if (target?.isContentEditable) return;
1313
1476
  const tag = target?.tagName;
1314
1477
  if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return;
1478
+ if (!this.isInScope()) return;
1315
1479
  if (e.key === " ") {
1316
1480
  this.spaceHeld = true;
1317
1481
  }
1318
- if (e.key === "Delete" || e.key === "Backspace") {
1319
- this.actions.deleteSelected();
1320
- }
1321
- if (e.key === "Escape") {
1322
- this.actions.deselect();
1323
- }
1324
- if ((e.ctrlKey || e.metaKey) && e.key === "z" && !e.shiftKey) {
1325
- e.preventDefault();
1326
- this.actions.undo();
1327
- }
1328
- if ((e.ctrlKey || e.metaKey) && (e.key === "y" || e.key === "z" && e.shiftKey)) {
1329
- e.preventDefault();
1330
- this.actions.redo();
1331
- }
1332
- if ((e.ctrlKey || e.metaKey) && e.key === "a") {
1333
- e.preventDefault();
1334
- this.actions.selectAll();
1335
- }
1336
- if ((e.ctrlKey || e.metaKey) && e.key === "c") {
1337
- e.preventDefault();
1338
- this.actions.copy();
1339
- }
1340
- if ((e.ctrlKey || e.metaKey) && e.key === "v") {
1341
- e.preventDefault();
1342
- this.actions.paste();
1343
- }
1344
- if ((e.ctrlKey || e.metaKey) && e.key === "d") {
1345
- e.preventDefault();
1346
- this.actions.duplicate();
1347
- }
1348
- if (e.key === "]") {
1349
- e.preventDefault();
1350
- this.actions.zOrder(e.ctrlKey || e.metaKey ? "front" : "forward");
1351
- }
1352
- if (e.key === "[") {
1353
- e.preventDefault();
1354
- this.actions.zOrder(e.ctrlKey || e.metaKey ? "back" : "backward");
1355
- }
1356
- if (e.shiftKey && e.code === "Digit1" && !e.ctrlKey && !e.metaKey && !e.altKey) {
1357
- e.preventDefault();
1358
- this.actions.zoomToFit();
1359
- }
1360
- const nudgeDelta = NUDGE_KEYS[e.key];
1361
- if (nudgeDelta) {
1362
- const [dx, dy] = nudgeDelta;
1363
- if (this.actions.nudge(dx, dy, e.shiftKey)) {
1364
- e.preventDefault();
1365
- }
1482
+ const action = this.shortcutMap.match(e);
1483
+ if (action !== null) {
1484
+ this.runAction(action, e);
1366
1485
  }
1367
1486
  };
1368
1487
  onKeyUp = (e) => {
@@ -1377,6 +1496,79 @@ var InputHandler = class {
1377
1496
  }
1378
1497
  }
1379
1498
  };
1499
+ runAction(action, e) {
1500
+ switch (action) {
1501
+ case "delete":
1502
+ e.preventDefault();
1503
+ this.actions.deleteSelected();
1504
+ return;
1505
+ case "deselect":
1506
+ this.actions.deselect();
1507
+ return;
1508
+ case "undo":
1509
+ e.preventDefault();
1510
+ this.actions.undo();
1511
+ return;
1512
+ case "redo":
1513
+ e.preventDefault();
1514
+ this.actions.redo();
1515
+ return;
1516
+ case "select-all":
1517
+ e.preventDefault();
1518
+ this.actions.selectAll();
1519
+ return;
1520
+ case "copy":
1521
+ e.preventDefault();
1522
+ this.actions.copy();
1523
+ return;
1524
+ case "paste":
1525
+ e.preventDefault();
1526
+ this.actions.paste();
1527
+ return;
1528
+ case "duplicate":
1529
+ e.preventDefault();
1530
+ this.actions.duplicate();
1531
+ return;
1532
+ case "z-forward":
1533
+ e.preventDefault();
1534
+ this.actions.zOrder("forward");
1535
+ return;
1536
+ case "z-backward":
1537
+ e.preventDefault();
1538
+ this.actions.zOrder("backward");
1539
+ return;
1540
+ case "z-front":
1541
+ e.preventDefault();
1542
+ this.actions.zOrder("front");
1543
+ return;
1544
+ case "z-back":
1545
+ e.preventDefault();
1546
+ this.actions.zOrder("back");
1547
+ return;
1548
+ case "zoom-fit":
1549
+ e.preventDefault();
1550
+ this.actions.zoomToFit();
1551
+ return;
1552
+ case "nudge-left":
1553
+ case "nudge-right":
1554
+ case "nudge-up":
1555
+ case "nudge-down": {
1556
+ const delta = NUDGE_DELTAS[action];
1557
+ if (delta && this.actions.nudge(delta[0], delta[1], e.shiftKey)) {
1558
+ e.preventDefault();
1559
+ }
1560
+ return;
1561
+ }
1562
+ default:
1563
+ if (action.startsWith("tool:")) {
1564
+ if (this.isToolActive) return;
1565
+ e.preventDefault();
1566
+ this.toolContext?.switchTool?.(action.slice("tool:".length));
1567
+ return;
1568
+ }
1569
+ console.warn(`[fieldnotes] unknown shortcut action "${action}"`);
1570
+ }
1571
+ }
1380
1572
  startPinch() {
1381
1573
  this.inputFilter.reset();
1382
1574
  this.deferredDown = null;
@@ -1447,6 +1639,15 @@ var InputHandler = class {
1447
1639
  this.toolManager.handlePointerUp(this.toPointerState(e), this.toolContext);
1448
1640
  this.historyRecorder?.commit();
1449
1641
  }
1642
+ isInScope() {
1643
+ if (this.scope === "window") return true;
1644
+ const active = document.activeElement;
1645
+ return active === this.element || this.element.contains(active);
1646
+ }
1647
+ focusSelf() {
1648
+ if (this.scope !== "focus" || this.isInScope()) return;
1649
+ this.element.focus({ preventScroll: true });
1650
+ }
1450
1651
  cancelToolIfActive(e) {
1451
1652
  if (this.isToolActive) {
1452
1653
  this.dispatchToolUp(e);
@@ -3279,17 +3480,36 @@ var FORMAT_SHORTCUTS = {
3279
3480
  i: toggleItalic,
3280
3481
  u: toggleUnderline
3281
3482
  };
3483
+ function ensureEditorStyles() {
3484
+ if (document.querySelector("style[data-fieldnotes-editor]")) return;
3485
+ const style = document.createElement("style");
3486
+ style.setAttribute("data-fieldnotes-editor", "");
3487
+ style.textContent = `[data-fn-placeholder][data-fn-empty='true']::before {
3488
+ content: attr(data-fn-placeholder);
3489
+ color: #9e9e9e;
3490
+ position: absolute;
3491
+ pointer-events: none;
3492
+ }`;
3493
+ document.head.appendChild(style);
3494
+ }
3495
+ function isNodeEmpty(node) {
3496
+ const text = node.textContent ?? "";
3497
+ return text.replace(/\u00a0/g, " ").trim().length === 0;
3498
+ }
3282
3499
  var NoteEditor = class {
3283
3500
  editingId = null;
3284
3501
  editingNode = null;
3285
3502
  blurHandler = null;
3286
3503
  keyHandler = null;
3287
3504
  pointerHandler = null;
3505
+ inputHandler = null;
3288
3506
  pendingEditId = null;
3289
3507
  onStopCallback = null;
3290
3508
  toolbar;
3509
+ placeholder;
3291
3510
  constructor(options) {
3292
3511
  this.toolbar = options?.toolbar === false ? null : new NoteToolbar(options?.fontSizePresets);
3512
+ this.placeholder = options?.placeholder ?? "Type\u2026";
3293
3513
  }
3294
3514
  get isEditing() {
3295
3515
  return this.editingId !== null;
@@ -3324,6 +3544,11 @@ var NoteEditor = class {
3324
3544
  if (this.pointerHandler) {
3325
3545
  this.editingNode.removeEventListener("pointerdown", this.pointerHandler);
3326
3546
  }
3547
+ if (this.inputHandler) {
3548
+ this.editingNode.removeEventListener("input", this.inputHandler);
3549
+ }
3550
+ this.editingNode.removeAttribute("data-fn-placeholder");
3551
+ this.editingNode.removeAttribute("data-fn-empty");
3327
3552
  const text = sanitizeNoteHtml(this.editingNode.innerHTML);
3328
3553
  store.update(this.editingId, { text });
3329
3554
  this.editingNode.contentEditable = "false";
@@ -3340,6 +3565,7 @@ var NoteEditor = class {
3340
3565
  this.blurHandler = null;
3341
3566
  this.keyHandler = null;
3342
3567
  this.pointerHandler = null;
3568
+ this.inputHandler = null;
3343
3569
  }
3344
3570
  destroy(store) {
3345
3571
  this.pendingEditId = null;
@@ -3370,6 +3596,13 @@ var NoteEditor = class {
3370
3596
  selection.removeAllRanges();
3371
3597
  selection.addRange(range);
3372
3598
  }
3599
+ ensureEditorStyles();
3600
+ node.setAttribute("data-fn-placeholder", this.placeholder);
3601
+ node.setAttribute("data-fn-empty", String(isNodeEmpty(node)));
3602
+ this.inputHandler = () => {
3603
+ node.setAttribute("data-fn-empty", String(isNodeEmpty(node)));
3604
+ };
3605
+ node.addEventListener("input", this.inputHandler);
3373
3606
  this.toolbar?.show(node);
3374
3607
  this.blurHandler = (e) => {
3375
3608
  const related = e.relatedTarget;
@@ -4808,7 +5041,8 @@ var Viewport = class {
4808
5041
  });
4809
5042
  this.noteEditor = new NoteEditor({
4810
5043
  fontSizePresets: options.fontSizePresets,
4811
- toolbar: options.toolbar
5044
+ toolbar: options.toolbar,
5045
+ placeholder: options.placeholder
4812
5046
  });
4813
5047
  this.noteEditor.setOnStop((id) => this.onTextEditStop(id));
4814
5048
  this.onHtmlElementMount = options.onHtmlElementMount;
@@ -4841,7 +5075,8 @@ var Viewport = class {
4841
5075
  toolContext: this.toolContext,
4842
5076
  historyRecorder: this.historyRecorder,
4843
5077
  historyStack: this.history,
4844
- fitToContent: () => this.fitToContent()
5078
+ fitToContent: () => this.fitToContent(),
5079
+ shortcuts: options.shortcuts
4845
5080
  });
4846
5081
  this.domNodeManager = new DomNodeManager({
4847
5082
  domLayer: this.domLayer,
@@ -5015,6 +5250,9 @@ var Viewport = class {
5015
5250
  setTool(name) {
5016
5251
  this.toolManager.setTool(name, this.toolContext);
5017
5252
  }
5253
+ get shortcuts() {
5254
+ return this.inputHandler.shortcuts;
5255
+ }
5018
5256
  undo() {
5019
5257
  this.inputHandler.flushPendingHistory();
5020
5258
  this.historyRecorder.pause();
@@ -5150,7 +5388,16 @@ var Viewport = class {
5150
5388
  }
5151
5389
  onTextEditStop(elementId) {
5152
5390
  const element = this.store.getById(elementId);
5153
- if (!element || element.type !== "text") return;
5391
+ if (!element) return;
5392
+ if (element.type === "note") {
5393
+ if (isNoteContentEmpty(element.text)) {
5394
+ this.historyRecorder.begin();
5395
+ this.store.remove(elementId);
5396
+ this.historyRecorder.commit();
5397
+ }
5398
+ return;
5399
+ }
5400
+ if (element.type !== "text") return;
5154
5401
  if (!element.text || element.text.trim() === "") {
5155
5402
  this.historyRecorder.begin();
5156
5403
  this.store.remove(elementId);
@@ -5699,6 +5946,7 @@ var SelectTool = class {
5699
5946
  pendingSingleSelectId = null;
5700
5947
  hasDragged = false;
5701
5948
  resizeAspectRatio = 0;
5949
+ hoveredId = null;
5702
5950
  get selectedIds() {
5703
5951
  return [...this._selectedIds];
5704
5952
  }
@@ -5715,6 +5963,7 @@ var SelectTool = class {
5715
5963
  onDeactivate(ctx) {
5716
5964
  this._selectedIds = [];
5717
5965
  this.mode = { type: "idle" };
5966
+ this.hoveredId = null;
5718
5967
  ctx.setCursor?.("default");
5719
5968
  }
5720
5969
  snap(point, ctx) {
@@ -5722,6 +5971,7 @@ var SelectTool = class {
5722
5971
  }
5723
5972
  onPointerDown(state, ctx) {
5724
5973
  this.ctx = ctx;
5974
+ this.setHovered(null, ctx);
5725
5975
  const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
5726
5976
  this.lastWorld = this.snap(world, ctx);
5727
5977
  this.currentWorld = world;
@@ -5864,7 +6114,8 @@ var SelectTool = class {
5864
6114
  }
5865
6115
  onHover(state, ctx) {
5866
6116
  const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
5867
- this.updateHoverCursor(world, ctx);
6117
+ const hoverId = this.updateHoverCursor(world, ctx);
6118
+ this.setHovered(hoverId, ctx);
5868
6119
  }
5869
6120
  renderOverlay(canvasCtx) {
5870
6121
  this.renderMarquee(canvasCtx);
@@ -5885,6 +6136,23 @@ var SelectTool = class {
5885
6136
  canvasCtx.restore();
5886
6137
  }
5887
6138
  }
6139
+ if (this.hoveredId && this.ctx && this.mode.type === "idle") {
6140
+ if (!this._selectedIds.includes(this.hoveredId)) {
6141
+ const el = this.ctx.store.getById(this.hoveredId);
6142
+ if (el) {
6143
+ const b = getElementBounds(el);
6144
+ if (b) {
6145
+ canvasCtx.save();
6146
+ canvasCtx.strokeStyle = "#2196F3";
6147
+ canvasCtx.globalAlpha = 0.35;
6148
+ canvasCtx.lineWidth = 1.5 / this.ctx.camera.zoom;
6149
+ canvasCtx.setLineDash([]);
6150
+ canvasCtx.strokeRect(b.x, b.y, b.w, b.h);
6151
+ canvasCtx.restore();
6152
+ }
6153
+ }
6154
+ }
6155
+ }
5888
6156
  }
5889
6157
  updateArrowsBoundTo(ids, ctx) {
5890
6158
  const movedNonArrowIds = /* @__PURE__ */ new Set();
@@ -5933,20 +6201,26 @@ var SelectTool = class {
5933
6201
  const arrowHit = hitTestArrowHandles(world, this._selectedIds, ctx);
5934
6202
  if (arrowHit) {
5935
6203
  ctx.setCursor?.(getArrowHandleCursor(arrowHit.handle, false));
5936
- return;
6204
+ return null;
5937
6205
  }
5938
6206
  const templateResizeHit = this.hitTestTemplateResizeHandle(world, ctx);
5939
6207
  if (templateResizeHit) {
5940
6208
  ctx.setCursor?.("nwse-resize");
5941
- return;
6209
+ return null;
5942
6210
  }
5943
6211
  const resizeHit = this.hitTestResizeHandle(world, ctx);
5944
6212
  if (resizeHit) {
5945
6213
  ctx.setCursor?.(HANDLE_CURSORS[resizeHit.handle]);
5946
- return;
6214
+ return null;
5947
6215
  }
5948
6216
  const hit = this.hitTest(world, ctx);
5949
6217
  ctx.setCursor?.(hit ? "move" : "default");
6218
+ return hit ? hit.id : null;
6219
+ }
6220
+ setHovered(id, ctx) {
6221
+ if (this.hoveredId === id) return;
6222
+ this.hoveredId = id;
6223
+ ctx.requestRender();
5950
6224
  }
5951
6225
  handleResize(world, ctx, shiftKey = false) {
5952
6226
  if (this.mode.type !== "resizing") return;
@@ -7053,7 +7327,7 @@ var TemplateTool = class {
7053
7327
  };
7054
7328
 
7055
7329
  // src/index.ts
7056
- var VERSION = "0.18.0";
7330
+ var VERSION = "0.20.0";
7057
7331
  // Annotate the CommonJS export names for ESM import in node:
7058
7332
  0 && (module.exports = {
7059
7333
  AddElementCommand,
@@ -7128,6 +7402,7 @@ var VERSION = "0.18.0";
7128
7402
  getHexDistance,
7129
7403
  isBindable,
7130
7404
  isNearBezier,
7405
+ isNoteContentEmpty,
7131
7406
  parseState,
7132
7407
  sanitizeNoteHtml,
7133
7408
  setFontSize,