@fieldnotes/core 0.19.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) {
@@ -3473,17 +3480,36 @@ var FORMAT_SHORTCUTS = {
3473
3480
  i: toggleItalic,
3474
3481
  u: toggleUnderline
3475
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
+ }
3476
3499
  var NoteEditor = class {
3477
3500
  editingId = null;
3478
3501
  editingNode = null;
3479
3502
  blurHandler = null;
3480
3503
  keyHandler = null;
3481
3504
  pointerHandler = null;
3505
+ inputHandler = null;
3482
3506
  pendingEditId = null;
3483
3507
  onStopCallback = null;
3484
3508
  toolbar;
3509
+ placeholder;
3485
3510
  constructor(options) {
3486
3511
  this.toolbar = options?.toolbar === false ? null : new NoteToolbar(options?.fontSizePresets);
3512
+ this.placeholder = options?.placeholder ?? "Type\u2026";
3487
3513
  }
3488
3514
  get isEditing() {
3489
3515
  return this.editingId !== null;
@@ -3518,6 +3544,11 @@ var NoteEditor = class {
3518
3544
  if (this.pointerHandler) {
3519
3545
  this.editingNode.removeEventListener("pointerdown", this.pointerHandler);
3520
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");
3521
3552
  const text = sanitizeNoteHtml(this.editingNode.innerHTML);
3522
3553
  store.update(this.editingId, { text });
3523
3554
  this.editingNode.contentEditable = "false";
@@ -3534,6 +3565,7 @@ var NoteEditor = class {
3534
3565
  this.blurHandler = null;
3535
3566
  this.keyHandler = null;
3536
3567
  this.pointerHandler = null;
3568
+ this.inputHandler = null;
3537
3569
  }
3538
3570
  destroy(store) {
3539
3571
  this.pendingEditId = null;
@@ -3564,6 +3596,13 @@ var NoteEditor = class {
3564
3596
  selection.removeAllRanges();
3565
3597
  selection.addRange(range);
3566
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);
3567
3606
  this.toolbar?.show(node);
3568
3607
  this.blurHandler = (e) => {
3569
3608
  const related = e.relatedTarget;
@@ -5002,7 +5041,8 @@ var Viewport = class {
5002
5041
  });
5003
5042
  this.noteEditor = new NoteEditor({
5004
5043
  fontSizePresets: options.fontSizePresets,
5005
- toolbar: options.toolbar
5044
+ toolbar: options.toolbar,
5045
+ placeholder: options.placeholder
5006
5046
  });
5007
5047
  this.noteEditor.setOnStop((id) => this.onTextEditStop(id));
5008
5048
  this.onHtmlElementMount = options.onHtmlElementMount;
@@ -5348,7 +5388,16 @@ var Viewport = class {
5348
5388
  }
5349
5389
  onTextEditStop(elementId) {
5350
5390
  const element = this.store.getById(elementId);
5351
- 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;
5352
5401
  if (!element.text || element.text.trim() === "") {
5353
5402
  this.historyRecorder.begin();
5354
5403
  this.store.remove(elementId);
@@ -5897,6 +5946,7 @@ var SelectTool = class {
5897
5946
  pendingSingleSelectId = null;
5898
5947
  hasDragged = false;
5899
5948
  resizeAspectRatio = 0;
5949
+ hoveredId = null;
5900
5950
  get selectedIds() {
5901
5951
  return [...this._selectedIds];
5902
5952
  }
@@ -5913,6 +5963,7 @@ var SelectTool = class {
5913
5963
  onDeactivate(ctx) {
5914
5964
  this._selectedIds = [];
5915
5965
  this.mode = { type: "idle" };
5966
+ this.hoveredId = null;
5916
5967
  ctx.setCursor?.("default");
5917
5968
  }
5918
5969
  snap(point, ctx) {
@@ -5920,6 +5971,7 @@ var SelectTool = class {
5920
5971
  }
5921
5972
  onPointerDown(state, ctx) {
5922
5973
  this.ctx = ctx;
5974
+ this.setHovered(null, ctx);
5923
5975
  const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
5924
5976
  this.lastWorld = this.snap(world, ctx);
5925
5977
  this.currentWorld = world;
@@ -6062,7 +6114,8 @@ var SelectTool = class {
6062
6114
  }
6063
6115
  onHover(state, ctx) {
6064
6116
  const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
6065
- this.updateHoverCursor(world, ctx);
6117
+ const hoverId = this.updateHoverCursor(world, ctx);
6118
+ this.setHovered(hoverId, ctx);
6066
6119
  }
6067
6120
  renderOverlay(canvasCtx) {
6068
6121
  this.renderMarquee(canvasCtx);
@@ -6083,6 +6136,23 @@ var SelectTool = class {
6083
6136
  canvasCtx.restore();
6084
6137
  }
6085
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
+ }
6086
6156
  }
6087
6157
  updateArrowsBoundTo(ids, ctx) {
6088
6158
  const movedNonArrowIds = /* @__PURE__ */ new Set();
@@ -6131,20 +6201,26 @@ var SelectTool = class {
6131
6201
  const arrowHit = hitTestArrowHandles(world, this._selectedIds, ctx);
6132
6202
  if (arrowHit) {
6133
6203
  ctx.setCursor?.(getArrowHandleCursor(arrowHit.handle, false));
6134
- return;
6204
+ return null;
6135
6205
  }
6136
6206
  const templateResizeHit = this.hitTestTemplateResizeHandle(world, ctx);
6137
6207
  if (templateResizeHit) {
6138
6208
  ctx.setCursor?.("nwse-resize");
6139
- return;
6209
+ return null;
6140
6210
  }
6141
6211
  const resizeHit = this.hitTestResizeHandle(world, ctx);
6142
6212
  if (resizeHit) {
6143
6213
  ctx.setCursor?.(HANDLE_CURSORS[resizeHit.handle]);
6144
- return;
6214
+ return null;
6145
6215
  }
6146
6216
  const hit = this.hitTest(world, ctx);
6147
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();
6148
6224
  }
6149
6225
  handleResize(world, ctx, shiftKey = false) {
6150
6226
  if (this.mode.type !== "resizing") return;
@@ -7251,7 +7327,7 @@ var TemplateTool = class {
7251
7327
  };
7252
7328
 
7253
7329
  // src/index.ts
7254
- var VERSION = "0.19.0";
7330
+ var VERSION = "0.20.0";
7255
7331
  // Annotate the CommonJS export names for ESM import in node:
7256
7332
  0 && (module.exports = {
7257
7333
  AddElementCommand,
@@ -7326,6 +7402,7 @@ var VERSION = "0.19.0";
7326
7402
  getHexDistance,
7327
7403
  isBindable,
7328
7404
  isNearBezier,
7405
+ isNoteContentEmpty,
7329
7406
  parseState,
7330
7407
  sanitizeNoteHtml,
7331
7408
  setFontSize,