@fieldnotes/core 0.5.0 → 0.6.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/dist/index.cjs CHANGED
@@ -26,6 +26,7 @@ __export(index_exports, {
26
26
  Background: () => Background,
27
27
  BatchCommand: () => BatchCommand,
28
28
  Camera: () => Camera,
29
+ CreateLayerCommand: () => CreateLayerCommand,
29
30
  ElementRenderer: () => ElementRenderer,
30
31
  ElementStore: () => ElementStore,
31
32
  EraserTool: () => EraserTool,
@@ -35,15 +36,18 @@ __export(index_exports, {
35
36
  HistoryStack: () => HistoryStack,
36
37
  ImageTool: () => ImageTool,
37
38
  InputHandler: () => InputHandler,
39
+ LayerManager: () => LayerManager,
38
40
  NoteEditor: () => NoteEditor,
39
41
  NoteTool: () => NoteTool,
40
42
  PencilTool: () => PencilTool,
41
43
  RemoveElementCommand: () => RemoveElementCommand,
44
+ RemoveLayerCommand: () => RemoveLayerCommand,
42
45
  SelectTool: () => SelectTool,
43
46
  ShapeTool: () => ShapeTool,
44
47
  TextTool: () => TextTool,
45
48
  ToolManager: () => ToolManager,
46
49
  UpdateElementCommand: () => UpdateElementCommand,
50
+ UpdateLayerCommand: () => UpdateLayerCommand,
47
51
  VERSION: () => VERSION,
48
52
  Viewport: () => Viewport,
49
53
  clearStaleBindings: () => clearStaleBindings,
@@ -100,15 +104,16 @@ var EventBus = class {
100
104
  };
101
105
 
102
106
  // src/core/state-serializer.ts
103
- var CURRENT_VERSION = 1;
104
- function exportState(elements, camera) {
107
+ var CURRENT_VERSION = 2;
108
+ function exportState(elements, camera, layers = []) {
105
109
  return {
106
110
  version: CURRENT_VERSION,
107
111
  camera: {
108
112
  position: { ...camera.position },
109
113
  zoom: camera.zoom
110
114
  },
111
- elements: elements.map((el) => structuredClone(el))
115
+ elements: elements.map((el) => structuredClone(el)),
116
+ layers: layers.map((l) => ({ ...l }))
112
117
  };
113
118
  }
114
119
  function parseState(json) {
@@ -146,6 +151,19 @@ function validateState(data) {
146
151
  migrateElement(el);
147
152
  }
148
153
  cleanBindings(obj["elements"]);
154
+ const layers = obj["layers"];
155
+ if (!Array.isArray(layers) || layers.length === 0) {
156
+ obj["layers"] = [
157
+ {
158
+ id: "default-layer",
159
+ name: "Layer 1",
160
+ visible: true,
161
+ locked: false,
162
+ order: 0,
163
+ opacity: 1
164
+ }
165
+ ];
166
+ }
149
167
  }
150
168
  var VALID_TYPES = /* @__PURE__ */ new Set(["stroke", "note", "arrow", "image", "html", "text", "shape"]);
151
169
  function validateElement(el) {
@@ -178,6 +196,9 @@ function cleanBindings(elements) {
178
196
  }
179
197
  }
180
198
  function migrateElement(obj) {
199
+ if (typeof obj["layerId"] !== "string") {
200
+ obj["layerId"] = "default-layer";
201
+ }
181
202
  if (obj["type"] === "arrow" && typeof obj["bend"] !== "number") {
182
203
  obj["bend"] = 0;
183
204
  }
@@ -213,9 +234,11 @@ var AutoSave = class {
213
234
  this.camera = camera;
214
235
  this.key = options.key ?? DEFAULT_KEY;
215
236
  this.debounceMs = options.debounceMs ?? DEFAULT_DEBOUNCE_MS;
237
+ this.layerManager = options.layerManager;
216
238
  }
217
239
  key;
218
240
  debounceMs;
241
+ layerManager;
219
242
  timerId = null;
220
243
  unsubscribers = [];
221
244
  start() {
@@ -226,6 +249,9 @@ var AutoSave = class {
226
249
  this.store.on("update", schedule),
227
250
  this.camera.onChange(schedule)
228
251
  ];
252
+ if (this.layerManager) {
253
+ this.unsubscribers.push(this.layerManager.on("change", schedule));
254
+ }
229
255
  }
230
256
  stop() {
231
257
  this.cancelPending();
@@ -258,8 +284,13 @@ var AutoSave = class {
258
284
  }
259
285
  save() {
260
286
  if (typeof localStorage === "undefined") return;
261
- const state = exportState(this.store.snapshot(), this.camera);
262
- localStorage.setItem(this.key, JSON.stringify(state));
287
+ const layers = this.layerManager?.snapshot() ?? [];
288
+ const state = exportState(this.store.snapshot(), this.camera, layers);
289
+ try {
290
+ localStorage.setItem(this.key, JSON.stringify(state));
291
+ } catch {
292
+ console.warn("Auto-save failed: storage quota exceeded. State too large for localStorage.");
293
+ }
263
294
  }
264
295
  };
265
296
 
@@ -635,11 +666,20 @@ var InputHandler = class {
635
666
  var ElementStore = class {
636
667
  elements = /* @__PURE__ */ new Map();
637
668
  bus = new EventBus();
669
+ layerOrderMap = /* @__PURE__ */ new Map();
638
670
  get count() {
639
671
  return this.elements.size;
640
672
  }
673
+ setLayerOrder(order) {
674
+ this.layerOrderMap = new Map(order);
675
+ }
641
676
  getAll() {
642
- return [...this.elements.values()].sort((a, b) => a.zIndex - b.zIndex);
677
+ return [...this.elements.values()].sort((a, b) => {
678
+ const layerA = this.layerOrderMap.get(a.layerId) ?? 0;
679
+ const layerB = this.layerOrderMap.get(b.layerId) ?? 0;
680
+ if (layerA !== layerB) return layerA - layerB;
681
+ return a.zIndex - b.zIndex;
682
+ });
643
683
  }
644
684
  getById(id) {
645
685
  return this.elements.get(id);
@@ -823,12 +863,13 @@ function getEdgeIntersection(bounds, outsidePoint) {
823
863
  y: cy + dy * scale
824
864
  };
825
865
  }
826
- function findBindTarget(point, store, threshold, excludeId) {
866
+ function findBindTarget(point, store, threshold, excludeId, filter) {
827
867
  let closest = null;
828
868
  let closestDist = Infinity;
829
869
  for (const el of store.getAll()) {
830
870
  if (!isBindable(el)) continue;
831
871
  if (excludeId && el.id === excludeId) continue;
872
+ if (filter && !filter(el)) continue;
832
873
  const bounds = getElementBounds(el);
833
874
  if (!bounds) continue;
834
875
  const dist = distToBounds(point, bounds);
@@ -986,14 +1027,19 @@ function smoothToSegments(points) {
986
1027
  }
987
1028
 
988
1029
  // src/elements/element-renderer.ts
989
- var DOM_ELEMENT_TYPES = /* @__PURE__ */ new Set(["note", "image", "html", "text"]);
1030
+ var DOM_ELEMENT_TYPES = /* @__PURE__ */ new Set(["note", "html", "text"]);
990
1031
  var ARROWHEAD_LENGTH = 12;
991
1032
  var ARROWHEAD_ANGLE = Math.PI / 6;
992
1033
  var ElementRenderer = class {
993
1034
  store = null;
1035
+ imageCache = /* @__PURE__ */ new Map();
1036
+ onImageLoad = null;
994
1037
  setStore(store) {
995
1038
  this.store = store;
996
1039
  }
1040
+ setOnImageLoad(callback) {
1041
+ this.onImageLoad = callback;
1042
+ }
997
1043
  isDomElement(element) {
998
1044
  return DOM_ELEMENT_TYPES.has(element.type);
999
1045
  }
@@ -1008,6 +1054,9 @@ var ElementRenderer = class {
1008
1054
  case "shape":
1009
1055
  this.renderShape(ctx, element);
1010
1056
  break;
1057
+ case "image":
1058
+ this.renderImage(ctx, element);
1059
+ break;
1011
1060
  }
1012
1061
  }
1013
1062
  renderStroke(ctx, stroke) {
@@ -1143,6 +1192,20 @@ var ElementRenderer = class {
1143
1192
  }
1144
1193
  }
1145
1194
  }
1195
+ renderImage(ctx, image) {
1196
+ const img = this.getImage(image.src);
1197
+ if (!img) return;
1198
+ ctx.drawImage(img, image.position.x, image.position.y, image.size.w, image.size.h);
1199
+ }
1200
+ getImage(src) {
1201
+ const cached = this.imageCache.get(src);
1202
+ if (cached) return cached.complete ? cached : null;
1203
+ const img = new Image();
1204
+ img.src = src;
1205
+ this.imageCache.set(src, img);
1206
+ img.onload = () => this.onImageLoad?.();
1207
+ return null;
1208
+ }
1146
1209
  };
1147
1210
 
1148
1211
  // src/elements/note-editor.ts
@@ -1494,6 +1557,7 @@ function createStroke(input) {
1494
1557
  position: input.position ?? { x: 0, y: 0 },
1495
1558
  zIndex: input.zIndex ?? 0,
1496
1559
  locked: input.locked ?? false,
1560
+ layerId: input.layerId ?? "",
1497
1561
  points: input.points,
1498
1562
  color: input.color ?? "#000000",
1499
1563
  width: input.width ?? 2,
@@ -1507,6 +1571,7 @@ function createNote(input) {
1507
1571
  position: input.position,
1508
1572
  zIndex: input.zIndex ?? 0,
1509
1573
  locked: input.locked ?? false,
1574
+ layerId: input.layerId ?? "",
1510
1575
  size: input.size ?? { w: 200, h: 100 },
1511
1576
  text: input.text ?? "",
1512
1577
  backgroundColor: input.backgroundColor ?? "#ffeb3b",
@@ -1520,6 +1585,7 @@ function createArrow(input) {
1520
1585
  position: input.position ?? { x: 0, y: 0 },
1521
1586
  zIndex: input.zIndex ?? 0,
1522
1587
  locked: input.locked ?? false,
1588
+ layerId: input.layerId ?? "",
1523
1589
  from: input.from,
1524
1590
  to: input.to,
1525
1591
  bend: input.bend ?? 0,
@@ -1537,6 +1603,7 @@ function createImage(input) {
1537
1603
  position: input.position,
1538
1604
  zIndex: input.zIndex ?? 0,
1539
1605
  locked: input.locked ?? false,
1606
+ layerId: input.layerId ?? "",
1540
1607
  size: input.size,
1541
1608
  src: input.src
1542
1609
  };
@@ -1548,6 +1615,7 @@ function createHtmlElement(input) {
1548
1615
  position: input.position,
1549
1616
  zIndex: input.zIndex ?? 0,
1550
1617
  locked: input.locked ?? false,
1618
+ layerId: input.layerId ?? "",
1551
1619
  size: input.size
1552
1620
  };
1553
1621
  }
@@ -1558,6 +1626,7 @@ function createShape(input) {
1558
1626
  position: input.position,
1559
1627
  zIndex: input.zIndex ?? 0,
1560
1628
  locked: input.locked ?? false,
1629
+ layerId: input.layerId ?? "",
1561
1630
  shape: input.shape ?? "rectangle",
1562
1631
  size: input.size,
1563
1632
  strokeColor: input.strokeColor ?? "#000000",
@@ -1572,6 +1641,7 @@ function createText(input) {
1572
1641
  position: input.position,
1573
1642
  zIndex: input.zIndex ?? 0,
1574
1643
  locked: input.locked ?? false,
1644
+ layerId: input.layerId ?? "",
1575
1645
  size: input.size ?? { w: 200, h: 28 },
1576
1646
  text: input.text ?? "",
1577
1647
  fontSize: input.fontSize ?? 16,
@@ -1580,6 +1650,155 @@ function createText(input) {
1580
1650
  };
1581
1651
  }
1582
1652
 
1653
+ // src/layers/layer-manager.ts
1654
+ var LayerManager = class {
1655
+ constructor(store) {
1656
+ this.store = store;
1657
+ const defaultLayer = {
1658
+ id: createId("layer"),
1659
+ name: "Layer 1",
1660
+ visible: true,
1661
+ locked: false,
1662
+ order: 0,
1663
+ opacity: 1
1664
+ };
1665
+ this.layers.set(defaultLayer.id, defaultLayer);
1666
+ this._activeLayerId = defaultLayer.id;
1667
+ this.syncLayerOrder();
1668
+ }
1669
+ layers = /* @__PURE__ */ new Map();
1670
+ _activeLayerId;
1671
+ bus = new EventBus();
1672
+ get activeLayer() {
1673
+ const layer = this.layers.get(this._activeLayerId);
1674
+ if (!layer) throw new Error("Active layer not found");
1675
+ return { ...layer };
1676
+ }
1677
+ get activeLayerId() {
1678
+ return this._activeLayerId;
1679
+ }
1680
+ getLayers() {
1681
+ return [...this.layers.values()].sort((a, b) => a.order - b.order).map((l) => ({ ...l }));
1682
+ }
1683
+ getLayer(id) {
1684
+ const layer = this.layers.get(id);
1685
+ return layer ? { ...layer } : void 0;
1686
+ }
1687
+ isLayerVisible(id) {
1688
+ return this.layers.get(id)?.visible ?? true;
1689
+ }
1690
+ isLayerLocked(id) {
1691
+ return this.layers.get(id)?.locked ?? false;
1692
+ }
1693
+ createLayer(name) {
1694
+ const maxOrder = Math.max(...[...this.layers.values()].map((l) => l.order), -1);
1695
+ const autoName = name ?? `Layer ${this.layers.size + 1}`;
1696
+ const layer = {
1697
+ id: createId("layer"),
1698
+ name: autoName,
1699
+ visible: true,
1700
+ locked: false,
1701
+ order: maxOrder + 1,
1702
+ opacity: 1
1703
+ };
1704
+ this.addLayerDirect(layer);
1705
+ return { ...layer };
1706
+ }
1707
+ removeLayer(id) {
1708
+ if (this.layers.size <= 1) {
1709
+ throw new Error("Cannot remove the last layer");
1710
+ }
1711
+ if (this._activeLayerId === id) {
1712
+ const remaining = [...this.layers.values()].filter((l) => l.id !== id).sort((a, b) => b.order - a.order);
1713
+ const fallback = remaining[0];
1714
+ if (fallback) this._activeLayerId = fallback.id;
1715
+ }
1716
+ const elements = this.store.getAll().filter((el) => el.layerId === id);
1717
+ for (const el of elements) {
1718
+ this.store.update(el.id, { layerId: this._activeLayerId });
1719
+ }
1720
+ this.removeLayerDirect(id);
1721
+ }
1722
+ renameLayer(id, name) {
1723
+ this.updateLayerDirect(id, { name });
1724
+ }
1725
+ reorderLayer(id, newOrder) {
1726
+ if (!this.layers.has(id)) return;
1727
+ this.updateLayerDirect(id, { order: newOrder });
1728
+ }
1729
+ setLayerVisible(id, visible) {
1730
+ if (!visible && id === this._activeLayerId) {
1731
+ const fallback = this.findFallbackLayer(id);
1732
+ if (!fallback) return false;
1733
+ this._activeLayerId = fallback.id;
1734
+ }
1735
+ this.updateLayerDirect(id, { visible });
1736
+ return true;
1737
+ }
1738
+ setLayerLocked(id, locked) {
1739
+ if (locked && id === this._activeLayerId) {
1740
+ const fallback = this.findFallbackLayer(id);
1741
+ if (!fallback) return false;
1742
+ this._activeLayerId = fallback.id;
1743
+ }
1744
+ this.updateLayerDirect(id, { locked });
1745
+ return true;
1746
+ }
1747
+ setActiveLayer(id) {
1748
+ if (!this.layers.has(id)) return;
1749
+ this._activeLayerId = id;
1750
+ this.bus.emit("change", null);
1751
+ }
1752
+ moveElementToLayer(elementId, layerId) {
1753
+ if (!this.layers.has(layerId)) return;
1754
+ this.store.update(elementId, { layerId });
1755
+ this.bus.emit("change", null);
1756
+ }
1757
+ snapshot() {
1758
+ return this.getLayers();
1759
+ }
1760
+ loadSnapshot(layers) {
1761
+ this.layers.clear();
1762
+ for (const layer of layers) {
1763
+ this.layers.set(layer.id, { ...layer });
1764
+ }
1765
+ const first = this.getLayers()[0];
1766
+ if (first) this._activeLayerId = first.id;
1767
+ this.syncLayerOrder();
1768
+ this.bus.emit("change", null);
1769
+ }
1770
+ on(event, callback) {
1771
+ return this.bus.on(event, callback);
1772
+ }
1773
+ addLayerDirect(layer) {
1774
+ this.layers.set(layer.id, { ...layer });
1775
+ this.syncLayerOrder();
1776
+ this.bus.emit("change", null);
1777
+ }
1778
+ removeLayerDirect(id) {
1779
+ this.layers.delete(id);
1780
+ this.syncLayerOrder();
1781
+ this.bus.emit("change", null);
1782
+ }
1783
+ updateLayerDirect(id, props) {
1784
+ const layer = this.layers.get(id);
1785
+ if (!layer) return;
1786
+ Object.assign(layer, props);
1787
+ if ("order" in props) this.syncLayerOrder();
1788
+ this.bus.emit("change", null);
1789
+ }
1790
+ syncLayerOrder() {
1791
+ const order = /* @__PURE__ */ new Map();
1792
+ for (const layer of this.layers.values()) {
1793
+ order.set(layer.id, layer.order);
1794
+ }
1795
+ this.store.setLayerOrder(order);
1796
+ }
1797
+ findFallbackLayer(excludeId) {
1798
+ return [...this.layers.values()].filter((l) => l.id !== excludeId && l.visible && !l.locked).sort((a, b) => b.order - a.order)[0];
1799
+ }
1800
+ };
1801
+
1583
1802
  // src/canvas/viewport.ts
1584
1803
  var Viewport = class {
1585
1804
  constructor(container, options = {}) {
@@ -1588,9 +1807,11 @@ var Viewport = class {
1588
1807
  this.background = new Background(options.background);
1589
1808
  this._gridSize = options.background?.spacing ?? 24;
1590
1809
  this.store = new ElementStore();
1810
+ this.layerManager = new LayerManager(this.store);
1591
1811
  this.toolManager = new ToolManager();
1592
1812
  this.renderer = new ElementRenderer();
1593
1813
  this.renderer.setStore(this.store);
1814
+ this.renderer.setOnImageLoad(() => this.requestRender());
1594
1815
  this.noteEditor = new NoteEditor();
1595
1816
  this.noteEditor.setOnStop((id) => this.onTextEditStop(id));
1596
1817
  this.history = new HistoryStack();
@@ -1611,7 +1832,10 @@ var Viewport = class {
1611
1832
  this.wrapper.style.cursor = cursor;
1612
1833
  },
1613
1834
  snapToGrid: false,
1614
- gridSize: this._gridSize
1835
+ gridSize: this._gridSize,
1836
+ activeLayerId: this.layerManager.activeLayerId,
1837
+ isLayerVisible: (id) => this.layerManager.isLayerVisible(id),
1838
+ isLayerLocked: (id) => this.layerManager.isLayerLocked(id)
1615
1839
  };
1616
1840
  this.inputHandler = new InputHandler(this.wrapper, this.camera, {
1617
1841
  toolManager: this.toolManager,
@@ -1632,6 +1856,10 @@ var Viewport = class {
1632
1856
  this.store.on("update", () => this.requestRender()),
1633
1857
  this.store.on("clear", () => this.clearDomNodes())
1634
1858
  ];
1859
+ this.layerManager.on("change", () => {
1860
+ this.toolContext.activeLayerId = this.layerManager.activeLayerId;
1861
+ this.requestRender();
1862
+ });
1635
1863
  this.wrapper.addEventListener("dblclick", this.onDblClick);
1636
1864
  this.wrapper.addEventListener("dragover", this.onDragOver);
1637
1865
  this.wrapper.addEventListener("drop", this.onDrop);
@@ -1641,6 +1869,7 @@ var Viewport = class {
1641
1869
  }
1642
1870
  camera;
1643
1871
  store;
1872
+ layerManager;
1644
1873
  toolManager;
1645
1874
  history;
1646
1875
  domLayer;
@@ -1676,7 +1905,7 @@ var Viewport = class {
1676
1905
  this.needsRender = true;
1677
1906
  }
1678
1907
  exportState() {
1679
- return exportState(this.store.snapshot(), this.camera);
1908
+ return exportState(this.store.snapshot(), this.camera, this.layerManager.snapshot());
1680
1909
  }
1681
1910
  exportJSON() {
1682
1911
  return JSON.stringify(this.exportState());
@@ -1686,6 +1915,9 @@ var Viewport = class {
1686
1915
  this.noteEditor.destroy(this.store);
1687
1916
  this.clearDomNodes();
1688
1917
  this.store.loadSnapshot(state.elements);
1918
+ if (state.layers && state.layers.length > 0) {
1919
+ this.layerManager.loadSnapshot(state.layers);
1920
+ }
1689
1921
  this.history.clear();
1690
1922
  this.historyRecorder.resume();
1691
1923
  this.camera.moveTo(state.camera.position.x, state.camera.position.y);
@@ -1709,7 +1941,7 @@ var Viewport = class {
1709
1941
  return result;
1710
1942
  }
1711
1943
  addImage(src, position, size = { w: 300, h: 200 }) {
1712
- const image = createImage({ position, size, src });
1944
+ const image = createImage({ position, size, src, layerId: this.layerManager.activeLayerId });
1713
1945
  this.historyRecorder.begin();
1714
1946
  this.store.add(image);
1715
1947
  this.historyRecorder.commit();
@@ -1717,7 +1949,7 @@ var Viewport = class {
1717
1949
  return image.id;
1718
1950
  }
1719
1951
  addHtmlElement(dom, position, size = { w: 200, h: 150 }) {
1720
- const el = createHtmlElement({ position, size });
1952
+ const el = createHtmlElement({ position, size, layerId: this.layerManager.activeLayerId });
1721
1953
  this.htmlContent.set(el.id, dom);
1722
1954
  this.historyRecorder.begin();
1723
1955
  this.store.add(el);
@@ -1760,9 +1992,17 @@ var Viewport = class {
1760
1992
  ctx.save();
1761
1993
  ctx.translate(this.camera.position.x, this.camera.position.y);
1762
1994
  ctx.scale(this.camera.zoom, this.camera.zoom);
1763
- for (const element of this.store.getAll()) {
1995
+ const allElements = this.store.getAll();
1996
+ let domZIndex = 0;
1997
+ for (const element of allElements) {
1998
+ if (!this.layerManager.isLayerVisible(element.layerId)) {
1999
+ if (this.renderer.isDomElement(element)) {
2000
+ this.hideDomNode(element.id);
2001
+ }
2002
+ continue;
2003
+ }
1764
2004
  if (this.renderer.isDomElement(element)) {
1765
- this.syncDomNode(element);
2005
+ this.syncDomNode(element, domZIndex++);
1766
2006
  } else {
1767
2007
  this.renderer.renderCanvasElement(ctx, element);
1768
2008
  }
@@ -1894,7 +2134,7 @@ var Viewport = class {
1894
2134
  reader.readAsDataURL(file);
1895
2135
  }
1896
2136
  };
1897
- syncDomNode(element) {
2137
+ syncDomNode(element, zIndex = 0) {
1898
2138
  let node = this.domNodes.get(element.id);
1899
2139
  if (!node) {
1900
2140
  node = document.createElement("div");
@@ -1908,10 +2148,12 @@ var Viewport = class {
1908
2148
  }
1909
2149
  const size = "size" in element ? element.size : null;
1910
2150
  Object.assign(node.style, {
2151
+ display: "block",
1911
2152
  left: `${element.position.x}px`,
1912
2153
  top: `${element.position.y}px`,
1913
2154
  width: size ? `${size.w}px` : "auto",
1914
- height: size ? `${size.h}px` : "auto"
2155
+ height: size ? `${size.h}px` : "auto",
2156
+ zIndex: String(zIndex)
1915
2157
  });
1916
2158
  this.renderDomContent(node, element);
1917
2159
  }
@@ -1946,26 +2188,6 @@ var Viewport = class {
1946
2188
  node.style.color = element.textColor;
1947
2189
  }
1948
2190
  }
1949
- if (element.type === "image") {
1950
- if (!node.dataset["initialized"]) {
1951
- node.dataset["initialized"] = "true";
1952
- const img = document.createElement("img");
1953
- img.src = element.src;
1954
- Object.assign(img.style, {
1955
- width: "100%",
1956
- height: "100%",
1957
- objectFit: "contain",
1958
- pointerEvents: "none"
1959
- });
1960
- img.draggable = false;
1961
- node.appendChild(img);
1962
- } else {
1963
- const img = node.querySelector("img");
1964
- if (img && img.src !== element.src) {
1965
- img.src = element.src;
1966
- }
1967
- }
1968
- }
1969
2191
  if (element.type === "html" && !node.dataset["initialized"]) {
1970
2192
  const content = this.htmlContent.get(element.id);
1971
2193
  if (content) {
@@ -2048,6 +2270,10 @@ var Viewport = class {
2048
2270
  }
2049
2271
  }
2050
2272
  }
2273
+ hideDomNode(id) {
2274
+ const node = this.domNodes.get(id);
2275
+ if (node) node.style.display = "none";
2276
+ }
2051
2277
  removeDomNode(id) {
2052
2278
  this.htmlContent.delete(id);
2053
2279
  const node = this.domNodes.get(id);
@@ -2194,7 +2420,8 @@ var PencilTool = class {
2194
2420
  const stroke = createStroke({
2195
2421
  points: simplified,
2196
2422
  color: this.color,
2197
- width: this.width
2423
+ width: this.width,
2424
+ layerId: ctx.activeLayerId ?? ""
2198
2425
  });
2199
2426
  ctx.store.add(stroke);
2200
2427
  this.points = [];
@@ -2258,6 +2485,8 @@ var EraserTool = class {
2258
2485
  const strokes = ctx.store.getElementsByType("stroke");
2259
2486
  let erased = false;
2260
2487
  for (const stroke of strokes) {
2488
+ if (ctx.isLayerVisible && !ctx.isLayerVisible(stroke.layerId)) continue;
2489
+ if (ctx.isLayerLocked && ctx.isLayerLocked(stroke.layerId)) continue;
2261
2490
  if (this.strokeIntersects(stroke, world)) {
2262
2491
  ctx.store.remove(stroke.id);
2263
2492
  erased = true;
@@ -2743,6 +2972,8 @@ var SelectTool = class {
2743
2972
  findElementsInRect(marquee, ctx) {
2744
2973
  const ids = [];
2745
2974
  for (const el of ctx.store.getAll()) {
2975
+ if (ctx.isLayerVisible && !ctx.isLayerVisible(el.layerId)) continue;
2976
+ if (ctx.isLayerLocked && ctx.isLayerLocked(el.layerId)) continue;
2746
2977
  const bounds = this.getElementBounds(el);
2747
2978
  if (bounds && this.rectsOverlap(marquee, bounds)) {
2748
2979
  ids.push(el.id);
@@ -2777,6 +3008,8 @@ var SelectTool = class {
2777
3008
  hitTest(world, ctx) {
2778
3009
  const elements = ctx.store.getAll().reverse();
2779
3010
  for (const el of elements) {
3011
+ if (ctx.isLayerVisible && !ctx.isLayerVisible(el.layerId)) continue;
3012
+ if (ctx.isLayerLocked && ctx.isLayerLocked(el.layerId)) continue;
2780
3013
  if (this.isInsideBounds(world, el)) return el;
2781
3014
  }
2782
3015
  return null;
@@ -2821,11 +3054,20 @@ var ArrowTool = class {
2821
3054
  if (options.color !== void 0) this.color = options.color;
2822
3055
  if (options.width !== void 0) this.width = options.width;
2823
3056
  }
3057
+ layerFilter(ctx) {
3058
+ if (!ctx.isLayerVisible && !ctx.isLayerLocked) return void 0;
3059
+ return (el) => {
3060
+ if (ctx.isLayerVisible && !ctx.isLayerVisible(el.layerId)) return false;
3061
+ if (ctx.isLayerLocked && ctx.isLayerLocked(el.layerId)) return false;
3062
+ return true;
3063
+ };
3064
+ }
2824
3065
  onPointerDown(state, ctx) {
2825
3066
  this.drawing = true;
2826
3067
  const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
2827
3068
  const threshold = BIND_THRESHOLD2 / ctx.camera.zoom;
2828
- const target = findBindTarget(world, ctx.store, threshold);
3069
+ const filter = this.layerFilter(ctx);
3070
+ const target = findBindTarget(world, ctx.store, threshold, void 0, filter);
2829
3071
  if (target) {
2830
3072
  this.start = getElementCenter(target);
2831
3073
  this.fromBinding = { elementId: target.id };
@@ -2843,7 +3085,8 @@ var ArrowTool = class {
2843
3085
  const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
2844
3086
  const threshold = BIND_THRESHOLD2 / ctx.camera.zoom;
2845
3087
  const excludeId = this.fromBinding?.elementId;
2846
- const target = findBindTarget(world, ctx.store, threshold, excludeId);
3088
+ const filter = this.layerFilter(ctx);
3089
+ const target = findBindTarget(world, ctx.store, threshold, excludeId, filter);
2847
3090
  if (target) {
2848
3091
  this.end = getElementCenter(target);
2849
3092
  this.toTarget = target;
@@ -2864,7 +3107,8 @@ var ArrowTool = class {
2864
3107
  color: this.color,
2865
3108
  width: this.width,
2866
3109
  fromBinding: this.fromBinding,
2867
- toBinding: this.toTarget ? { elementId: this.toTarget.id } : void 0
3110
+ toBinding: this.toTarget ? { elementId: this.toTarget.id } : void 0,
3111
+ layerId: ctx.activeLayerId ?? ""
2868
3112
  });
2869
3113
  ctx.store.add(arrow);
2870
3114
  this.fromTarget = null;
@@ -2951,7 +3195,8 @@ var NoteTool = class {
2951
3195
  position: world,
2952
3196
  size: { ...this.size },
2953
3197
  backgroundColor: this.backgroundColor,
2954
- textColor: this.textColor
3198
+ textColor: this.textColor,
3199
+ layerId: ctx.activeLayerId ?? ""
2955
3200
  });
2956
3201
  ctx.store.add(note);
2957
3202
  ctx.requestRender();
@@ -2995,7 +3240,8 @@ var TextTool = class {
2995
3240
  position: world,
2996
3241
  fontSize: this.fontSize,
2997
3242
  color: this.color,
2998
- textAlign: this.textAlign
3243
+ textAlign: this.textAlign,
3244
+ layerId: ctx.activeLayerId ?? ""
2999
3245
  });
3000
3246
  ctx.store.add(textEl);
3001
3247
  ctx.requestRender();
@@ -3091,7 +3337,8 @@ var ShapeTool = class {
3091
3337
  shape: this.shape,
3092
3338
  strokeColor: this.strokeColor,
3093
3339
  strokeWidth: this.strokeWidth,
3094
- fillColor: this.fillColor
3340
+ fillColor: this.fillColor,
3341
+ layerId: ctx.activeLayerId ?? ""
3095
3342
  });
3096
3343
  ctx.store.add(shape);
3097
3344
  ctx.requestRender();
@@ -3152,8 +3399,48 @@ var ShapeTool = class {
3152
3399
  };
3153
3400
  };
3154
3401
 
3402
+ // src/history/layer-commands.ts
3403
+ var CreateLayerCommand = class {
3404
+ constructor(manager, layer) {
3405
+ this.manager = manager;
3406
+ this.layer = layer;
3407
+ }
3408
+ execute(_store) {
3409
+ this.manager.addLayerDirect(this.layer);
3410
+ }
3411
+ undo(_store) {
3412
+ this.manager.removeLayerDirect(this.layer.id);
3413
+ }
3414
+ };
3415
+ var RemoveLayerCommand = class {
3416
+ constructor(manager, layer) {
3417
+ this.manager = manager;
3418
+ this.layer = layer;
3419
+ }
3420
+ execute(_store) {
3421
+ this.manager.removeLayerDirect(this.layer.id);
3422
+ }
3423
+ undo(_store) {
3424
+ this.manager.addLayerDirect(this.layer);
3425
+ }
3426
+ };
3427
+ var UpdateLayerCommand = class {
3428
+ constructor(manager, layerId, previous, current) {
3429
+ this.manager = manager;
3430
+ this.layerId = layerId;
3431
+ this.previous = previous;
3432
+ this.current = current;
3433
+ }
3434
+ execute(_store) {
3435
+ this.manager.updateLayerDirect(this.layerId, { ...this.current });
3436
+ }
3437
+ undo(_store) {
3438
+ this.manager.updateLayerDirect(this.layerId, { ...this.previous });
3439
+ }
3440
+ };
3441
+
3155
3442
  // src/index.ts
3156
- var VERSION = "0.5.0";
3443
+ var VERSION = "0.6.1";
3157
3444
  // Annotate the CommonJS export names for ESM import in node:
3158
3445
  0 && (module.exports = {
3159
3446
  AddElementCommand,
@@ -3162,6 +3449,7 @@ var VERSION = "0.5.0";
3162
3449
  Background,
3163
3450
  BatchCommand,
3164
3451
  Camera,
3452
+ CreateLayerCommand,
3165
3453
  ElementRenderer,
3166
3454
  ElementStore,
3167
3455
  EraserTool,
@@ -3171,15 +3459,18 @@ var VERSION = "0.5.0";
3171
3459
  HistoryStack,
3172
3460
  ImageTool,
3173
3461
  InputHandler,
3462
+ LayerManager,
3174
3463
  NoteEditor,
3175
3464
  NoteTool,
3176
3465
  PencilTool,
3177
3466
  RemoveElementCommand,
3467
+ RemoveLayerCommand,
3178
3468
  SelectTool,
3179
3469
  ShapeTool,
3180
3470
  TextTool,
3181
3471
  ToolManager,
3182
3472
  UpdateElementCommand,
3473
+ UpdateLayerCommand,
3183
3474
  VERSION,
3184
3475
  Viewport,
3185
3476
  clearStaleBindings,