@fieldnotes/core 0.38.2 → 0.38.4

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
@@ -2257,7 +2257,12 @@ var ElementStore = class {
2257
2257
  if (!existing) return;
2258
2258
  this.sortedCache = null;
2259
2259
  this._versions.set(id, (this._versions.get(id) ?? 0) + 1);
2260
- const updated = { ...existing, ...partial, id: existing.id, type: existing.type };
2260
+ const updated = {
2261
+ ...existing,
2262
+ ...partial,
2263
+ id: existing.id,
2264
+ type: existing.type
2265
+ };
2261
2266
  if (updated.type === "stroke" && existing.type === "stroke") {
2262
2267
  transferStrokeRenderData(existing, updated);
2263
2268
  transferStrokeBounds(existing, updated);
@@ -6059,9 +6064,158 @@ var GridController = class {
6059
6064
  }
6060
6065
  };
6061
6066
 
6067
+ // src/canvas/viewport-interactions.ts
6068
+ var ARROW_HIT_THRESHOLD = 10;
6069
+ var ViewportInteractions = class {
6070
+ constructor(deps) {
6071
+ this.deps = deps;
6072
+ }
6073
+ doubleTapDetector = new DoubleTapDetector();
6074
+ tapDownX = 0;
6075
+ tapDownY = 0;
6076
+ startEditingElement(id) {
6077
+ const element = this.deps.store.getById(id);
6078
+ if (!element || element.type !== "note" && element.type !== "text") return;
6079
+ this.deps.renderLoop.flush();
6080
+ const node = this.deps.domNodeManager.getNode(id);
6081
+ if (node) {
6082
+ this.deps.noteEditor.startEditing(node, id, this.deps.store);
6083
+ }
6084
+ }
6085
+ fitNoteHeight(elementId) {
6086
+ const element = this.deps.store.getById(elementId);
6087
+ if (!element || element.type !== "note") return;
6088
+ if (isNoteContentEmpty(element.text)) return;
6089
+ const node = this.deps.domNodeManager.getNode(elementId);
6090
+ if (!node) return;
6091
+ const measured = node.scrollHeight;
6092
+ if (measured > element.size.h) {
6093
+ this.deps.store.update(elementId, { size: { w: element.size.w, h: measured } });
6094
+ }
6095
+ }
6096
+ onTextEditStop(elementId) {
6097
+ const element = this.deps.store.getById(elementId);
6098
+ if (!element) return;
6099
+ if (element.type === "note") {
6100
+ if (isNoteContentEmpty(element.text)) {
6101
+ this.deps.store.remove(elementId);
6102
+ return;
6103
+ }
6104
+ this.fitNoteHeight(elementId);
6105
+ return;
6106
+ }
6107
+ if (element.type !== "text") return;
6108
+ if (!element.text || element.text.trim() === "") {
6109
+ this.deps.store.remove(elementId);
6110
+ return;
6111
+ }
6112
+ const node = this.deps.domNodeManager.getNode(elementId);
6113
+ if (node && "size" in element) {
6114
+ const measured = node.scrollHeight;
6115
+ if (measured !== element.size.h) {
6116
+ this.deps.store.update(elementId, { size: { w: element.size.w, h: measured } });
6117
+ }
6118
+ }
6119
+ }
6120
+ onTapDown = (e) => {
6121
+ this.tapDownX = e.clientX;
6122
+ this.tapDownY = e.clientY;
6123
+ };
6124
+ onDoubleTap = (e) => {
6125
+ const dx = e.clientX - this.tapDownX;
6126
+ const dy = e.clientY - this.tapDownY;
6127
+ const moved = Math.sqrt(dx * dx + dy * dy);
6128
+ if (moved > 10) return;
6129
+ if (!this.doubleTapDetector.feed(e)) return;
6130
+ if (typeof document.elementFromPoint !== "function") return;
6131
+ const el = document.elementFromPoint(e.clientX, e.clientY);
6132
+ const nodeEl = el?.closest("[data-element-id]");
6133
+ if (nodeEl) {
6134
+ const elementId = nodeEl.dataset["elementId"];
6135
+ if (elementId) {
6136
+ const element = this.deps.store.getById(elementId);
6137
+ if (element?.type === "note" || element?.type === "text") {
6138
+ this.startEditingElement(elementId);
6139
+ return;
6140
+ }
6141
+ }
6142
+ }
6143
+ const rect = this.deps.wrapper.getBoundingClientRect();
6144
+ const screen = { x: e.clientX - rect.left, y: e.clientY - rect.top };
6145
+ const world = this.deps.camera.screenToWorld(screen);
6146
+ const hit = this.hitTestWorld(world);
6147
+ if (hit?.type === "html") {
6148
+ this.deps.interactMode.startInteracting(hit.id);
6149
+ return;
6150
+ }
6151
+ const arrow = this.findArrowAt(world);
6152
+ if (arrow) {
6153
+ this.startArrowLabelEdit(arrow);
6154
+ }
6155
+ };
6156
+ findArrowAt(world) {
6157
+ const candidates = this.deps.store.queryPoint(world).reverse();
6158
+ for (const el of candidates) {
6159
+ if (el.type === "arrow" && isNearBezier(world, el.from, el.to, el.bend, ARROW_HIT_THRESHOLD)) {
6160
+ return el;
6161
+ }
6162
+ }
6163
+ return void 0;
6164
+ }
6165
+ startArrowLabelEdit(arrow) {
6166
+ this.deps.arrowLabelEditor.startEditing({
6167
+ arrow,
6168
+ layer: this.deps.domLayer,
6169
+ store: this.deps.store,
6170
+ recorder: this.deps.recorder,
6171
+ onDone: () => {
6172
+ this.deps.renderer.setLabelEditingId(null);
6173
+ this.deps.requestRender();
6174
+ }
6175
+ });
6176
+ this.deps.renderer.setLabelEditingId(arrow.id);
6177
+ }
6178
+ hitTestWorld(world) {
6179
+ const candidates = this.deps.store.queryPoint(world).reverse();
6180
+ for (const el of candidates) {
6181
+ if (!("size" in el)) continue;
6182
+ const { x, y } = el.position;
6183
+ const { w, h } = el.size;
6184
+ if (world.x >= x && world.x <= x + w && world.y >= y && world.y <= y + h) {
6185
+ return el;
6186
+ }
6187
+ }
6188
+ return null;
6189
+ }
6190
+ onDragOver = (e) => {
6191
+ e.preventDefault();
6192
+ };
6193
+ onDrop = (e) => {
6194
+ e.preventDefault();
6195
+ const rect = this.deps.wrapper.getBoundingClientRect();
6196
+ const screenPos = { x: e.clientX - rect.left, y: e.clientY - rect.top };
6197
+ const worldPos = this.deps.camera.screenToWorld(screenPos);
6198
+ if (this.deps.dropHandler) {
6199
+ this.deps.dropHandler(e, worldPos);
6200
+ return;
6201
+ }
6202
+ const files = e.dataTransfer?.files;
6203
+ if (!files) return;
6204
+ for (const file of files) {
6205
+ if (!file.type.startsWith("image/")) continue;
6206
+ const reader = new FileReader();
6207
+ reader.onload = () => {
6208
+ const src = reader.result;
6209
+ if (typeof src !== "string") return;
6210
+ this.deps.addImage(src, worldPos);
6211
+ };
6212
+ reader.readAsDataURL(file);
6213
+ }
6214
+ };
6215
+ };
6216
+
6062
6217
  // src/canvas/viewport.ts
6063
6218
  var EMPTY_IDS = [];
6064
- var ARROW_HIT_THRESHOLD = 10;
6065
6219
  function noop() {
6066
6220
  }
6067
6221
  var Viewport = class {
@@ -6096,7 +6250,7 @@ var Viewport = class {
6096
6250
  toolbar: options.toolbar,
6097
6251
  placeholder: options.placeholder
6098
6252
  });
6099
- this.noteEditor.setOnStop((id) => this.onTextEditStop(id));
6253
+ this.noteEditor.setOnStop((id) => this.interactions.onTextEditStop(id));
6100
6254
  this.arrowLabelEditor = new ArrowLabelEditor();
6101
6255
  this.noteEditor.setHistoryHooks(
6102
6256
  () => this.historyRecorder.begin(),
@@ -6123,8 +6277,8 @@ var Viewport = class {
6123
6277
  store: this.store,
6124
6278
  requestRender: () => this.requestRender(),
6125
6279
  switchTool: (name) => this.toolManager.setTool(name, this.toolContext),
6126
- editElement: (id) => this.startEditingElement(id),
6127
- fitNoteHeight: (id) => this.fitNoteHeight(id),
6280
+ editElement: (id) => this.interactions.startEditingElement(id),
6281
+ fitNoteHeight: (id) => this.interactions.fitNoteHeight(id),
6128
6282
  setCursor: (cursor) => {
6129
6283
  this.wrapper.style.cursor = cursor;
6130
6284
  },
@@ -6160,7 +6314,7 @@ var Viewport = class {
6160
6314
  this.unsubToolChange = this.toolManager.onChange(() => this.contextMenu?.close());
6161
6315
  this.domNodeManager = new DomNodeManager({
6162
6316
  domLayer: this.domLayer,
6163
- onEditRequest: (id) => this.startEditingElement(id),
6317
+ onEditRequest: (id) => this.interactions.startEditingElement(id),
6164
6318
  isEditingElement: (id) => this.noteEditor.isEditing && this.noteEditor.editingElementId === id,
6165
6319
  getVersion: (id) => this.store.getVersion(id)
6166
6320
  });
@@ -6232,10 +6386,26 @@ var Viewport = class {
6232
6386
  this.toolContext.activeLayerId = this.layerManager.activeLayerId;
6233
6387
  this.requestRender();
6234
6388
  });
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);
6389
+ this.interactions = new ViewportInteractions({
6390
+ store: this.store,
6391
+ camera: this.camera,
6392
+ wrapper: this.wrapper,
6393
+ domLayer: this.domLayer,
6394
+ renderLoop: this.renderLoop,
6395
+ domNodeManager: this.domNodeManager,
6396
+ noteEditor: this.noteEditor,
6397
+ arrowLabelEditor: this.arrowLabelEditor,
6398
+ interactMode: this.interactMode,
6399
+ renderer: this.renderer,
6400
+ recorder: this.historyRecorder,
6401
+ requestRender: () => this.requestRender(),
6402
+ addImage: (src, position) => this.addImage(src, position),
6403
+ dropHandler: this.dropHandler
6404
+ });
6405
+ this.wrapper.addEventListener("pointerdown", this.interactions.onTapDown);
6406
+ this.wrapper.addEventListener("pointerup", this.interactions.onDoubleTap);
6407
+ this.wrapper.addEventListener("dragover", this.interactions.onDragOver);
6408
+ this.wrapper.addEventListener("drop", this.interactions.onDrop);
6239
6409
  this.observeResize();
6240
6410
  this.syncCanvasSize();
6241
6411
  this.renderLoop.start();
@@ -6271,9 +6441,7 @@ var Viewport = class {
6271
6441
  onHtmlElementMount;
6272
6442
  dropHandler;
6273
6443
  gridController;
6274
- doubleTapDetector = new DoubleTapDetector();
6275
- tapDownX = 0;
6276
- tapDownY = 0;
6444
+ interactions;
6277
6445
  contextMenu = null;
6278
6446
  get ctx() {
6279
6447
  return this.canvasEl.getContext("2d");
@@ -6534,10 +6702,10 @@ var Viewport = class {
6534
6702
  this.arrowLabelEditor.cancel();
6535
6703
  this.historyRecorder.destroy();
6536
6704
  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);
6705
+ this.wrapper.removeEventListener("pointerdown", this.interactions.onTapDown);
6706
+ this.wrapper.removeEventListener("pointerup", this.interactions.onDoubleTap);
6707
+ this.wrapper.removeEventListener("dragover", this.interactions.onDragOver);
6708
+ this.wrapper.removeEventListener("drop", this.interactions.onDrop);
6541
6709
  this.inputHandler.destroy();
6542
6710
  this.unsubCamera();
6543
6711
  this.unsubToolChange();
@@ -6546,148 +6714,9 @@ var Viewport = class {
6546
6714
  this.resizeObserver = null;
6547
6715
  this.wrapper.remove();
6548
6716
  }
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
6717
  stopInteracting() {
6664
6718
  this.interactMode.stopInteracting();
6665
6719
  }
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
6720
  unbindArrowsFrom(removedElement) {
6692
6721
  const boundArrows = findBoundArrows(removedElement.id, this.store);
6693
6722
  const bounds = getElementBounds(removedElement);
@@ -9044,7 +9073,7 @@ var TemplateTool = class {
9044
9073
  };
9045
9074
 
9046
9075
  // src/index.ts
9047
- var VERSION = "0.38.2";
9076
+ var VERSION = "0.38.4";
9048
9077
  // Annotate the CommonJS export names for ESM import in node:
9049
9078
  0 && (module.exports = {
9050
9079
  ArrowTool,