@fieldnotes/core 0.38.2 → 0.38.3

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
@@ -6059,9 +6059,158 @@ var GridController = class {
6059
6059
  }
6060
6060
  };
6061
6061
 
6062
+ // src/canvas/viewport-interactions.ts
6063
+ var ARROW_HIT_THRESHOLD = 10;
6064
+ var ViewportInteractions = class {
6065
+ constructor(deps) {
6066
+ this.deps = deps;
6067
+ }
6068
+ doubleTapDetector = new DoubleTapDetector();
6069
+ tapDownX = 0;
6070
+ tapDownY = 0;
6071
+ startEditingElement(id) {
6072
+ const element = this.deps.store.getById(id);
6073
+ if (!element || element.type !== "note" && element.type !== "text") return;
6074
+ this.deps.renderLoop.flush();
6075
+ const node = this.deps.domNodeManager.getNode(id);
6076
+ if (node) {
6077
+ this.deps.noteEditor.startEditing(node, id, this.deps.store);
6078
+ }
6079
+ }
6080
+ fitNoteHeight(elementId) {
6081
+ const element = this.deps.store.getById(elementId);
6082
+ if (!element || element.type !== "note") return;
6083
+ if (isNoteContentEmpty(element.text)) return;
6084
+ const node = this.deps.domNodeManager.getNode(elementId);
6085
+ if (!node) return;
6086
+ const measured = node.scrollHeight;
6087
+ if (measured > element.size.h) {
6088
+ this.deps.store.update(elementId, { size: { w: element.size.w, h: measured } });
6089
+ }
6090
+ }
6091
+ onTextEditStop(elementId) {
6092
+ const element = this.deps.store.getById(elementId);
6093
+ if (!element) return;
6094
+ if (element.type === "note") {
6095
+ if (isNoteContentEmpty(element.text)) {
6096
+ this.deps.store.remove(elementId);
6097
+ return;
6098
+ }
6099
+ this.fitNoteHeight(elementId);
6100
+ return;
6101
+ }
6102
+ if (element.type !== "text") return;
6103
+ if (!element.text || element.text.trim() === "") {
6104
+ this.deps.store.remove(elementId);
6105
+ return;
6106
+ }
6107
+ const node = this.deps.domNodeManager.getNode(elementId);
6108
+ if (node && "size" in element) {
6109
+ const measured = node.scrollHeight;
6110
+ if (measured !== element.size.h) {
6111
+ this.deps.store.update(elementId, { size: { w: element.size.w, h: measured } });
6112
+ }
6113
+ }
6114
+ }
6115
+ onTapDown = (e) => {
6116
+ this.tapDownX = e.clientX;
6117
+ this.tapDownY = e.clientY;
6118
+ };
6119
+ onDoubleTap = (e) => {
6120
+ const dx = e.clientX - this.tapDownX;
6121
+ const dy = e.clientY - this.tapDownY;
6122
+ const moved = Math.sqrt(dx * dx + dy * dy);
6123
+ if (moved > 10) return;
6124
+ if (!this.doubleTapDetector.feed(e)) return;
6125
+ if (typeof document.elementFromPoint !== "function") return;
6126
+ const el = document.elementFromPoint(e.clientX, e.clientY);
6127
+ const nodeEl = el?.closest("[data-element-id]");
6128
+ if (nodeEl) {
6129
+ const elementId = nodeEl.dataset["elementId"];
6130
+ if (elementId) {
6131
+ const element = this.deps.store.getById(elementId);
6132
+ if (element?.type === "note" || element?.type === "text") {
6133
+ this.startEditingElement(elementId);
6134
+ return;
6135
+ }
6136
+ }
6137
+ }
6138
+ const rect = this.deps.wrapper.getBoundingClientRect();
6139
+ const screen = { x: e.clientX - rect.left, y: e.clientY - rect.top };
6140
+ const world = this.deps.camera.screenToWorld(screen);
6141
+ const hit = this.hitTestWorld(world);
6142
+ if (hit?.type === "html") {
6143
+ this.deps.interactMode.startInteracting(hit.id);
6144
+ return;
6145
+ }
6146
+ const arrow = this.findArrowAt(world);
6147
+ if (arrow) {
6148
+ this.startArrowLabelEdit(arrow);
6149
+ }
6150
+ };
6151
+ findArrowAt(world) {
6152
+ const candidates = this.deps.store.queryPoint(world).reverse();
6153
+ for (const el of candidates) {
6154
+ if (el.type === "arrow" && isNearBezier(world, el.from, el.to, el.bend, ARROW_HIT_THRESHOLD)) {
6155
+ return el;
6156
+ }
6157
+ }
6158
+ return void 0;
6159
+ }
6160
+ startArrowLabelEdit(arrow) {
6161
+ this.deps.arrowLabelEditor.startEditing({
6162
+ arrow,
6163
+ layer: this.deps.domLayer,
6164
+ store: this.deps.store,
6165
+ recorder: this.deps.recorder,
6166
+ onDone: () => {
6167
+ this.deps.renderer.setLabelEditingId(null);
6168
+ this.deps.requestRender();
6169
+ }
6170
+ });
6171
+ this.deps.renderer.setLabelEditingId(arrow.id);
6172
+ }
6173
+ hitTestWorld(world) {
6174
+ const candidates = this.deps.store.queryPoint(world).reverse();
6175
+ for (const el of candidates) {
6176
+ if (!("size" in el)) continue;
6177
+ const { x, y } = el.position;
6178
+ const { w, h } = el.size;
6179
+ if (world.x >= x && world.x <= x + w && world.y >= y && world.y <= y + h) {
6180
+ return el;
6181
+ }
6182
+ }
6183
+ return null;
6184
+ }
6185
+ onDragOver = (e) => {
6186
+ e.preventDefault();
6187
+ };
6188
+ onDrop = (e) => {
6189
+ e.preventDefault();
6190
+ const rect = this.deps.wrapper.getBoundingClientRect();
6191
+ const screenPos = { x: e.clientX - rect.left, y: e.clientY - rect.top };
6192
+ const worldPos = this.deps.camera.screenToWorld(screenPos);
6193
+ if (this.deps.dropHandler) {
6194
+ this.deps.dropHandler(e, worldPos);
6195
+ return;
6196
+ }
6197
+ const files = e.dataTransfer?.files;
6198
+ if (!files) return;
6199
+ for (const file of files) {
6200
+ if (!file.type.startsWith("image/")) continue;
6201
+ const reader = new FileReader();
6202
+ reader.onload = () => {
6203
+ const src = reader.result;
6204
+ if (typeof src !== "string") return;
6205
+ this.deps.addImage(src, worldPos);
6206
+ };
6207
+ reader.readAsDataURL(file);
6208
+ }
6209
+ };
6210
+ };
6211
+
6062
6212
  // src/canvas/viewport.ts
