@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.js CHANGED
@@ -273,6 +273,12 @@ function sanitizeNode(node) {
273
273
  sanitizeNode(el);
274
274
  }
275
275
  }
276
+ function isNoteContentEmpty(html) {
277
+ if (!html) return true;
278
+ const doc = new DOMParser().parseFromString(html, "text/html");
279
+ const text = doc.body.textContent ?? "";
280
+ return text.replace(/\u00a0/g, " ").trim().length === 0;
281
+ }
276
282
  function sanitizeAttributes(el, tag) {
277
283
  const attrs = Array.from(el.attributes);
278
284
  for (const attr of attrs) {
@@ -1041,14 +1047,158 @@ var KeyboardActions = class {
1041
1047
  }
1042
1048
  };
1043
1049
 
1050
+ // src/canvas/shortcut-map.ts
1051
+ var DEFAULT_BINDINGS = [
1052
+ ["delete", ["delete", "backspace"]],
1053
+ ["deselect", ["escape"]],
1054
+ ["undo", ["mod+z"]],
1055
+ ["redo", ["mod+y", "mod+shift+z"]],
1056
+ ["select-all", ["mod+a"]],
1057
+ ["copy", ["mod+c"]],
1058
+ ["paste", ["mod+v"]],
1059
+ ["duplicate", ["mod+d"]],
1060
+ ["z-forward", ["]"]],
1061
+ ["z-backward", ["["]],
1062
+ ["z-front", ["mod+]"]],
1063
+ ["z-back", ["mod+["]],
1064
+ ["zoom-fit", ["shift+1"]],
1065
+ ["nudge-left", ["arrowleft"]],
1066
+ ["nudge-right", ["arrowright"]],
1067
+ ["nudge-up", ["arrowup"]],
1068
+ ["nudge-down", ["arrowdown"]],
1069
+ ["tool:select", ["v"]],
1070
+ ["tool:hand", ["h"]],
1071
+ ["tool:pencil", ["p"]],
1072
+ ["tool:eraser", ["e"]],
1073
+ ["tool:arrow", ["a"]],
1074
+ ["tool:note", ["n"]],
1075
+ ["tool:text", ["t"]],
1076
+ ["tool:shape", ["s"]],
1077
+ ["tool:measure", ["m"]],
1078
+ ["tool:template", ["g"]]
1079
+ ];
1080
+ var ALLOW_SHIFT = /* @__PURE__ */ new Set(["nudge-left", "nudge-right", "nudge-up", "nudge-down"]);
1081
+ var MODIFIERS = /* @__PURE__ */ new Set(["mod", "ctrl", "meta", "shift", "alt"]);
1082
+ function parseBinding(binding) {
1083
+ const parts = binding.toLowerCase().split("+");
1084
+ const key = parts.pop();
1085
+ if (key === void 0 || key.length === 0 || MODIFIERS.has(key)) {
1086
+ throw new Error(`Invalid shortcut binding "${binding}": missing key`);
1087
+ }
1088
+ const normalizedKey = key === "space" ? " " : key;
1089
+ const parsed = {
1090
+ mod: false,
1091
+ ctrl: false,
1092
+ meta: false,
1093
+ shift: false,
1094
+ alt: false,
1095
+ key: normalizedKey,
1096
+ digit: /^[0-9]$/.test(normalizedKey)
1097
+ };
1098
+ for (const part of parts) {
1099
+ switch (part) {
1100
+ case "mod":
1101
+ parsed.mod = true;
1102
+ break;
1103
+ case "ctrl":
1104
+ parsed.ctrl = true;
1105
+ break;
1106
+ case "meta":
1107
+ parsed.meta = true;
1108
+ break;
1109
+ case "shift":
1110
+ parsed.shift = true;
1111
+ break;
1112
+ case "alt":
1113
+ parsed.alt = true;
1114
+ break;
1115
+ default:
1116
+ throw new Error(`Invalid shortcut binding "${binding}": unknown modifier "${part}"`);
1117
+ }
1118
+ }
1119
+ return parsed;
1120
+ }
1121
+ function bindingMatches(p, e, allowShift) {
1122
+ if (p.mod) {
1123
+ if (!e.ctrlKey && !e.metaKey) return false;
1124
+ } else if (e.ctrlKey !== p.ctrl || e.metaKey !== p.meta) {
1125
+ return false;
1126
+ }
1127
+ if (!allowShift && e.shiftKey !== p.shift) return false;
1128
+ if (e.altKey !== p.alt) return false;
1129
+ return p.digit ? e.code === `Digit${p.key}` : e.key.toLowerCase() === p.key;
1130
+ }
1131
+ function toArray(bindings) {
1132
+ if (bindings === null) return [];
1133
+ return Array.isArray(bindings) ? [...bindings] : [bindings];
1134
+ }
1135
+ var ShortcutMap = class {
1136
+ raw = /* @__PURE__ */ new Map();
1137
+ parsed = /* @__PURE__ */ new Map();
1138
+ constructor(overrides) {
1139
+ this.applyDefaults();
1140
+ if (overrides) {
1141
+ for (const [action, bindings] of Object.entries(overrides)) {
1142
+ this.rebind(action, bindings);
1143
+ }
1144
+ }
1145
+ }
1146
+ /** First matching action in registration order wins when bindings conflict. */
1147
+ match(e) {
1148
+ for (const [action, parsedList] of this.parsed) {
1149
+ const allowShift = ALLOW_SHIFT.has(action);
1150
+ for (const p of parsedList) {
1151
+ if (bindingMatches(p, e, allowShift)) return action;
1152
+ }
1153
+ }
1154
+ return null;
1155
+ }
1156
+ rebind(action, bindings) {
1157
+ const list = toArray(bindings);
1158
+ const parsedList = list.map(parseBinding);
1159
+ this.raw.set(action, list);
1160
+ this.parsed.set(action, parsedList);
1161
+ }
1162
+ disable(action) {
1163
+ this.rebind(action, null);
1164
+ }
1165
+ reset(action) {
1166
+ if (action === void 0) {
1167
+ this.raw.clear();
1168
+ this.parsed.clear();
1169
+ this.applyDefaults();
1170
+ return;
1171
+ }
1172
+ const def = DEFAULT_BINDINGS.find(([name]) => name === action);
1173
+ if (def) {
1174
+ this.rebind(action, [...def[1]]);
1175
+ } else if (this.raw.has(action)) {
1176
+ this.raw.delete(action);
1177
+ this.parsed.delete(action);
1178
+ }
1179
+ }
1180
+ getBindings() {
1181
+ const out = {};
1182
+ for (const [action, list] of this.raw) {
1183
+ out[action] = [...list];
1184
+ }
1185
+ return out;
1186
+ }
1187
+ applyDefaults() {
1188
+ for (const [action, bindings] of DEFAULT_BINDINGS) {
1189
+ this.rebind(action, [...bindings]);
1190
+ }
1191
+ }
1192
+ };
1193
+
1044
1194
  // src/canvas/input-handler.ts
1045
1195
  var ZOOM_SENSITIVITY = 1e-3;
1046
1196
  var MIDDLE_BUTTON = 1;
1047
- var NUDGE_KEYS = {
1048
- ArrowLeft: [-1, 0],
1049
- ArrowRight: [1, 0],
1050
- ArrowUp: [0, -1],
1051
- ArrowDown: [0, 1]
1197
+ var NUDGE_DELTAS = {
1198
+ "nudge-left": [-1, 0],
1199
+ "nudge-right": [1, 0],
1200
+ "nudge-up": [0, -1],
1201
+ "nudge-down": [0, 1]
1052
1202
  };
1053
1203
  var InputHandler = class {
1054
1204
  constructor(element, camera, options = {}) {
@@ -1066,7 +1216,13 @@ var InputHandler = class {
1066
1216
  isToolActive: () => this.isToolActive,
1067
1217
  fitToContent: options.fitToContent
1068
1218
  });
1219
+ this.shortcutMap = new ShortcutMap(options.shortcuts?.bindings);
1220
+ this.scope = options.shortcuts?.scope ?? "focus";
1069
1221
  this.element.style.touchAction = "none";
1222
+ if (this.scope === "focus") {
1223
+ this.element.tabIndex = 0;
1224
+ this.element.style.outline = "none";
1225
+ }
1070
1226
  this.bind();
1071
1227
  }
1072
1228
  isPanning = false;
@@ -1085,6 +1241,8 @@ var InputHandler = class {
1085
1241
  deferredDown = null;
1086
1242
  abortController = new AbortController();
1087
1243
  actions;
1244
+ shortcutMap;
1245
+ scope;
1088
1246
  setToolManager(toolManager, toolContext) {
1089
1247
  this.toolManager = toolManager;
1090
1248
  this.toolContext = toolContext;
@@ -1092,6 +1250,9 @@ var InputHandler = class {
1092
1250
  flushPendingHistory() {
1093
1251
  this.actions.flushPendingNudge();
1094
1252
  }
1253
+ get shortcuts() {
1254
+ return this.shortcutMap;
1255
+ }
1095
1256
  destroy() {
1096
1257
  this.actions.dispose();
1097
1258
  this.abortController.abort();
@@ -1121,6 +1282,7 @@ var InputHandler = class {
1121
1282
  });
1122
1283
  };
1123
1284
  onPointerDown = (e) => {
1285
+ this.focusSelf();
1124
1286
  this.activePointers.set(e.pointerId, { x: e.clientX, y: e.clientY });
1125
1287
  this.element.setPointerCapture?.(e.pointerId);
1126
1288
  if (this.activePointers.size === 2) {
@@ -1203,57 +1365,13 @@ var InputHandler = class {
1203
1365
  if (target?.isContentEditable) return;
1204
1366
  const tag = target?.tagName;
1205
1367
  if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return;
1368
+ if (!this.isInScope()) return;
1206
1369
  if (e.key === " ") {
1207
1370
  this.spaceHeld = true;
1208
1371
  }
1209
- if (e.key === "Delete" || e.key === "Backspace") {
1210
- this.actions.deleteSelected();
1211
- }
1212
- if (e.key === "Escape") {
1213
- this.actions.deselect();
1214
- }
1215
- if ((e.ctrlKey || e.metaKey) && e.key === "z" && !e.shiftKey) {
1216
- e.preventDefault();
1217
- this.actions.undo();
1218
- }
1219
- if ((e.ctrlKey || e.metaKey) && (e.key === "y" || e.key === "z" && e.shiftKey)) {
1220
- e.preventDefault();
1221
- this.actions.redo();
1222
- }
1223
- if ((e.ctrlKey || e.metaKey) && e.key === "a") {
1224
- e.preventDefault();
1225
- this.actions.selectAll();
1226
- }
1227
- if ((e.ctrlKey || e.metaKey) && e.key === "c") {
1228
- e.preventDefault();
1229
- this.actions.copy();
1230
- }
1231
- if ((e.ctrlKey || e.metaKey) && e.key === "v") {
1232
- e.preventDefault();
1233
- this.actions.paste();
1234
- }
1235
- if ((e.ctrlKey || e.metaKey) && e.key === "d") {
1236
- e.preventDefault();
1237
- this.actions.duplicate();
1238
- }
1239
- if (e.key === "]") {
1240
- e.preventDefault();
1241
- this.actions.zOrder(e.ctrlKey || e.metaKey ? "front" : "forward");
1242
- }
1243
- if (e.key === "[") {
1244
- e.preventDefault();
1245
- this.actions.zOrder(e.ctrlKey || e.metaKey ? "back" : "backward");
1246
- }
1247
- if (e.shiftKey && e.code === "Digit1" && !e.ctrlKey && !e.metaKey && !e.altKey) {
1248
- e.preventDefault();
1249
- this.actions.zoomToFit();
1250
- }
1251
- const nudgeDelta = NUDGE_KEYS[e.key];
1252
- if (nudgeDelta) {
1253
- const [dx, dy] = nudgeDelta;
1254
- if (this.actions.nudge(dx, dy, e.shiftKey)) {
1255
- e.preventDefault();
1256
- }
1372
+ const action = this.shortcutMap.match(e);
1373
+ if (action !== null) {
1374
+ this.runAction(action, e);
1257
1375
  }
1258
1376
  };
1259
1377
  onKeyUp = (e) => {
@@ -1268,6 +1386,79 @@ var InputHandler = class {
1268
1386
  }
1269
1387
  }
1270
1388
  };
1389
+ runAction(action, e) {
1390
+ switch (action) {
1391
+ case "delete":
1392
+ e.preventDefault();
1393
+ this.actions.deleteSelected();
1394
+ return;
1395
+ case "deselect":
1396
+ this.actions.deselect();
1397
+ return;
1398
+ case "undo":
1399
+ e.preventDefault();
1400
+ this.actions.undo();
1401
+ return;
1402
+ case "redo":
1403
+ e.preventDefault();
1404
+ this.actions.redo();
1405
+ return;
1406
+ case "select-all":
1407
+ e.preventDefault();
1408
+ this.actions.selectAll();
1409
+ return;
1410
+ case "copy":
1411
+ e.preventDefault();
1412
+ this.actions.copy();
1413
+ return;
1414
+ case "paste":
1415
+ e.preventDefault();
1416
+ this.actions.paste();
1417
+ return;
1418
+ case "duplicate":
1419
+ e.preventDefault();
1420
+ this.actions.duplicate();
1421
+ return;
1422
+ case "z-forward":
1423
+ e.preventDefault();
1424
+ this.actions.zOrder("forward");
1425
+ return;
1426
+ case "z-backward":
1427
+ e.preventDefault();
1428
+ this.actions.zOrder("backward");
1429
+ return;
1430
+ case "z-front":
1431
+ e.preventDefault();
1432
+ this.actions.zOrder("front");
1433
+ return;
1434
+ case "z-back":
1435
+ e.preventDefault();
1436
+ this.actions.zOrder("back");
1437
+ return;
1438
+ case "zoom-fit":
1439
+ e.preventDefault();
1440
+ this.actions.zoomToFit();
1441
+ return;
1442
+ case "nudge-left":
1443
+ case "nudge-right":
1444
+ case "nudge-up":
1445
+ case "nudge-down": {
1446
+ const delta = NUDGE_DELTAS[action];
1447
+ if (delta && this.actions.nudge(delta[0], delta[1], e.shiftKey)) {
1448
+ e.preventDefault();
1449
+ }
1450
+ return;
1451
+ }
1452
+ default:
1453
+ if (action.startsWith("tool:")) {
1454
+ if (this.isToolActive) return;
1455
+ e.preventDefault();
1456
+ this.toolContext?.switchTool?.(action.slice("tool:".length));
1457
+ return;
1458
+ }
1459
+ console.warn(`[fieldnotes] unknown shortcut action "${action}"`);
1460
+ }
1461
+ }
1271
1462
  startPinch() {
1272
1463
  this.inputFilter.reset();
1273
1464
  this.deferredDown = null;
@@ -1338,6 +1529,15 @@ var InputHandler = class {
1338
1529
  this.toolManager.handlePointerUp(this.toPointerState(e), this.toolContext);
1339
1530
  this.historyRecorder?.commit();
1340
1531
  }
1532
+ isInScope() {
1533
+ if (this.scope === "window") return true;
1534
+ const active = document.activeElement;
1535
+ return active === this.element || this.element.contains(active);
1536
+ }
1537
+ focusSelf() {
1538
+ if (this.scope !== "focus" || this.isInScope()) return;
1539
+ this.element.focus({ preventScroll: true });
1540
+ }
1341
1541
  cancelToolIfActive(e) {
1342
1542
  if (this.isToolActive) {
1343
1543
  this.dispatchToolUp(e);
@@ -3170,17 +3370,36 @@ var FORMAT_SHORTCUTS = {
3170
3370
  i: toggleItalic,
3171
3371
  u: toggleUnderline
3172
3372
  };
3373
+ function ensureEditorStyles() {
3374
+ if (document.querySelector("style[data-fieldnotes-editor]")) return;
3375
+ const style = document.createElement("style");
3376
+ style.setAttribute("data-fieldnotes-editor", "");
3377
+ style.textContent = `[data-fn-placeholder][data-fn-empty='true']::before {
3378
+ content: attr(data-fn-placeholder);
3379
+ color: #9e9e9e;
3380
+ position: absolute;
3381
+ pointer-events: none;
3382
+ }`;
3383
+ document.head.appendChild(style);
3384
+ }
3385
+ function isNodeEmpty(node) {
3386
+ const text = node.textContent ?? "";
3387
+ return text.replace(/\u00a0/g, " ").trim().length === 0;
3388
+ }
3173
3389
  var NoteEditor = class {
3174
3390
  editingId = null;
3175
3391
  editingNode = null;
3176
3392
  blurHandler = null;
3177
3393
  keyHandler = null;
3178
3394
  pointerHandler = null;
3395
+ inputHandler = null;
3179
3396
  pendingEditId = null;
3180
3397
  onStopCallback = null;
3181
3398
  toolbar;
3399
+ placeholder;
3182
3400
  constructor(options) {
3183
3401
  this.toolbar = options?.toolbar === false ? null : new NoteToolbar(options?.fontSizePresets);
3402
+ this.placeholder = options?.placeholder ?? "Type\u2026";
3184
3403
  }
3185
3404
  get isEditing() {
3186
3405
  return this.editingId !== null;
@@ -3215,6 +3434,11 @@ var NoteEditor = class {
3215
3434
  if (this.pointerHandler) {
3216
3435
  this.editingNode.removeEventListener("pointerdown", this.pointerHandler);
3217
3436
  }
3437
+ if (this.inputHandler) {
3438
+ this.editingNode.removeEventListener("input", this.inputHandler);
3439
+ }
3440
+ this.editingNode.removeAttribute("data-fn-placeholder");
3441
+ this.editingNode.removeAttribute("data-fn-empty");
3218
3442
  const text = sanitizeNoteHtml(this.editingNode.innerHTML);
3219
3443
  store.update(this.editingId, { text });
3220
3444
  this.editingNode.contentEditable = "false";
@@ -3231,6 +3455,7 @@ var NoteEditor = class {
3231
3455
  this.blurHandler = null;
3232
3456
  this.keyHandler = null;
3233
3457
  this.pointerHandler = null;
3458
+ this.inputHandler = null;
3234
3459
  }
3235
3460
  destroy(store) {
3236
3461
  this.pendingEditId = null;
@@ -3261,6 +3486,13 @@ var NoteEditor = class {
3261
3486
  selection.removeAllRanges();
3262
3487
  selection.addRange(range);
3263
3488
  }
3489
+ ensureEditorStyles();
3490
+ node.setAttribute("data-fn-placeholder", this.placeholder);
3491
+ node.setAttribute("data-fn-empty", String(isNodeEmpty(node)));
3492
+ this.inputHandler = () => {
3493
+ node.setAttribute("data-fn-empty", String(isNodeEmpty(node)));
3494
+ };
3495
+ node.addEventListener("input", this.inputHandler);
3264
3496
  this.toolbar?.show(node);
3265
3497
  this.blurHandler = (e) => {
3266
3498
  const related = e.relatedTarget;
@@ -4699,7 +4931,8 @@ var Viewport = class {
4699
4931
  });
4700
4932
  this.noteEditor = new NoteEditor({
4701
4933
  fontSizePresets: options.fontSizePresets,
4702
- toolbar: options.toolbar
4934
+ toolbar: options.toolbar,
4935
+ placeholder: options.placeholder
4703
4936
  });
4704
4937
  this.noteEditor.setOnStop((id) => this.onTextEditStop(id));
4705
4938
  this.onHtmlElementMount = options.onHtmlElementMount;
@@ -4732,7 +4965,8 @@ var Viewport = class {
4732
4965
  toolContext: this.toolContext,
4733
4966
  historyRecorder: this.historyRecorder,
4734
4967
  historyStack: this.history,
4735
- fitToContent: () => this.fitToContent()
4968
+ fitToContent: () => this.fitToContent(),
4969
+ shortcuts: options.shortcuts
4736
4970
  });
4737
4971
  this.domNodeManager = new DomNodeManager({
4738
4972
  domLayer: this.domLayer,
@@ -4906,6 +5140,9 @@ var Viewport = class {
4906
5140
  setTool(name) {
4907
5141
  this.toolManager.setTool(name, this.toolContext);
4908
5142
  }
5143
+ get shortcuts() {
5144
+ return this.inputHandler.shortcuts;
5145
+ }
4909
5146
  undo() {
4910
5147
  this.inputHandler.flushPendingHistory();
4911
5148
  this.historyRecorder.pause();
@@ -5041,7 +5278,16 @@ var Viewport = class {
5041
5278
  }
5042
5279
  onTextEditStop(elementId) {
5043
5280
  const element = this.store.getById(elementId);
5044
- if (!element || element.type !== "text") return;
5281
+ if (!element) return;
5282
+ if (element.type === "note") {
5283
+ if (isNoteContentEmpty(element.text)) {
5284
+ this.historyRecorder.begin();
5285
+ this.store.remove(elementId);
5286
+ this.historyRecorder.commit();
5287
+ }
5288
+ return;
5289
+ }
5290
+ if (element.type !== "text") return;
5045
5291
  if (!element.text || element.text.trim() === "") {
5046
5292
  this.historyRecorder.begin();
5047
5293
  this.store.remove(elementId);
@@ -5590,6 +5836,7 @@ var SelectTool = class {
5590
5836
  pendingSingleSelectId = null;
5591
5837
  hasDragged = false;
5592
5838
  resizeAspectRatio = 0;
5839
+ hoveredId = null;
5593
5840
  get selectedIds() {
5594
5841
  return [...this._selectedIds];
5595
5842
  }
@@ -5606,6 +5853,7 @@ var SelectTool = class {
5606
5853
  onDeactivate(ctx) {
5607
5854
  this._selectedIds = [];
5608
5855
  this.mode = { type: "idle" };
5856
+ this.hoveredId = null;
5609
5857
  ctx.setCursor?.("default");
5610
5858
  }
5611
5859
  snap(point, ctx) {
@@ -5613,6 +5861,7 @@ var SelectTool = class {
5613
5861
  }
5614
5862
  onPointerDown(state, ctx) {
5615
5863
  this.ctx = ctx;
5864
+ this.setHovered(null, ctx);
5616
5865
  const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
5617
5866
  this.lastWorld = this.snap(world, ctx);
5618
5867
  this.currentWorld = world;
@@ -5755,7 +6004,8 @@ var SelectTool = class {
5755
6004
  }
5756
6005
  onHover(state, ctx) {
5757
6006
  const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
5758
- this.updateHoverCursor(world, ctx);
6007
+ const hoverId = this.updateHoverCursor(world, ctx);
6008
+ this.setHovered(hoverId, ctx);
5759
6009
  }
5760
6010
  renderOverlay(canvasCtx) {
5761
6011
  this.renderMarquee(canvasCtx);
@@ -5776,6 +6026,23 @@ var SelectTool = class {
5776
6026
  canvasCtx.restore();
5777
6027
  }
5778
6028
  }
6029
+ if (this.hoveredId && this.ctx && this.mode.type === "idle") {
6030
+ if (!this._selectedIds.includes(this.hoveredId)) {
6031
+ const el = this.ctx.store.getById(this.hoveredId);
6032
+ if (el) {
6033
+ const b = getElementBounds(el);
6034
+ if (b) {
6035
+ canvasCtx.save();
6036
+ canvasCtx.strokeStyle = "#2196F3";
6037
+ canvasCtx.globalAlpha = 0.35;
6038
+ canvasCtx.lineWidth = 1.5 / this.ctx.camera.zoom;
6039
+ canvasCtx.setLineDash([]);
6040
+ canvasCtx.strokeRect(b.x, b.y, b.w, b.h);
6041
+ canvasCtx.restore();
6042
+ }
6043
+ }
6044
+ }
6045
+ }
5779
6046
  }
5780
6047
  updateArrowsBoundTo(ids, ctx) {
5781
6048
  const movedNonArrowIds = /* @__PURE__ */ new Set();
@@ -5824,20 +6091,26 @@ var SelectTool = class {
5824
6091
  const arrowHit = hitTestArrowHandles(world, this._selectedIds, ctx);
5825
6092
  if (arrowHit) {
5826
6093
  ctx.setCursor?.(getArrowHandleCursor(arrowHit.handle, false));
5827
- return;
6094
+ return null;
5828
6095
  }
5829
6096
  const templateResizeHit = this.hitTestTemplateResizeHandle(world, ctx);
5830
6097
  if (templateResizeHit) {
5831
6098
  ctx.setCursor?.("nwse-resize");
5832
- return;
6099
+ return null;
5833
6100
  }
5834
6101
  const resizeHit = this.hitTestResizeHandle(world, ctx);
5835
6102
  if (resizeHit) {
5836
6103
  ctx.setCursor?.(HANDLE_CURSORS[resizeHit.handle]);
5837
- return;
6104
+ return null;
5838
6105
  }
5839
6106
  const hit = this.hitTest(world, ctx);
5840
6107
  ctx.setCursor?.(hit ? "move" : "default");
6108
+ return hit ? hit.id : null;
6109
+ }
6110
+ setHovered(id, ctx) {
6111
+ if (this.hoveredId === id) return;
6112
+ this.hoveredId = id;
6113
+ ctx.requestRender();
5841
6114
  }
5842
6115
  handleResize(world, ctx, shiftKey = false) {
5843
6116
  if (this.mode.type !== "resizing") return;
@@ -6944,7 +7217,7 @@ var TemplateTool = class {
6944
7217
  };
6945
7218
 
6946
7219
  // src/index.ts
6947
- var VERSION = "0.18.0";
7220
+ var VERSION = "0.20.0";
6948
7221
  export {
6949
7222
  AddElementCommand,
6950
7223
  ArrowTool,
@@ -7018,6 +7291,7 @@ export {
7018
7291
  getHexDistance,
7019
7292
  isBindable,
7020
7293
  isNearBezier,
7294
+ isNoteContentEmpty,
7021
7295
  parseState,
7022
7296
  sanitizeNoteHtml,
7023
7297
  setFontSize,