@fieldnotes/core 0.52.0 → 0.52.2

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
@@ -983,6 +983,12 @@ function getElementBounds(element) {
983
983
  }
984
984
  return null;
985
985
  }
986
+ function getElementVisualBounds(element) {
987
+ const bounds = getElementBounds(element);
988
+ if (!bounds) return null;
989
+ const rotation = element.rotation ?? 0;
990
+ return rotation === 0 ? bounds : rotatedAABB(bounds, rotation);
991
+ }
986
992
  function getArrowBoundsAnalytical(from, to, bend) {
987
993
  if (bend === 0) {
988
994
  const minX2 = Math.min(from.x, to.x);
@@ -2754,10 +2760,7 @@ var ElementStore = class {
2754
2760
  // Spatial index stores the rotation-expanded AABB so rotated elements remain
2755
2761
  // broad-phase hit-test/marquee candidates; precise tests run against local bounds.
2756
2762
  indexBounds(element) {
2757
- const bounds = getElementBounds(element);
2758
- if (!bounds) return null;
2759
- const angle = element.rotation ?? 0;
2760
- return angle === 0 ? bounds : rotatedAABB(bounds, angle);
2763
+ return getElementVisualBounds(element);
2761
2764
  }
2762
2765
  add(element, meta) {
2763
2766
  element = sanitizeRichText(element);
@@ -4921,6 +4924,57 @@ function createDomLayer() {
4921
4924
  });
4922
4925
  return el;
4923
4926
  }
4927
+ function createPaintStack() {
4928
+ const el = createDomLayer();
4929
+ el.dataset["fieldnotesPaintStack"] = "";
4930
+ el.style.transformOrigin = "";
4931
+ return el;
4932
+ }
4933
+
4934
+ // src/canvas/hybrid-render-surface.ts
4935
+ var HybridRenderSurface = class {
4936
+ constructor(root) {
4937
+ this.root = root;
4938
+ }
4939
+ canvases = /* @__PURE__ */ new Map();
4940
+ beginFrame(activeOrders, width, height) {
4941
+ for (const [order, canvas] of this.canvases) {
4942
+ if (!activeOrders.has(order)) {
4943
+ canvas.remove();
4944
+ this.canvases.delete(order);
4945
+ }
4946
+ }
4947
+ for (const order of activeOrders) {
4948
+ const canvas = this.getCanvas(order);
4949
+ if (canvas.width !== width) canvas.width = width;
4950
+ if (canvas.height !== height) canvas.height = height;
4951
+ canvas.style.zIndex = String(order);
4952
+ }
4953
+ }
4954
+ getContext(order) {
4955
+ return this.getCanvas(order).getContext("2d");
4956
+ }
4957
+ clear() {
4958
+ for (const canvas of this.canvases.values()) canvas.remove();
4959
+ this.canvases.clear();
4960
+ }
4961
+ getCanvas(order) {
4962
+ let canvas = this.canvases.get(order);
4963
+ if (canvas) return canvas;
4964
+ canvas = document.createElement("canvas");
4965
+ canvas.dataset["paintOrder"] = String(order);
4966
+ Object.assign(canvas.style, {
4967
+ position: "absolute",
4968
+ inset: "0",
4969
+ width: "100%",
4970
+ height: "100%",
4971
+ pointerEvents: "none"
4972
+ });
4973
+ this.root.appendChild(canvas);
4974
+ this.canvases.set(order, canvas);
4975
+ return canvas;
4976
+ }
4977
+ };
4924
4978
 
4925
4979
  // src/elements/arrow-label-editor.ts
4926
4980
  var ArrowLabelEditor = class {
@@ -6539,6 +6593,7 @@ var DoubleTapDetector = class {
6539
6593
  // src/canvas/dom-node-manager.ts
6540
6594
  var DomNodeManager = class {
6541
6595
  domNodes = /* @__PURE__ */ new Map();
6596
+ strata = /* @__PURE__ */ new Map();
6542
6597
  htmlContent = /* @__PURE__ */ new Map();
6543
6598
  domLayer;
6544
6599
  onEditRequest;
@@ -6547,6 +6602,7 @@ var DomNodeManager = class {
6547
6602
  lastSyncedVersion = /* @__PURE__ */ new Map();
6548
6603
  lastSyncedZIndex = /* @__PURE__ */ new Map();
6549
6604
  lastSyncedOpacity = /* @__PURE__ */ new Map();
6605
+ cameraTransform = "";
6550
6606
  constructor(deps) {
6551
6607
  this.domLayer = deps.domLayer;
6552
6608
  this.onEditRequest = deps.onEditRequest;
@@ -6556,6 +6612,11 @@ var DomNodeManager = class {
6556
6612
  getNode(id) {
6557
6613
  return this.domNodes.get(id);
6558
6614
  }
6615
+ setCameraTransform(transform) {
6616
+ if (transform === this.cameraTransform) return;
6617
+ this.cameraTransform = transform;
6618
+ for (const stratum of this.strata.values()) stratum.style.transform = transform;
6619
+ }
6559
6620
  storeHtmlContent(elementId, dom) {
6560
6621
  this.htmlContent.set(elementId, dom);
6561
6622
  }
@@ -6583,7 +6644,7 @@ var DomNodeManager = class {
6583
6644
  position: "absolute",
6584
6645
  pointerEvents: "auto"
6585
6646
  });
6586
- this.domLayer.appendChild(node);
6647
+ this.getStratum(zIndex).appendChild(node);
6587
6648
  this.domNodes.set(element.id, node);
6588
6649
  } else if (this.getVersion) {
6589
6650
  const currentVersion = this.getVersion(element.id);
@@ -6594,6 +6655,15 @@ var DomNodeManager = class {
6594
6655
  return;
6595
6656
  }
6596
6657
  }
6658
+ if (node.parentElement !== this.getStratum(zIndex)) {
6659
+ const previousStratum = node.parentElement;
6660
+ this.getStratum(zIndex).appendChild(node);
6661
+ if (previousStratum?.childElementCount === 0) {
6662
+ const previousOrder = Number(previousStratum.dataset["paintOrder"]);
6663
+ previousStratum.remove();
6664
+ this.strata.delete(previousOrder);
6665
+ }
6666
+ }
6597
6667
  if (this.getVersion) {
6598
6668
  this.lastSyncedVersion.set(element.id, this.getVersion(element.id));
6599
6669
  this.lastSyncedZIndex.set(element.id, zIndex);
@@ -6615,7 +6685,10 @@ var DomNodeManager = class {
6615
6685
  }
6616
6686
  hideDomNode(id) {
6617
6687
  const node = this.domNodes.get(id);
6618
- if (node) node.style.display = "none";
6688
+ if (node) {
6689
+ node.style.display = "none";
6690
+ this.lastSyncedVersion.delete(id);
6691
+ }
6619
6692
  }
6620
6693
  removeDomNode(id) {
6621
6694
  this.htmlContent.delete(id);
@@ -6624,8 +6697,14 @@ var DomNodeManager = class {
6624
6697
  this.lastSyncedOpacity.delete(id);
6625
6698
  const node = this.domNodes.get(id);
6626
6699
  if (node) {
6700
+ const stratum = node.parentElement;
6627
6701
  node.remove();
6628
6702
  this.domNodes.delete(id);
6703
+ if (stratum?.childElementCount === 0) {
6704
+ const order = Number(stratum.dataset["paintOrder"]);
6705
+ stratum.remove();
6706
+ this.strata.delete(order);
6707
+ }
6629
6708
  }
6630
6709
  }
6631
6710
  clearDomNodes() {
@@ -6635,6 +6714,27 @@ var DomNodeManager = class {
6635
6714
  this.lastSyncedVersion.clear();
6636
6715
  this.lastSyncedZIndex.clear();
6637
6716
  this.lastSyncedOpacity.clear();
6717
+ for (const stratum of this.strata.values()) stratum.remove();
6718
+ this.strata.clear();
6719
+ }
6720
+ getStratum(order) {
6721
+ let stratum = this.strata.get(order);
6722
+ if (stratum) return stratum;
6723
+ stratum = document.createElement("div");
6724
+ stratum.dataset["paintOrder"] = String(order);
6725
+ Object.assign(stratum.style, {
6726
+ position: "absolute",
6727
+ inset: "0",
6728
+ width: "100%",
6729
+ height: "100%",
6730
+ pointerEvents: "none",
6731
+ transformOrigin: "0 0",
6732
+ transform: this.cameraTransform,
6733
+ zIndex: String(order)
6734
+ });
6735
+ this.domLayer.appendChild(stratum);
6736
+ this.strata.set(order, stratum);
6737
+ return stratum;
6638
6738
  }
6639
6739
  reattachHtmlContent(store) {
6640
6740
  for (const el of store.getElementsByType("html")) {
@@ -6816,6 +6916,7 @@ var RenderLoop = class {
6816
6916
  domNodeManager;
6817
6917
  layerCache;
6818
6918
  marginViewport;
6919
+ hybridSurface;
6819
6920
  activeDrawingLayerId = null;
6820
6921
  gridCacheDirty = true;
6821
6922
  // set on recenter/viewport-change; consumed by the grid block
@@ -6835,6 +6936,7 @@ var RenderLoop = class {
6835
6936
  this.domNodeManager = deps.domNodeManager;
6836
6937
  this.layerCache = deps.layerCache;
6837
6938
  this.marginViewport = deps.marginViewport;
6939
+ this.hybridSurface = deps.hybridSurface;
6838
6940
  }
6839
6941
  requestRender() {
6840
6942
  this.needsRender = true;
@@ -6955,9 +7057,19 @@ var RenderLoop = class {
6955
7057
  h: cullBounds.h + cullPad * 2
6956
7058
  };
6957
7059
  const allElements = this.store.getAll();
7060
+ const visibleElements = allElements.filter(
7061
+ (element) => this.layerManager.isLayerVisible(element.layerId)
7062
+ );
7063
+ const firstDomIndex = visibleElements.findIndex(
7064
+ (element) => this.renderer.isDomElement(element)
7065
+ );
7066
+ const hybridActive = firstDomIndex >= 0 && visibleElements.slice(firstDomIndex + 1).some((element) => !this.renderer.isDomElement(element));
7067
+ const hybridCanvasRuns = /* @__PURE__ */ new Map();
7068
+ const hybridOrders = /* @__PURE__ */ new Set();
7069
+ let activeHybridOrder = null;
6958
7070
  this.layerGroups.clear();
6959
7071
  const gridElements = [];
6960
- let domZIndex = 0;
7072
+ let paintOrder = 0;
6961
7073
  for (const element of allElements) {
6962
7074
  if (!this.layerManager.isLayerVisible(element.layerId)) {
6963
7075
  if (this.renderer.isDomElement(element)) {
@@ -6965,14 +7077,27 @@ var RenderLoop = class {
6965
7077
  }
6966
7078
  continue;
6967
7079
  }
7080
+ const order = ++paintOrder;
6968
7081
  if (this.renderer.isDomElement(element)) {
7082
+ activeHybridOrder = null;
6969
7083
  const layerOpacity = this.layerManager.getLayer?.(element.layerId)?.opacity ?? 1;
6970
- const elBounds = getElementBounds(element);
7084
+ const elBounds = getElementVisualBounds(element);
6971
7085
  if (elBounds && !boundsIntersect(elBounds, cullingRect)) {
6972
7086
  this.domNodeManager.hideDomNode(element.id);
6973
7087
  } else {
6974
- this.domNodeManager.syncDomNode(element, domZIndex++, layerOpacity);
7088
+ this.domNodeManager.syncDomNode(element, order, layerOpacity);
7089
+ }
7090
+ continue;
7091
+ }
7092
+ if (hybridActive && paintOrder > firstDomIndex + 1 && element.type !== "grid") {
7093
+ activeHybridOrder ??= order;
7094
+ let run = hybridCanvasRuns.get(activeHybridOrder);
7095
+ if (!run) {
7096
+ run = [];
7097
+ hybridCanvasRuns.set(activeHybridOrder, run);
7098
+ hybridOrders.add(activeHybridOrder);
6975
7099
  }
7100
+ run.push(element);
6976
7101
  continue;
6977
7102
  }
6978
7103
  if (element.type === "grid") {
@@ -6986,6 +7111,10 @@ var RenderLoop = class {
6986
7111
  }
6987
7112
  group.push(element);
6988
7113
  }
7114
+ const activeTool = this.toolManager.activeTool;
7115
+ const overlayOrder = visibleElements.length + 1;
7116
+ if (hybridActive && activeTool?.renderOverlay) hybridOrders.add(overlayOrder);
7117
+ this.hybridSurface.beginFrame(hybridOrders, this.canvasEl.width, this.canvasEl.height);
6989
7118
  for (const [layerId, elements] of this.layerGroups) {
6990
7119
  const isActiveDrawingLayer = layerId === this.activeDrawingLayerId;
6991
7120
  const layerOpacity = this.layerManager.getLayer?.(layerId)?.opacity ?? 1;
@@ -7009,7 +7138,7 @@ var RenderLoop = class {
7009
7138
  offCtx.save();
7010
7139
  this.marginViewport.applyRenderTransform(offCtx);
7011
7140
  for (const element of elements) {
7012
- const elBounds = getElementBounds(element);
7141
+ const elBounds = getElementVisualBounds(element);
7013
7142
  if (elBounds && !boundsIntersect(elBounds, cullingRect)) continue;
7014
7143
  this.renderer.renderCanvasElement(offCtx, element);
7015
7144
  }
@@ -7075,9 +7204,37 @@ var RenderLoop = class {
7075
7204
  } else {
7076
7205
  this.lastGridRefs = [];
7077
7206
  }
7207
+ for (const [order, elements] of hybridCanvasRuns) {
7208
+ const hybridCtx = this.hybridSurface.getContext(order);
7209
+ if (!hybridCtx) continue;
7210
+ hybridCtx.clearRect(0, 0, this.canvasEl.width, this.canvasEl.height);
7211
+ hybridCtx.save();
7212
+ hybridCtx.scale(dpr, dpr);
7213
+ hybridCtx.translate(this.camera.position.x, this.camera.position.y);
7214
+ hybridCtx.scale(this.camera.zoom, this.camera.zoom);
7215
+ for (const element of elements) {
7216
+ const elBounds = getElementVisualBounds(element);
7217
+ if (elBounds && !boundsIntersect(elBounds, cullingRect)) continue;
7218
+ hybridCtx.save();
7219
+ hybridCtx.globalAlpha = this.layerManager.getLayer?.(element.layerId)?.opacity ?? 1;
7220
+ this.renderer.renderCanvasElement(hybridCtx, element);
7221
+ hybridCtx.restore();
7222
+ }
7223
+ hybridCtx.restore();
7224
+ }
7078
7225
  const overlayT0 = performance.now();
7079
- const activeTool = this.toolManager.activeTool;
7080
- if (activeTool?.renderOverlay) {
7226
+ if (hybridActive && activeTool?.renderOverlay) {
7227
+ const overlayCtx = this.hybridSurface.getContext(overlayOrder);
7228
+ if (overlayCtx) {
7229
+ overlayCtx.clearRect(0, 0, this.canvasEl.width, this.canvasEl.height);
7230
+ overlayCtx.save();
7231
+ overlayCtx.scale(dpr, dpr);
7232
+ overlayCtx.translate(this.camera.position.x, this.camera.position.y);
7233
+ overlayCtx.scale(this.camera.zoom, this.camera.zoom);
7234
+ activeTool.renderOverlay(overlayCtx);
7235
+ overlayCtx.restore();
7236
+ }
7237
+ } else if (activeTool?.renderOverlay) {
7081
7238
  activeTool.renderOverlay(ctx);
7082
7239
  }
7083
7240
  const overlayMs = performance.now() - overlayT0;
@@ -7828,8 +7985,11 @@ var Viewport = class {
7828
7985
  });
7829
7986
  this.wrapper = createWrapper();
7830
7987
  this.canvasEl = createCanvas();
7988
+ this.paintStack = createPaintStack();
7831
7989
  this.domLayer = createDomLayer();
7990
+ this.domLayer.style.zIndex = "2147483647";
7832
7991
  this.wrapper.appendChild(this.canvasEl);
7992
+ this.wrapper.appendChild(this.paintStack);
7833
7993
  this.wrapper.appendChild(this.domLayer);
7834
7994
  this.container.appendChild(this.wrapper);
7835
7995
  this.toolContext = {
@@ -7899,11 +8059,12 @@ var Viewport = class {
7899
8059
  this.minimap.scheduleDraw();
7900
8060
  }
7901
8061
  this.domNodeManager = new DomNodeManager({
7902
- domLayer: this.domLayer,
8062
+ domLayer: this.paintStack,
7903
8063
  onEditRequest: (id) => this.interactions.startEditingElement(id),
7904
8064
  isEditingElement: (id) => this.noteEditor.isEditing && this.noteEditor.editingElementId === id,
7905
8065
  getVersion: (id) => this.store.getVersion(id)
7906
8066
  });
8067
+ this.domNodeManager.setCameraTransform(this.camera.toCSSTransform());
7907
8068
  this.interactMode = new InteractMode({
7908
8069
  getNode: (id) => this.domNodeManager.getNode(id)
7909
8070
  });
@@ -7924,7 +8085,8 @@ var Viewport = class {
7924
8085
  layerManager: this.layerManager,
7925
8086
  domNodeManager: this.domNodeManager,
7926
8087
  layerCache,
7927
- marginViewport: this.marginViewport
8088
+ marginViewport: this.marginViewport,
8089
+ hybridSurface: new HybridRenderSurface(this.paintStack)
7928
8090
  });
7929
8091
  this.unsubCamera = this.camera.onChange(() => {
7930
8092
  this.applyCameraTransform();
@@ -8011,6 +8173,7 @@ var Viewport = class {
8011
8173
  history;
8012
8174
  domLayer;
8013
8175
  canvasEl;
8176
+ paintStack;
8014
8177
  wrapper;
8015
8178
  unsubCamera;
8016
8179
  unsubToolChange;
@@ -8366,7 +8529,9 @@ var Viewport = class {
8366
8529
  }
8367
8530
  }
8368
8531
  applyCameraTransform() {
8369
- this.domLayer.style.transform = this.camera.toCSSTransform();
8532
+ const transform = this.camera.toCSSTransform();
8533
+ this.domLayer.style.transform = transform;
8534
+ this.domNodeManager.setCameraTransform(transform);
8370
8535
  }
8371
8536
  syncCanvasSize() {
8372
8537
  const rect = this.container.getBoundingClientRect();
@@ -11047,7 +11212,7 @@ var LaserTool = class {
11047
11212
  };
11048
11213
 
11049
11214
  // src/index.ts
11050
- var VERSION = "0.52.0";
11215
+ var VERSION = "0.52.2";
11051
11216
  // Annotate the CommonJS export names for ESM import in node:
11052
11217
  0 && (module.exports = {
11053
11218
  ArrowTool,