6063
6213
  var EMPTY_IDS = [];
6064
- var ARROW_HIT_THRESHOLD = 10;
6065
6214
  function noop() {
6066
6215
  }
6067
6216
  var Viewport = class {
@@ -6096,7 +6245,7 @@ var Viewport = class {
6096
6245
  toolbar: options.toolbar,
6097
6246
  placeholder: options.placeholder
6098
6247
  });
6099
- this.noteEditor.setOnStop((id) => this.onTextEditStop(id));
6248
+ this.noteEditor.setOnStop((id) => this.interactions.onTextEditStop(id));
6100
6249
  this.arrowLabelEditor = new ArrowLabelEditor();
6101
6250
  this.noteEditor.setHistoryHooks(
6102
6251
  () => this.historyRecorder.begin(),
@@ -6123,8 +6272,8 @@ var Viewport = class {
6123
6272
  store: this.store,
6124
6273
  requestRender: () => this.requestRender(),
6125
6274
  switchTool: (name) => this.toolManager.setTool(name, this.toolContext),
6126
- editElement: (id) => this.startEditingElement(id),
6127
- fitNoteHeight: (id) => this.fitNoteHeight(id),
6275
+ editElement: (id) => this.interactions.startEditingElement(id),
6276
+ fitNoteHeight: (id) => this.interactions.fitNoteHeight(id),
6128
6277
  setCursor: (cursor) => {
6129
6278
  this.wrapper.style.cursor = cursor;
6130
6279
  },
@@ -6160,7 +6309,7 @@ var Viewport = class {
6160
6309
  this.unsubToolChange = this.toolManager.onChange(() => this.contextMenu?.close());
6161
6310
  this.domNodeManager = new DomNodeManager({
6162
6311
  domLayer: this.domLayer,
6163
- onEditRequest: (id) => this.startEditingElement(id),
6312
+ onEditRequest: (id) => this.interactions.startEditingElement(id),
6164
6313
  isEditingElement: (id) => this.noteEditor.isEditing && this.noteEditor.editingElementId === id,
6165
6314
  getVersion: (id) => this.store.getVersion(id)
6166
6315
  });
@@ -6232,10 +6381,26 @@ var Viewport = class {
6232
6381
  this.toolContext.activeLayerId = this.layerManager.activeLayerId;
6233
6382
  this.requestRender();
6234
6383
  });
6235
- this.wrapper.addEventListener("pointerdown", this.onTapDown);
6236
- this.wrapper.addEventListener("pointerup", this.onDoubleTap);
6237
- this.wrapper.addEventListener("dragover", this.onDragOver);
6238
- this.wrapper.addEventListener("drop", this.onDrop);
6384
+ this.interactions = new ViewportInteractions({
6385
+ store: this.store,
6386
+ camera: this.camera,
6387
+ wrapper: this.wrapper,
6388
+ domLayer: this.domLayer,
6389
+ renderLoop: this.renderLoop,
6390
+ domNodeManager: this.domNodeManager,
6391
+ noteEditor: this.noteEditor,
6392
+ arrowLabelEditor: this.arrowLabelEditor,
6393
+ interactMode: this.interactMode,
6394
+ renderer: this.renderer,
6395
+ recorder: this.historyRecorder,
6396
+ requestRender: () => this.requestRender(),
6397
+ addImage: (src, position) => this.addImage(src, position),
6398
+ dropHandler: this.dropHandler
6399
+ });
6400
+ this.wrapper.addEventListener("pointerdown", this.interactions.onTapDown);
6401
+ this.wrapper.addEventListener("pointerup", this.interactions.onDoubleTap);
6402
+ this.wrapper.addEventListener("dragover", this.interactions.onDragOver);
6403
+ this.wrapper.addEventListener("drop", this.interactions.onDrop);
6239
6404
  this.observeResize();
6240
6405
  this.syncCanvasSize();
6241
6406
  this.renderLoop.start();
@@ -6271,9 +6436,7 @@ var Viewport = class {
6271
6436
  onHtmlElementMount;
6272
6437
  dropHandler;
6273
6438
  gridController;
6274
- doubleTapDetector = new DoubleTapDetector();
6275
- tapDownX = 0;
6276
- tapDownY = 0;
6439
+ interactions;
6277
6440
  contextMenu = null;
6278
6441
  get ctx() {
6279
6442
  return this.canvasEl.getContext("2d");
@@ -6534,10 +6697,10 @@ var Viewport = class {
6534
6697
  this.arrowLabelEditor.cancel();
6535
6698
  this.historyRecorder.destroy();
6536
6699
  this.contextMenu?.dispose();
6537
- this.wrapper.removeEventListener("pointerdown", this.onTapDown);
6538
- this.wrapper.removeEventListener("pointerup", this.onDoubleTap);
6539
- this.wrapper.removeEventListener("dragover", this.onDragOver);
6540
- this.wrapper.removeEventListener("drop", this.onDrop);
6700
+ this.wrapper.removeEventListener("pointerdown", this.interactions.onTapDown);
6701
+ this.wrapper.removeEventListener("pointerup", this.interactions.onDoubleTap);
6702
+ this.wrapper.removeEventListener("dragover", this.interactions.onDragOver);
6703
+ this.wrapper.removeEventListener("drop", this.interactions.onDrop);
6541
6704
  this.inputHandler.destroy();
6542
6705
  this.unsubCamera();
6543
6706
  this.unsubToolChange();
@@ -6546,148 +6709,9 @@ var Viewport = class {
6546
6709
  this.resizeObserver = null;
6547
6710
  this.wrapper.remove();
6548
6711
  }
6549
- startEditingElement(id) {
6550
- const element = this.store.getById(id);
6551
- if (!element || element.type !== "note" && element.type !== "text") return;
6552
- this.renderLoop.flush();
6553
- const node = this.domNodeManager.getNode(id);
6554
- if (node) {
6555
- this.noteEditor.startEditing(node, id, this.store);
6556
- }
6557
- }
6558
- fitNoteHeight(elementId) {
6559
- const element = this.store.getById(elementId);
6560
- if (!element || element.type !== "note") return;
6561
- if (isNoteContentEmpty(element.text)) return;
6562
- const node = this.domNodeManager.getNode(elementId);
6563
- if (!node) return;
6564
- const measured = node.scrollHeight;
6565
- if (measured > element.size.h) {
6566
- this.store.update(elementId, { size: { w: element.size.w, h: measured } });
6567
- }
6568
- }
6569
- onTextEditStop(elementId) {
6570
- const element = this.store.getById(elementId);
6571
- if (!element) return;
6572
- if (element.type === "note") {
6573
- if (isNoteContentEmpty(element.text)) {
6574
- this.store.remove(elementId);
6575
- return;
6576
- }
6577
- this.fitNoteHeight(elementId);
6578
- return;
6579
- }
6580
- if (element.type !== "text") return;
6581
- if (!element.text || element.text.trim() === "") {
6582
- this.store.remove(elementId);
6583
- return;
6584
- }
6585
- const node = this.domNodeManager.getNode(elementId);
6586
- if (node && "size" in element) {
6587
- const measured = node.scrollHeight;
6588
- if (measured !== element.size.h) {
6589
- this.store.update(elementId, { size: { w: element.size.w, h: measured } });
6590
- }
6591
- }
6592
- }
6593
- onTapDown = (e) => {
6594
- this.tapDownX = e.clientX;
6595
- this.tapDownY = e.clientY;
6596
- };
6597
- onDoubleTap = (e) => {
6598
- const dx = e.clientX - this.tapDownX;
6599
- const dy = e.clientY - this.tapDownY;
6600
- const moved = Math.sqrt(dx * dx + dy * dy);
6601
- if (moved > 10) return;
6602
- if (!this.doubleTapDetector.feed(e)) return;
6603
- if (typeof document.elementFromPoint !== "function") return;
6604
- const el = document.elementFromPoint(e.clientX, e.clientY);
6605
- const nodeEl = el?.closest("[data-element-id]");
6606
- if (nodeEl) {
6607
- const elementId = nodeEl.dataset["elementId"];
6608
- if (elementId) {
6609
- const element = this.store.getById(elementId);
6610
- if (element?.type === "note" || element?.type === "text") {
6611
- this.startEditingElement(elementId);
6612
- return;
6613
- }
6614
- }
6615
- }
6616
- const rect = this.wrapper.getBoundingClientRect();
6617
- const screen = { x: e.clientX - rect.left, y: e.clientY - rect.top };
6618
- const world = this.camera.screenToWorld(screen);
6619
- const hit = this.hitTestWorld(world);
6620
- if (hit?.type === "html") {
6621
- this.interactMode.startInteracting(hit.id);
6622
- return;
6623
- }
6624
- const arrow = this.findArrowAt(world);
6625
- if (arrow) {
6626
- this.startArrowLabelEdit(arrow);
6627
- }
6628
- };
6629
- findArrowAt(world) {
6630
- const candidates = this.store.queryPoint(world).reverse();
6631
- for (const el of candidates) {
6632
- if (el.type === "arrow" && isNearBezier(world, el.from, el.to, el.bend, ARROW_HIT_THRESHOLD)) {
6633
- return el;
6634
- }
6635
- }
6636
- return void 0;
6637
- }
6638
- startArrowLabelEdit(arrow) {
6639
- this.arrowLabelEditor.startEditing({
6640
- arrow,
6641
- layer: this.domLayer,
6642
- store: this.store,
6643
- recorder: this.historyRecorder,
6644
- onDone: () => {
6645
- this.renderer.setLabelEditingId(null);
6646
- this.requestRender();
6647
- }
6648
- });
6649
- this.renderer.setLabelEditingId(arrow.id);
6650
- }
6651
- hitTestWorld(world) {
6652
- const candidates = this.store.queryPoint(world).reverse();
6653
- for (const el of candidates) {
6654
- if (!("size" in el)) continue;
6655
- const { x, y } = el.position;
6656
- const { w, h } = el.size;
6657
- if (world.x >= x && world.x <= x + w && world.y >= y && world.y <= y + h) {
6658
- return el;
6659
- }
6660
- }
6661
- return null;
6662
- }
6663
6712
  stopInteracting() {
6664
6713
  this.interactMode.stopInteracting();
6665
6714
  }
6666
- onDragOver = (e) => {
6667
- e.preventDefault();
6668
- };
6669
- onDrop = (e) => {
6670
- e.preventDefault();
6671
- const rect = this.wrapper.getBoundingClientRect();
6672
- const screenPos = { x: e.clientX - rect.left, y: e.clientY - rect.top };
6673
- const worldPos = this.camera.screenToWorld(screenPos);
6674
- if (this.dropHandler) {
6675
- this.dropHandler(e, worldPos);
6676
- return;
6677
- }
6678
- const files = e.dataTransfer?.files;
6679
- if (!files) return;
6680
- for (const file of files) {
6681
- if (!file.type.startsWith("image/")) continue;
6682
- const reader = new FileReader();
6683
- reader.onload = () => {
6684
- const src = reader.result;
6685
- if (typeof src !== "string") return;
6686
- this.addImage(src, worldPos);
6687
- };
6688
- reader.readAsDataURL(file);
6689
- }
6690
- };
6691
6715
  unbindArrowsFrom(removedElement) {
6692
6716
  const boundArrows = findBoundArrows(removedElement.id, this.store);
6693
6717
  const bounds = getElementBounds(removedElement);
@@ -9044,7 +9068,7 @@ var TemplateTool = class {
9044
9068
  };
9045
9069
 
9046
9070
  // src/index.ts
9047
- var VERSION = "0.38.2";
9071
+ var VERSION = "0.38.3";
9048
9072
  // Annotate the CommonJS export names for ESM import in node:
9049
9073
  0 && (module.exports = {
9050
9074
  ArrowTool,