@fieldnotes/core 0.5.0 → 0.6.0

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,7 +284,8 @@ var AutoSave = class {
258
284
  }
259
285
  save() {
260
286
  if (typeof localStorage === "undefined") return;
261
- const state = exportState(this.store.snapshot(), this.camera);
287
+ const layers = this.layerManager?.snapshot() ?? [];
288
+ const state = exportState(this.store.snapshot(), this.camera, layers);
262
289
  localStorage.setItem(this.key, JSON.stringify(state));
263
290
  }
264
291
  };
@@ -635,11 +662,20 @@ var InputHandler = class {
635
662
  var ElementStore = class {
636
663
  elements = /* @__PURE__ */ new Map();
637
664
  bus = new EventBus();
665
+ layerOrderMap = /* @__PURE__ */ new Map();
638
666
  get count() {
639
667
  return this.elements.size;
640
668
  }
669
+ setLayerOrder(order) {
670
+ this.layerOrderMap = new Map(order);
671
+ }
641
672
  getAll() {
642
- return [...this.elements.values()].sort((a, b) => a.zIndex - b.zIndex);
673
+ return [...this.elements.values()].sort((a, b) => {
674
+ const layerA = this.layerOrderMap.get(a.layerId) ?? 0;
675
+ const layerB = this.layerOrderMap.get(b.layerId) ?? 0;
676
+ if (layerA !== layerB) return layerA - layerB;
677
+ return a.zIndex - b.zIndex;
678
+ });
643
679
  }
644
680
  getById(id) {
645
681
  return this.elements.get(id);
@@ -823,12 +859,13 @@ function getEdgeIntersection(bounds, outsidePoint) {
823
859
  y: cy + dy * scale
824
860
  };
825
861
  }
826
- function findBindTarget(point, store, threshold, excludeId) {
862
+ function findBindTarget(point, store, threshold, excludeId, filter) {
827
863
  let closest = null;
828
864
  let closestDist = Infinity;
829
865
  for (const el of store.getAll()) {
830
866
  if (!isBindable(el)) continue;
831
867
  if (excludeId && el.id === excludeId) continue;
868
+ if (filter && !filter(el)) continue;
832
869
  const bounds = getElementBounds(el);
833
870
  if (!bounds) continue;
834
871
  const dist = distToBounds(point, bounds);
@@ -986,14 +1023,19 @@ function smoothToSegments(points) {
986
1023
  }
987
1024
 
988
1025
  // src/elements/element-renderer.ts
989
- var DOM_ELEMENT_TYPES = /* @__PURE__ */ new Set(["note", "image", "html", "text"]);
1026
+ var DOM_ELEMENT_TYPES = /* @__PURE__ */ new Set(["note", "html", "text"]);
990
1027
  var ARROWHEAD_LENGTH = 12;
991
1028
  var ARROWHEAD_ANGLE = Math.PI / 6;
992
1029
  var ElementRenderer = class {
993
1030
  store = null;
1031
+ imageCache = /* @__PURE__ */ new Map();
1032
+ onImageLoad = null;
994
1033
  setStore(store) {
995
1034
  this.store = store;
996
1035
  }
1036
+ setOnImageLoad(callback) {
1037
+ this.onImageLoad = callback;
1038
+ }
997
1039
  isDomElement(element) {
998
1040
  return DOM_ELEMENT_TYPES.has(element.type);
999
1041
  }
@@ -1008,6 +1050,9 @@ var ElementRenderer = class {
1008
1050
  case "shape":
1009
1051
  this.renderShape(ctx, element);
1010
1052
  break;
1053
+ case "image":
1054
+ this.renderImage(ctx, element);
1055
+ break;
1011
1056
  }
1012
1057
  }
1013
1058
  renderStroke(ctx, stroke) {
@@ -1143,6 +1188,20 @@ var ElementRenderer = class {
1143
1188
  }
1144
1189
  }
1145
1190
  }
1191
+ renderImage(ctx, image) {
1192
+ const img = this.getImage(image.src);
1193
+ if (!img) return;
1194
+ ctx.drawImage(img, image.position.x, image.position.y, image.size.w, image.size.h);
1195
+ }
1196
+ getImage(src) {
1197
+ const cached = this.imageCache.get(src);
1198
+ if (cached) return cached.complete ? cached : null;
1199
+ const img = new Image();
1200
+ img.src = src;
1201
+ this.imageCache.set(src, img);
1202
+ img.onload = () => this.onImageLoad?.();
1203
+ return null;
1204
+ }
1146
1205
  };
1147
1206
 
1148
1207
  // src/elements/note-editor.ts
@@ -1494,6 +1553,7 @@ function createStroke(input) {
1494
1553
  position: input.position ?? { x: 0, y: 0 },
1495
1554
  zIndex: input.zIndex ?? 0,
1496
1555
  locked: input.locked ?? false,
1556
+ layerId: input.layerId ?? "",
1497
1557
  points: input.points,
1498
1558
  color: input.color ?? "#000000",
1499
1559
  width: input.width ?? 2,
@@ -1507,6 +1567,7 @@ function createNote(input) {
1507
1567
  position: input.position,
1508
1568
  zIndex: input.zIndex ?? 0,
1509
1569
  locked: input.locked ?? false,
1570
+ layerId: input.layerId ?? "",
1510
1571
  size: input.size ?? { w: 200, h: 100 },
1511
1572
  text: input.text ?? "",
1512
1573
  backgroundColor: input.backgroundColor ?? "#ffeb3b",
@@ -1520,6 +1581,7 @@ function createArrow(input) {
1520
1581
  position: input.position ?? { x: 0, y: 0 },
1521
1582
  zIndex: input.zIndex ?? 0,
1522
1583
  locked: input.locked ?? false,
1584
+ layerId: input.layerId ?? "",
1523
1585
  from: input.from,
1524
1586
  to: input.to,
1525
1587
  bend: input.bend ?? 0,
@@ -1537,6 +1599,7 @@ function createImage(input) {
1537
1599
  position: input.position,
1538
1600
  zIndex: input.zIndex ?? 0,
1539
1601
  locked: input.locked ?? false,
1602
+ layerId: input.layerId ?? "",
1540
1603
  size: input.size,
1541
1604
  src: input.src
1542
1605
  };
@@ -1548,6 +1611,7 @@ function createHtmlElement(input) {
1548
1611
  position: input.position,
1549
1612
  zIndex: input.zIndex ?? 0,
1550
1613
  locked: input.locked ?? false,
1614
+ layerId: input.layerId ?? "",
1551
1615
  size: input.size
1552
1616
  };
1553
1617
  }
@@ -1558,6 +1622,7 @@ function createShape(input) {
1558
1622
  position: input.position,
1559
1623
  zIndex: input.zIndex ?? 0,
1560
1624
  locked: input.locked ?? false,
1625
+ layerId: input.layerId ?? "",
1561
1626
  shape: input.shape ?? "rectangle",
1562
1627
  size: input.size,
1563
1628
  strokeColor: input.strokeColor ?? "#000000",
@@ -1572,6 +1637,7 @@ function createText(input) {
1572
1637
  position: input.position,
1573
1638
  zIndex: input.zIndex ?? 0,
1574
1639
  locked: input.locked ?? false,
1640
+ layerId: input.layerId ?? "",
1575
1641
  size: input.size ?? { w: 200, h: 28 },
1576
1642
  text: input.text ?? "",
1577
1643
  fontSize: input.fontSize ?? 16,
@@ -1580,6 +1646,155 @@ function createText(input) {
1580
1646
  };
1581
1647
  }
1582
1648
 
1649
+ // src/layers/layer-manager.ts
1650
+ var LayerManager = class {
1651
+ constructor(store) {
1652
+ this.store = store;
1653
+ const defaultLayer = {
1654
+ id: createId("layer"),
1655
+ name: "Layer 1",
1656
+ visible: true,
1657
+ locked: false,
1658
+ order: 0,
1659
+ opacity: 1
1660
+ };
1661
+ this.layers.set(defaultLayer.id, defaultLayer);
1662
+ this._activeLayerId = defaultLayer.id;
1663
+ this.syncLayerOrder();
1664
+ }
1665
+ layers = /* @__PURE__ */ new Map();
1666
+ _activeLayerId;
1667
+ bus = new EventBus();
1668
+ get activeLayer() {
1669
+ const layer = this.layers.get(this._activeLayerId);
1670
+ if (!layer) throw new Error("Active layer not found");
1671
+ return { ...layer };
1672
+ }
1673
+ get activeLayerId() {
1674
+ return this._activeLayerId;
1675
+ }
1676
+ getLayers() {
1677
+ return [...this.layers.values()].sort((a, b) => a.order - b.order).map((l) => ({ ...l }));
1678
+ }
1679
+ getLayer(id) {
1680
+ const layer = this.layers.get(id);
1681
+ return layer ? { ...layer } : void 0;
1682
+ }
1683
+ isLayerVisible(id) {
1684
+ return this.layers.get(id)?.visible ?? true;
1685
+ }
1686
+ isLayerLocked(id) {
1687
+ return this.layers.get(id)?.locked ?? false;
1688
+ }
1689
+ createLayer(name) {
1690
+ const maxOrder = Math.max(...[...this.layers.values()].map((l) => l.order), -1);
1691
+ const autoName = name ?? `Layer ${this.layers.size + 1}`;
1692
+ const layer = {
1693
+ id: createId("layer"),
1694
+ name: autoName,
1695
+ visible: true,
1696
+ locked: false,
1697
+ order: maxOrder + 1,
1698
+ opacity: 1
1699
+ };
1700
+ this.addLayerDirect(layer);
1701
+ return { ...layer };
1702
+ }
1703
+ removeLayer(id) {
1704
+ if (this.layers.size <= 1) {
1705
+ throw new Error("Cannot remove the last layer");
1706
+ }
1707
+ if (this._activeLayerId === id) {
1708
+ const remaining = [...this.layers.values()].filter((l) => l.id !== id).sort((a, b) => b.order - a.order);
1709
+ const fallback = remaining[0];
1710
+ if (fallback) this._activeLayerId = fallback.id;
1711
+ }
1712
+ const elements = this.store.getAll().filter((el) => el.layerId === id);
1713
+ for (const el of elements) {
1714
+ this.store.update(el.id, { layerId: this._activeLayerId });
1715
+ }
1716
+ this.removeLayerDirect(id);
1717
+ }
1718
+ renameLayer(id, name) {
1719
+ this.updateLayerDirect(id, { name });
1720
+ }
1721
+ reorderLayer(id, newOrder) {
1722
+ if (!this.layers.has(id)) return;
1723
+ this.updateLayerDirect(id, { order: newOrder });
1724
+ }
1725
+ setLayerVisible(id, visible) {
1726
+ if (!visible && id === this._activeLayerId) {
1727
+ const fallback = this.findFallbackLayer(id);
1728
+ if (!fallback) return false;
1729
+ this._activeLayerId = fallback.id;
1730
+ }
1731
+ this.updateLayerDirect(id, { visible });
1732
+ return true;
1733
+ }
1734
+ setLayerLocked(id, locked) {
1735
+ if (locked && id === this._activeLayerId) {
1736
+ const fallback = this.findFallbackLayer(id);
1737
+ if (!fallback) return false;
1738
+ this._activeLayerId = fallback.id;
1739
+ }
1740
+ this.updateLayerDirect(id, { locked });
1741
+ return true;
1742
+ }
1743
+ setActiveLayer(id) {
1744
+ if (!this.layers.has(id)) return;
1745
+ this._activeLayerId = id;
1746
+ this.bus.emit("change", null);
1747
+ }
1748
+ moveElementToLayer(elementId, layerId) {
1749
+ if (!this.layers.has(layerId)) return;
1750
+ this.store.update(elementId, { layerId });
1751
+ this.bus.emit("change", null);
1752
+ }
1753
+ snapshot() {
1754
+ return this.getLayers();
1755
+ }
1756
+ loadSnapshot(layers) {
1757
+ this.layers.clear();
1758
+ for (const layer of layers) {
1759
+ this.layers.set(layer.id, { ...layer });
1760
+ }
1761
+ const first = this.getLayers()[0];
1762
+ if (first) this._activeLayerId = first.id;
1763
+ this.syncLayerOrder();
1764
+ this.bus.emit("change", null);
1765
+ }
1766
+ on(event, callback) {
1767
+ return this.bus.on(event, callback);
1768
+ }
1769
+ addLayerDirect(layer) {
1770
+ this.layers.set(layer.id, { ...layer });
1771
+ this.syncLayerOrder();
1772
+ this.bus.emit("change", null);
1773
+ }
1774
+ removeLayerDirect(id) {
1775
+ this.layers.delete(id);
1776
+ this.syncLayerOrder();
1777
+ this.bus.emit("change", null);
1778
+ }
1779
+ updateLayerDirect(id, props) {
1780
+ const layer = this.layers.get(id);
1781
+ if (!layer) return;
1782
+ Object.assign(layer, props);
1783
+ if ("order" in props) this.syncLayerOrder();
1784
+ this.bus.emit("change", null);
1785
+ }
1786
+ syncLayerOrder() {
1787
+ const order = /* @__PURE__ */ new Map();
1788
+ for (const layer of this.layers.values()) {
1789
+ order.set(layer.id, layer.order);
1790
+ }
1791
+ this.store.setLayerOrder(order);
1792
+ }
1793
+ findFallbackLayer(excludeId) {
1794
+ return [...this.layers.values()].filter((l) => l.id !== excludeId && l.visible && !l.locked).sort((a, b) => b.order - a.order)[0];
1795
+ }
1796
+ };
1797
+
1583
1798
  // src/canvas/viewport.ts
1584
1799
  var Viewport = class {
1585
1800
  constructor(container, options = {}) {
@@ -1588,9 +1803,11 @@ var Viewport = class {
1588
1803
  this.background = new Background(options.background);
1589
1804
  this._gridSize = options.background?.spacing ?? 24;
1590
1805
  this.store = new ElementStore();
1806
+ this.layerManager = new LayerManager(this.store);
1591
1807
  this.toolManager = new ToolManager();
1592
1808
  this.renderer = new ElementRenderer();
1593
1809
  this.renderer.setStore(this.store);
1810
+ this.renderer.setOnImageLoad(() => this.requestRender());
1594
1811
  this.noteEditor = new NoteEditor();
1595
1812
  this.noteEditor.setOnStop((id) => this.onTextEditStop(id));
1596
1813
  this.history = new HistoryStack();
@@ -1611,7 +1828,10 @@ var Viewport = class {
1611
1828
  this.wrapper.style.cursor = cursor;
1612
1829
  },
1613
1830
  snapToGrid: false,
1614
- gridSize: this._gridSize
1831
+ gridSize: this._gridSize,
1832
+ activeLayerId: this.layerManager.activeLayerId,
1833
+ isLayerVisible: (id) => this.layerManager.isLayerVisible(id),
1834
+ isLayerLocked: (id) => this.layerManager.isLayerLocked(id)
1615
1835
  };
1616
1836
  this.inputHandler = new InputHandler(this.wrapper, this.camera, {
1617
1837
  toolManager: this.toolManager,
@@ -1632,6 +1852,10 @@ var Viewport = class {
1632
1852
  this.store.on("update", () => this.requestRender()),
1633
1853
  this.store.on("clear", () => this.clearDomNodes())
1634
1854
  ];
1855
+ this.layerManager.on("change", () => {
1856
+ this.toolContext.activeLayerId = this.layerManager.activeLayerId;
1857
+ this.requestRender();
1858
+ });
1635
1859
  this.wrapper.addEventListener("dblclick", this.onDblClick);
1636
1860
  this.wrapper.addEventListener("dragover", this.onDragOver);
1637
1861
  this.wrapper.addEventListener("drop", this.onDrop);
@@ -1641,6 +1865,7 @@ var Viewport = class {
1641
1865
  }
1642
1866
  camera;
1643
1867
  store;
1868
+ layerManager;
1644
1869
  toolManager;
1645
1870
  history;
1646
1871
  domLayer;
@@ -1676,7 +1901,7 @@ var Viewport = class {
1676
1901
  this.needsRender = true;
1677
1902
  }
1678
1903
  exportState() {
1679
- return exportState(this.store.snapshot(), this.camera);
1904
+ return exportState(this.store.snapshot(), this.camera, this.layerManager.snapshot());
1680
1905
  }
1681
1906
  exportJSON() {
1682
1907
  return JSON.stringify(this.exportState());
@@ -1686,6 +1911,9 @@ var Viewport = class {
1686
1911
  this.noteEditor.destroy(this.store);
1687
1912
  this.clearDomNodes();
1688
1913
  this.store.loadSnapshot(state.elements);
1914
+ if (state.layers && state.layers.length > 0) {
1915
+ this.layerManager.loadSnapshot(state.layers);
1916
+ }
1689
1917
  this.history.clear();
1690
1918
  this.historyRecorder.resume();
1691
1919
  this.camera.moveTo(state.camera.position.x, state.camera.position.y);
@@ -1709,7 +1937,7 @@ var Viewport = class {
1709
1937
  return result;
1710
1938
  }
1711
1939
  addImage(src, position, size = { w: 300, h: 200 }) {
1712
- const image = createImage({ position, size, src });
1940
+ const image = createImage({ position, size, src, layerId: this.layerManager.activeLayerId });
1713
1941
  this.historyRecorder.begin();
1714
1942
  this.store.add(image);
1715
1943
  this.historyRecorder.commit();
@@ -1717,7 +1945,7 @@ var Viewport = class {
1717
1945
  return image.id;
1718
1946
  }
1719
1947
  addHtmlElement(dom, position, size = { w: 200, h: 150 }) {
1720
- const el = createHtmlElement({ position, size });
1948
+ const el = createHtmlElement({ position, size, layerId: this.layerManager.activeLayerId });
1721
1949
  this.htmlContent.set(el.id, dom);
1722
1950
  this.historyRecorder.begin();
1723
1951
  this.store.add(el);
@@ -1760,9 +1988,17 @@ var Viewport = class {
1760
1988
  ctx.save();
1761
1989
  ctx.translate(this.camera.position.x, this.camera.position.y);
1762
1990
  ctx.scale(this.camera.zoom, this.camera.zoom);
1763
- for (const element of this.store.getAll()) {
1991
+ const allElements = this.store.getAll();
1992
+ let domZIndex = 0;
1993
+ for (const element of allElements) {
1994
+ if (!this.layerManager.isLayerVisible(element.layerId)) {
1995
+ if (this.renderer.isDomElement(element)) {
1996
+ this.hideDomNode(element.id);
1997
+ }
1998
+ continue;
1999
+ }
1764
2000
  if (this.renderer.isDomElement(element)) {
1765
- this.syncDomNode(element);
2001
+ this.syncDomNode(element, domZIndex++);
1766
2002
  } else {
1767
2003
  this.renderer.renderCanvasElement(ctx, element);
1768
2004
  }
@@ -1894,7 +2130,7 @@ var Viewport = class {
1894
2130
  reader.readAsDataURL(file);
1895
2131
  }
1896
2132
  };
1897
- syncDomNode(element) {
2133
+ syncDomNode(element, zIndex = 0) {
1898
2134
  let node = this.domNodes.get(element.id);
1899
2135
  if (!node) {
1900
2136
  node = document.createElement("div");
@@ -1908,10 +2144,12 @@ var Viewport = class {
1908
2144
  }
1909
2145
  const size = "size" in element ? element.size : null;
1910
2146
  Object.assign(node.style, {
2147
+ display: "block",
1911
2148
  left: `${element.position.x}px`,
1912
2149
  top: `${element.position.y}px`,
1913
2150
  width: size ? `${size.w}px` : "auto",
1914
- height: size ? `${size.h}px` : "auto"
2151
+ height: size ? `${size.h}px` : "auto",
2152
+ zIndex: String(zIndex)
1915
2153
  });
1916
2154
  this.renderDomContent(node, element);
1917
2155
  }
@@ -1946,26 +2184,6 @@ var Viewport = class {
1946
2184
  node.style.color = element.textColor;
1947
2185
  }
1948
2186
  }
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
2187
  if (element.type === "html" && !node.dataset["initialized"]) {
1970
2188
  const content = this.htmlContent.get(element.id);
1971
2189
  if (content) {
@@ -2048,6 +2266,10 @@ var Viewport = class {
2048
2266
  }
2049
2267
  }
2050
2268
  }
2269
+ hideDomNode(id) {
2270
+ const node = this.domNodes.get(id);
2271
+ if (node) node.style.display = "none";
2272
+ }
2051
2273
  removeDomNode(id) {
2052
2274
  this.htmlContent.delete(id);
2053
2275
  const node = this.domNodes.get(id);
@@ -2194,7 +2416,8 @@ var PencilTool = class {
2194
2416
  const stroke = createStroke({
2195
2417
  points: simplified,
2196
2418
  color: this.color,
2197
- width: this.width
2419
+ width: this.width,
2420
+ layerId: ctx.activeLayerId ?? ""
2198
2421
  });
2199
2422
  ctx.store.add(stroke);
2200
2423
  this.points = [];
@@ -2258,6 +2481,8 @@ var EraserTool = class {
2258
2481
  const strokes = ctx.store.getElementsByType("stroke");
2259
2482
  let erased = false;
2260
2483
  for (const stroke of strokes) {
2484
+ if (ctx.isLayerVisible && !ctx.isLayerVisible(stroke.layerId)) continue;
2485
+ if (ctx.isLayerLocked && ctx.isLayerLocked(stroke.layerId)) continue;
2261
2486
  if (this.strokeIntersects(stroke, world)) {
2262
2487
  ctx.store.remove(stroke.id);
2263
2488
  erased = true;
@@ -2743,6 +2968,8 @@ var SelectTool = class {
2743
2968
  findElementsInRect(marquee, ctx) {
2744
2969
  const ids = [];
2745
2970
  for (const el of ctx.store.getAll()) {
2971
+ if (ctx.isLayerVisible && !ctx.isLayerVisible(el.layerId)) continue;
2972
+ if (ctx.isLayerLocked && ctx.isLayerLocked(el.layerId)) continue;
2746
2973
  const bounds = this.getElementBounds(el);
2747
2974
  if (bounds && this.rectsOverlap(marquee, bounds)) {
2748
2975
  ids.push(el.id);
@@ -2777,6 +3004,8 @@ var SelectTool = class {
2777
3004
  hitTest(world, ctx) {
2778
3005
  const elements = ctx.store.getAll().reverse();
2779
3006
  for (const el of elements) {
3007
+ if (ctx.isLayerVisible && !ctx.isLayerVisible(el.layerId)) continue;
3008
+ if (ctx.isLayerLocked && ctx.isLayerLocked(el.layerId)) continue;
2780
3009
  if (this.isInsideBounds(world, el)) return el;
2781
3010
  }
2782
3011
  return null;
@@ -2821,11 +3050,20 @@ var ArrowTool = class {
2821
3050
  if (options.color !== void 0) this.color = options.color;
2822
3051
  if (options.width !== void 0) this.width = options.width;
2823
3052
  }
3053
+ layerFilter(ctx) {
3054
+ if (!ctx.isLayerVisible && !ctx.isLayerLocked) return void 0;
3055
+ return (el) => {
3056
+ if (ctx.isLayerVisible && !ctx.isLayerVisible(el.layerId)) return false;
3057
+ if (ctx.isLayerLocked && ctx.isLayerLocked(el.layerId)) return false;
3058
+ return true;
3059
+ };
3060
+ }
2824
3061
  onPointerDown(state, ctx) {
2825
3062
  this.drawing = true;
2826
3063
  const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
2827
3064
  const threshold = BIND_THRESHOLD2 / ctx.camera.zoom;
2828
- const target = findBindTarget(world, ctx.store, threshold);
3065
+ const filter = this.layerFilter(ctx);
3066
+ const target = findBindTarget(world, ctx.store, threshold, void 0, filter);
2829
3067
  if (target) {
2830
3068
  this.start = getElementCenter(target);
2831
3069
  this.fromBinding = { elementId: target.id };
@@ -2843,7 +3081,8 @@ var ArrowTool = class {
2843
3081
  const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
2844
3082
  const threshold = BIND_THRESHOLD2 / ctx.camera.zoom;
2845
3083
  const excludeId = this.fromBinding?.elementId;
2846
- const target = findBindTarget(world, ctx.store, threshold, excludeId);
3084
+ const filter = this.layerFilter(ctx);
3085
+ const target = findBindTarget(world, ctx.store, threshold, excludeId, filter);
2847
3086
  if (target) {
2848
3087
  this.end = getElementCenter(target);
2849
3088
  this.toTarget = target;
@@ -2864,7 +3103,8 @@ var ArrowTool = class {
2864
3103
  color: this.color,
2865
3104
  width: this.width,
2866
3105
  fromBinding: this.fromBinding,
2867
- toBinding: this.toTarget ? { elementId: this.toTarget.id } : void 0
3106
+ toBinding: this.toTarget ? { elementId: this.toTarget.id } : void 0,
3107
+ layerId: ctx.activeLayerId ?? ""
2868
3108
  });
2869
3109
  ctx.store.add(arrow);
2870
3110
  this.fromTarget = null;
@@ -2951,7 +3191,8 @@ var NoteTool = class {
2951
3191
  position: world,
2952
3192
  size: { ...this.size },
2953
3193
  backgroundColor: this.backgroundColor,
2954
- textColor: this.textColor
3194
+ textColor: this.textColor,
3195
+ layerId: ctx.activeLayerId ?? ""
2955
3196
  });
2956
3197
  ctx.store.add(note);
2957
3198
  ctx.requestRender();
@@ -2995,7 +3236,8 @@ var TextTool = class {
2995
3236
  position: world,
2996
3237
  fontSize: this.fontSize,
2997
3238
  color: this.color,
2998
- textAlign: this.textAlign
3239
+ textAlign: this.textAlign,
3240
+ layerId: ctx.activeLayerId ?? ""
2999
3241
  });
3000
3242
  ctx.store.add(textEl);
3001
3243
  ctx.requestRender();
@@ -3091,7 +3333,8 @@ var ShapeTool = class {
3091
3333
  shape: this.shape,
3092
3334
  strokeColor: this.strokeColor,
3093
3335
  strokeWidth: this.strokeWidth,
3094
- fillColor: this.fillColor
3336
+ fillColor: this.fillColor,
3337
+ layerId: ctx.activeLayerId ?? ""
3095
3338
  });
3096
3339
  ctx.store.add(shape);
3097
3340
  ctx.requestRender();
@@ -3152,8 +3395,48 @@ var ShapeTool = class {
3152
3395
  };
3153
3396
  };
3154
3397
 
3398
+ // src/history/layer-commands.ts
3399
+ var CreateLayerCommand = class {
3400
+ constructor(manager, layer) {
3401
+ this.manager = manager;
3402
+ this.layer = layer;
3403
+ }
3404
+ execute(_store) {
3405
+ this.manager.addLayerDirect(this.layer);
3406
+ }
3407
+ undo(_store) {
3408
+ this.manager.removeLayerDirect(this.layer.id);
3409
+ }
3410
+ };
3411
+ var RemoveLayerCommand = class {
3412
+ constructor(manager, layer) {
3413
+ this.manager = manager;
3414
+ this.layer = layer;
3415
+ }
3416
+ execute(_store) {
3417
+ this.manager.removeLayerDirect(this.layer.id);
3418
+ }
3419
+ undo(_store) {
3420
+ this.manager.addLayerDirect(this.layer);
3421
+ }
3422
+ };
3423
+ var UpdateLayerCommand = class {
3424
+ constructor(manager, layerId, previous, current) {
3425
+ this.manager = manager;
3426
+ this.layerId = layerId;
3427
+ this.previous = previous;
3428
+ this.current = current;
3429
+ }
3430
+ execute(_store) {
3431
+ this.manager.updateLayerDirect(this.layerId, { ...this.current });
3432
+ }
3433
+ undo(_store) {
3434
+ this.manager.updateLayerDirect(this.layerId, { ...this.previous });
3435
+ }
3436
+ };
3437
+
3155
3438
  // src/index.ts
3156
- var VERSION = "0.5.0";
3439
+ var VERSION = "0.6.0";
3157
3440
  // Annotate the CommonJS export names for ESM import in node:
3158
3441
  0 && (module.exports = {
3159
3442
  AddElementCommand,
@@ -3162,6 +3445,7 @@ var VERSION = "0.5.0";
3162
3445
  Background,
3163
3446
  BatchCommand,
3164
3447
  Camera,
3448
+ CreateLayerCommand,
3165
3449
  ElementRenderer,
3166
3450
  ElementStore,
3167
3451
  EraserTool,
@@ -3171,15 +3455,18 @@ var VERSION = "0.5.0";
3171
3455
  HistoryStack,
3172
3456
  ImageTool,
3173
3457
  InputHandler,
3458
+ LayerManager,
3174
3459
  NoteEditor,
3175
3460
  NoteTool,
3176
3461
  PencilTool,
3177
3462
  RemoveElementCommand,
3463
+ RemoveLayerCommand,
3178
3464
  SelectTool,
3179
3465
  ShapeTool,
3180
3466
  TextTool,
3181
3467
  ToolManager,
3182
3468
  UpdateElementCommand,
3469
+ UpdateLayerCommand,
3183
3470
  VERSION,
3184
3471
  Viewport,
3185
3472
  clearStaleBindings,