@fieldnotes/core 0.52.0 → 0.52.1

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/README.md CHANGED
@@ -80,7 +80,8 @@ card.querySelector('button').addEventListener('click', () => {
80
80
  const elementId = viewport.addHtmlElement(card, { x: 100, y: 200 }, { w: 250, h: 150 });
81
81
  ```
82
82
 
83
- HTML elements pan, zoom, and resize with the canvas. They use a **two-mode interaction model**:
83
+ HTML elements pan, zoom, resize, and interleave by layer/`zIndex` with canvas-rendered content. They
84
+ use a **two-mode interaction model**:
84
85
 
85
86
  - **Default** — the element can be selected, dragged, and resized like any other element
86
87
  - **Double-click** — enters interact mode, making buttons, inputs, and links work
package/dist/index.cjs CHANGED
@@ -4921,6 +4921,57 @@ function createDomLayer() {
4921
4921
  });
4922
4922
  return el;
4923
4923
  }
4924
+ function createPaintStack() {
4925
+ const el = createDomLayer();
4926
+ el.dataset["fieldnotesPaintStack"] = "";
4927
+ el.style.transformOrigin = "";
4928
+ return el;
4929
+ }
4930
+
4931
+ // src/canvas/hybrid-render-surface.ts
4932
+ var HybridRenderSurface = class {
4933
+ constructor(root) {
4934
+ this.root = root;
4935
+ }
4936
+ canvases = /* @__PURE__ */ new Map();
4937
+ beginFrame(activeOrders, width, height) {
4938
+ for (const [order, canvas] of this.canvases) {
4939
+ if (!activeOrders.has(order)) {
4940
+ canvas.remove();
4941
+ this.canvases.delete(order);
4942
+ }
4943
+ }
4944
+ for (const order of activeOrders) {
4945
+ const canvas = this.getCanvas(order);
4946
+ if (canvas.width !== width) canvas.width = width;
4947
+ if (canvas.height !== height) canvas.height = height;
4948
+ canvas.style.zIndex = String(order);
4949
+ }
4950
+ }
4951
+ getContext(order) {
4952
+ return this.getCanvas(order).getContext("2d");
4953
+ }
4954
+ clear() {
4955
+ for (const canvas of this.canvases.values()) canvas.remove();
4956
+ this.canvases.clear();
4957
+ }
4958
+ getCanvas(order) {
4959
+ let canvas = this.canvases.get(order);
4960
+ if (canvas) return canvas;
4961
+ canvas = document.createElement("canvas");
4962
+ canvas.dataset["paintOrder"] = String(order);
4963
+ Object.assign(canvas.style, {
4964
+ position: "absolute",
4965
+ inset: "0",
4966
+ width: "100%",
4967
+ height: "100%",
4968
+ pointerEvents: "none"
4969
+ });
4970
+ this.root.appendChild(canvas);
4971
+ this.canvases.set(order, canvas);
4972
+ return canvas;
4973
+ }
4974
+ };
4924
4975
 
4925
4976
  // src/elements/arrow-label-editor.ts
4926
4977
  var ArrowLabelEditor = class {
@@ -6539,6 +6590,7 @@ var DoubleTapDetector = class {
6539
6590
  // src/canvas/dom-node-manager.ts
6540
6591
  var DomNodeManager = class {
6541
6592
  domNodes = /* @__PURE__ */ new Map();
6593
+ strata = /* @__PURE__ */ new Map();
6542
6594
  htmlContent = /* @__PURE__ */ new Map();
6543
6595
  domLayer;
6544
6596
  onEditRequest;
@@ -6547,6 +6599,7 @@ var DomNodeManager = class {
6547
6599
  lastSyncedVersion = /* @__PURE__ */ new Map();
6548
6600
  lastSyncedZIndex = /* @__PURE__ */ new Map();
6549
6601
  lastSyncedOpacity = /* @__PURE__ */ new Map();
6602
+ cameraTransform = "";
6550
6603
  constructor(deps) {
6551
6604
  this.domLayer = deps.domLayer;
6552
6605
  this.onEditRequest = deps.onEditRequest;
@@ -6556,6 +6609,11 @@ var DomNodeManager = class {
6556
6609
  getNode(id) {
6557
6610
  return this.domNodes.get(id);
6558
6611
  }
6612
+ setCameraTransform(transform) {
6613
+ if (transform === this.cameraTransform) return;
6614
+ this.cameraTransform = transform;
6615
+ for (const stratum of this.strata.values()) stratum.style.transform = transform;
6616
+ }
6559
6617
  storeHtmlContent(elementId, dom) {
6560
6618
  this.htmlContent.set(elementId, dom);
6561
6619
  }
@@ -6583,7 +6641,7 @@ var DomNodeManager = class {
6583
6641
  position: "absolute",
6584
6642
  pointerEvents: "auto"
6585
6643
  });
6586
- this.domLayer.appendChild(node);
6644
+ this.getStratum(zIndex).appendChild(node);
6587
6645
  this.domNodes.set(element.id, node);
6588
6646
  } else if (this.getVersion) {
6589
6647
  const currentVersion = this.getVersion(element.id);
@@ -6594,6 +6652,15 @@ var DomNodeManager = class {
6594
6652
  return;
6595
6653
  }
6596
6654
  }
6655
+ if (node.parentElement !== this.getStratum(zIndex)) {
6656
+ const previousStratum = node.parentElement;
6657
+ this.getStratum(zIndex).appendChild(node);
6658
+ if (previousStratum?.childElementCount === 0) {
6659
+ const previousOrder = Number(previousStratum.dataset["paintOrder"]);
6660
+ previousStratum.remove();
6661
+ this.strata.delete(previousOrder);
6662
+ }
6663
+ }
6597
6664
  if (this.getVersion) {
6598
6665
  this.lastSyncedVersion.set(element.id, this.getVersion(element.id));
6599
6666
  this.lastSyncedZIndex.set(element.id, zIndex);
@@ -6615,7 +6682,10 @@ var DomNodeManager = class {
6615
6682
  }
6616
6683
  hideDomNode(id) {
6617
6684
  const node = this.domNodes.get(id);
6618
- if (node) node.style.display = "none";
6685
+ if (node) {
6686
+ node.style.display = "none";
6687
+ this.lastSyncedVersion.delete(id);
6688
+ }
6619
6689
  }
6620
6690
  removeDomNode(id) {
6621
6691
  this.htmlContent.delete(id);
@@ -6624,8 +6694,14 @@ var DomNodeManager = class {
6624
6694
  this.lastSyncedOpacity.delete(id);
6625
6695
  const node = this.domNodes.get(id);
6626
6696
  if (node) {
6697
+ const stratum = node.parentElement;
6627
6698
  node.remove();
6628
6699
  this.domNodes.delete(id);
6700
+ if (stratum?.childElementCount === 0) {
6701
+ const order = Number(stratum.dataset["paintOrder"]);
6702
+ stratum.remove();
6703
+ this.strata.delete(order);
6704
+ }
6629
6705
  }
6630
6706
  }
6631
6707
  clearDomNodes() {
@@ -6635,6 +6711,27 @@ var DomNodeManager = class {
6635
6711
  this.lastSyncedVersion.clear();
6636
6712
  this.lastSyncedZIndex.clear();
6637
6713
  this.lastSyncedOpacity.clear();
6714
+ for (const stratum of this.strata.values()) stratum.remove();
6715
+ this.strata.clear();
6716
+ }
6717
+ getStratum(order) {
6718
+ let stratum = this.strata.get(order);
6719
+ if (stratum) return stratum;
6720
+ stratum = document.createElement("div");
6721
+ stratum.dataset["paintOrder"] = String(order);
6722
+ Object.assign(stratum.style, {
6723
+ position: "absolute",
6724
+ inset: "0",
6725
+ width: "100%",
6726
+ height: "100%",
6727
+ pointerEvents: "none",
6728
+ transformOrigin: "0 0",
6729
+ transform: this.cameraTransform,
6730
+ zIndex: String(order)
6731
+ });
6732
+ this.domLayer.appendChild(stratum);
6733
+ this.strata.set(order, stratum);
6734
+ return stratum;
6638
6735
  }
6639
6736
  reattachHtmlContent(store) {
6640
6737
  for (const el of store.getElementsByType("html")) {
@@ -6816,6 +6913,7 @@ var RenderLoop = class {
6816
6913
  domNodeManager;
6817
6914
  layerCache;
6818
6915
  marginViewport;
6916
+ hybridSurface;
6819
6917
  activeDrawingLayerId = null;
6820
6918
  gridCacheDirty = true;
6821
6919
  // set on recenter/viewport-change; consumed by the grid block
@@ -6835,6 +6933,7 @@ var RenderLoop = class {
6835
6933
  this.domNodeManager = deps.domNodeManager;
6836
6934
  this.layerCache = deps.layerCache;
6837
6935
  this.marginViewport = deps.marginViewport;
6936
+ this.hybridSurface = deps.hybridSurface;
6838
6937
  }
6839
6938
  requestRender() {
6840
6939
  this.needsRender = true;
@@ -6955,9 +7054,19 @@ var RenderLoop = class {
6955
7054
  h: cullBounds.h + cullPad * 2
6956
7055
  };
6957
7056
  const allElements = this.store.getAll();
7057
+ const visibleElements = allElements.filter(
7058
+ (element) => this.layerManager.isLayerVisible(element.layerId)
7059
+ );
7060
+ const firstDomIndex = visibleElements.findIndex(
7061
+ (element) => this.renderer.isDomElement(element)
7062
+ );
7063
+ const hybridActive = firstDomIndex >= 0 && visibleElements.slice(firstDomIndex + 1).some((element) => !this.renderer.isDomElement(element));
7064
+ const hybridCanvasRuns = /* @__PURE__ */ new Map();
7065
+ const hybridOrders = /* @__PURE__ */ new Set();
7066
+ let activeHybridOrder = null;
6958
7067
  this.layerGroups.clear();
6959
7068
  const gridElements = [];
6960
- let domZIndex = 0;
7069
+ let paintOrder = 0;
6961
7070
  for (const element of allElements) {
6962
7071
  if (!this.layerManager.isLayerVisible(element.layerId)) {
6963
7072
  if (this.renderer.isDomElement(element)) {
@@ -6965,16 +7074,29 @@ var RenderLoop = class {
6965
7074
  }
6966
7075
  continue;
6967
7076
  }
7077
+ const order = ++paintOrder;
6968
7078
  if (this.renderer.isDomElement(element)) {
7079
+ activeHybridOrder = null;
6969
7080
  const layerOpacity = this.layerManager.getLayer?.(element.layerId)?.opacity ?? 1;
6970
7081
  const elBounds = getElementBounds(element);
6971
7082
  if (elBounds && !boundsIntersect(elBounds, cullingRect)) {
6972
7083
  this.domNodeManager.hideDomNode(element.id);
6973
7084
  } else {
6974
- this.domNodeManager.syncDomNode(element, domZIndex++, layerOpacity);
7085
+ this.domNodeManager.syncDomNode(element, order, layerOpacity);
6975
7086
  }
6976
7087
  continue;
6977
7088
  }
7089
+ if (hybridActive && paintOrder > firstDomIndex + 1 && element.type !== "grid") {
7090
+ activeHybridOrder ??= order;
7091
+ let run = hybridCanvasRuns.get(activeHybridOrder);
7092
+ if (!run) {
7093
+ run = [];
7094
+ hybridCanvasRuns.set(activeHybridOrder, run);
7095
+ hybridOrders.add(activeHybridOrder);
7096
+ }
7097
+ run.push(element);
7098
+ continue;
7099
+ }
6978
7100
  if (element.type === "grid") {
6979
7101
  gridElements.push(element);
6980
7102
  continue;
@@ -6986,6 +7108,10 @@ var RenderLoop = class {
6986
7108
  }
6987
7109
  group.push(element);
6988
7110
  }
7111
+ const activeTool = this.toolManager.activeTool;
7112
+ const overlayOrder = visibleElements.length + 1;
7113
+ if (hybridActive && activeTool?.renderOverlay) hybridOrders.add(overlayOrder);
7114
+ this.hybridSurface.beginFrame(hybridOrders, this.canvasEl.width, this.canvasEl.height);
6989
7115
  for (const [layerId, elements] of this.layerGroups) {
6990
7116
  const isActiveDrawingLayer = layerId === this.activeDrawingLayerId;
6991
7117
  const layerOpacity = this.layerManager.getLayer?.(layerId)?.opacity ?? 1;
@@ -7075,9 +7201,37 @@ var RenderLoop = class {
7075
7201
  } else {
7076
7202
  this.lastGridRefs = [];
7077
7203
  }
7204
+ for (const [order, elements] of hybridCanvasRuns) {
7205
+ const hybridCtx = this.hybridSurface.getContext(order);
7206
+ if (!hybridCtx) continue;
7207
+ hybridCtx.clearRect(0, 0, this.canvasEl.width, this.canvasEl.height);
7208
+ hybridCtx.save();
7209
+ hybridCtx.scale(dpr, dpr);
7210
+ hybridCtx.translate(this.camera.position.x, this.camera.position.y);
7211
+ hybridCtx.scale(this.camera.zoom, this.camera.zoom);
7212
+ for (const element of elements) {
7213
+ const elBounds = getElementBounds(element);
7214
+ if (elBounds && !boundsIntersect(elBounds, cullingRect)) continue;
7215
+ hybridCtx.save();
7216
+ hybridCtx.globalAlpha = this.layerManager.getLayer?.(element.layerId)?.opacity ?? 1;
7217
+ this.renderer.renderCanvasElement(hybridCtx, element);
7218
+ hybridCtx.restore();
7219
+ }
7220
+ hybridCtx.restore();
7221
+ }
7078
7222
  const overlayT0 = performance.now();
7079
- const activeTool = this.toolManager.activeTool;
7080
- if (activeTool?.renderOverlay) {
7223
+ if (hybridActive && activeTool?.renderOverlay) {
7224
+ const overlayCtx = this.hybridSurface.getContext(overlayOrder);
7225
+ if (overlayCtx) {
7226
+ overlayCtx.clearRect(0, 0, this.canvasEl.width, this.canvasEl.height);
7227
+ overlayCtx.save();
7228
+ overlayCtx.scale(dpr, dpr);
7229
+ overlayCtx.translate(this.camera.position.x, this.camera.position.y);
7230
+ overlayCtx.scale(this.camera.zoom, this.camera.zoom);
7231
+ activeTool.renderOverlay(overlayCtx);
7232
+ overlayCtx.restore();
7233
+ }
7234
+ } else if (activeTool?.renderOverlay) {
7081
7235
  activeTool.renderOverlay(ctx);
7082
7236
  }
7083
7237
  const overlayMs = performance.now() - overlayT0;
@@ -7828,8 +7982,11 @@ var Viewport = class {
7828
7982
  });
7829
7983
  this.wrapper = createWrapper();
7830
7984
  this.canvasEl = createCanvas();
7985
+ this.paintStack = createPaintStack();
7831
7986
  this.domLayer = createDomLayer();
7987
+ this.domLayer.style.zIndex = "2147483647";
7832
7988
  this.wrapper.appendChild(this.canvasEl);
7989
+ this.wrapper.appendChild(this.paintStack);
7833
7990
  this.wrapper.appendChild(this.domLayer);
7834
7991
  this.container.appendChild(this.wrapper);
7835
7992
  this.toolContext = {
@@ -7899,11 +8056,12 @@ var Viewport = class {
7899
8056
  this.minimap.scheduleDraw();
7900
8057
  }
7901
8058
  this.domNodeManager = new DomNodeManager({
7902
- domLayer: this.domLayer,
8059
+ domLayer: this.paintStack,
7903
8060
  onEditRequest: (id) => this.interactions.startEditingElement(id),
7904
8061
  isEditingElement: (id) => this.noteEditor.isEditing && this.noteEditor.editingElementId === id,
7905
8062
  getVersion: (id) => this.store.getVersion(id)
7906
8063
  });
8064
+ this.domNodeManager.setCameraTransform(this.camera.toCSSTransform());
7907
8065
  this.interactMode = new InteractMode({
7908
8066
  getNode: (id) => this.domNodeManager.getNode(id)
7909
8067
  });
@@ -7924,7 +8082,8 @@ var Viewport = class {
7924
8082
  layerManager: this.layerManager,
7925
8083
  domNodeManager: this.domNodeManager,
7926
8084
  layerCache,
7927
- marginViewport: this.marginViewport
8085
+ marginViewport: this.marginViewport,
8086
+ hybridSurface: new HybridRenderSurface(this.paintStack)
7928
8087
  });
7929
8088
  this.unsubCamera = this.camera.onChange(() => {
7930
8089
  this.applyCameraTransform();
@@ -8011,6 +8170,7 @@ var Viewport = class {
8011
8170
  history;
8012
8171
  domLayer;
8013
8172
  canvasEl;
8173
+ paintStack;
8014
8174
  wrapper;
8015
8175
  unsubCamera;
8016
8176
  unsubToolChange;
@@ -8366,7 +8526,9 @@ var Viewport = class {
8366
8526
  }
8367
8527
  }
8368
8528
  applyCameraTransform() {
8369
- this.domLayer.style.transform = this.camera.toCSSTransform();
8529
+ const transform = this.camera.toCSSTransform();
8530
+ this.domLayer.style.transform = transform;
8531
+ this.domNodeManager.setCameraTransform(transform);
8370
8532
  }
8371
8533
  syncCanvasSize() {
8372
8534
  const rect = this.container.getBoundingClientRect();
@@ -11047,7 +11209,7 @@ var LaserTool = class {
11047
11209
  };
11048
11210
 
11049
11211
  // src/index.ts
11050
- var VERSION = "0.52.0";
11212
+ var VERSION = "0.52.1";
11051
11213
  // Annotate the CommonJS export names for ESM import in node:
11052
11214
  0 && (module.exports = {
11053
11215
  ArrowTool,