@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.js CHANGED
@@ -23,15 +23,16 @@ var EventBus = class {
23
23
  };
24
24
 
25
25
  // src/core/state-serializer.ts
26
- var CURRENT_VERSION = 1;
27
- function exportState(elements, camera) {
26
+ var CURRENT_VERSION = 2;
27
+ function exportState(elements, camera, layers = []) {
28
28
  return {
29
29
  version: CURRENT_VERSION,
30
30
  camera: {
31
31
  position: { ...camera.position },
32
32
  zoom: camera.zoom
33
33
  },
34
- elements: elements.map((el) => structuredClone(el))
34
+ elements: elements.map((el) => structuredClone(el)),
35
+ layers: layers.map((l) => ({ ...l }))
35
36
  };
36
37
  }
37
38
  function parseState(json) {
@@ -69,6 +70,19 @@ function validateState(data) {
69
70
  migrateElement(el);
70
71
  }
71
72
  cleanBindings(obj["elements"]);
73
+ const layers = obj["layers"];
74
+ if (!Array.isArray(layers) || layers.length === 0) {
75
+ obj["layers"] = [
76
+ {
77
+ id: "default-layer",
78
+ name: "Layer 1",
79
+ visible: true,
80
+ locked: false,
81
+ order: 0,
82
+ opacity: 1
83
+ }
84
+ ];
85
+ }
72
86
  }
73
87
  var VALID_TYPES = /* @__PURE__ */ new Set(["stroke", "note", "arrow", "image", "html", "text", "shape"]);
74
88
  function validateElement(el) {
@@ -101,6 +115,9 @@ function cleanBindings(elements) {
101
115
  }
102
116
  }
103
117
  function migrateElement(obj) {
118
+ if (typeof obj["layerId"] !== "string") {
119
+ obj["layerId"] = "default-layer";
120
+ }
104
121
  if (obj["type"] === "arrow" && typeof obj["bend"] !== "number") {
105
122
  obj["bend"] = 0;
106
123
  }
@@ -136,9 +153,11 @@ var AutoSave = class {
136
153
  this.camera = camera;
137
154
  this.key = options.key ?? DEFAULT_KEY;
138
155
  this.debounceMs = options.debounceMs ?? DEFAULT_DEBOUNCE_MS;
156
+ this.layerManager = options.layerManager;
139
157
  }
140
158
  key;
141
159
  debounceMs;
160
+ layerManager;
142
161
  timerId = null;
143
162
  unsubscribers = [];
144
163
  start() {
@@ -149,6 +168,9 @@ var AutoSave = class {
149
168
  this.store.on("update", schedule),
150
169
  this.camera.onChange(schedule)
151
170
  ];
171
+ if (this.layerManager) {
172
+ this.unsubscribers.push(this.layerManager.on("change", schedule));
173
+ }
152
174
  }
153
175
  stop() {
154
176
  this.cancelPending();
@@ -181,8 +203,13 @@ var AutoSave = class {
181
203
  }
182
204
  save() {
183
205
  if (typeof localStorage === "undefined") return;
184
- const state = exportState(this.store.snapshot(), this.camera);
185
- localStorage.setItem(this.key, JSON.stringify(state));
206
+ const layers = this.layerManager?.snapshot() ?? [];
207
+ const state = exportState(this.store.snapshot(), this.camera, layers);
208
+ try {
209
+ localStorage.setItem(this.key, JSON.stringify(state));
210
+ } catch {
211
+ console.warn("Auto-save failed: storage quota exceeded. State too large for localStorage.");
212
+ }
186
213
  }
187
214
  };
188
215
 
@@ -558,11 +585,20 @@ var InputHandler = class {
558
585
  var ElementStore = class {
559
586
  elements = /* @__PURE__ */ new Map();
560
587
  bus = new EventBus();
588
+ layerOrderMap = /* @__PURE__ */ new Map();
561
589
  get count() {
562
590
  return this.elements.size;
563
591
  }
592
+ setLayerOrder(order) {
593
+ this.layerOrderMap = new Map(order);
594
+ }
564
595
  getAll() {
565
- return [...this.elements.values()].sort((a, b) => a.zIndex - b.zIndex);
596
+ return [...this.elements.values()].sort((a, b) => {
597
+ const layerA = this.layerOrderMap.get(a.layerId) ?? 0;
598
+ const layerB = this.layerOrderMap.get(b.layerId) ?? 0;
599
+ if (layerA !== layerB) return layerA - layerB;
600
+ return a.zIndex - b.zIndex;
601
+ });
566
602
  }
567
603
  getById(id) {
568
604
  return this.elements.get(id);
@@ -746,12 +782,13 @@ function getEdgeIntersection(bounds, outsidePoint) {
746
782
  y: cy + dy * scale
747
783
  };
748
784
  }
749
- function findBindTarget(point, store, threshold, excludeId) {
785
+ function findBindTarget(point, store, threshold, excludeId, filter) {
750
786
  let closest = null;
751
787
  let closestDist = Infinity;
752
788
  for (const el of store.getAll()) {
753
789
  if (!isBindable(el)) continue;
754
790
  if (excludeId && el.id === excludeId) continue;
791
+ if (filter && !filter(el)) continue;
755
792
  const bounds = getElementBounds(el);
756
793
  if (!bounds) continue;
757
794
  const dist = distToBounds(point, bounds);
@@ -909,14 +946,19 @@ function smoothToSegments(points) {
909
946
  }
910
947
 
911
948
  // src/elements/element-renderer.ts
912
- var DOM_ELEMENT_TYPES = /* @__PURE__ */ new Set(["note", "image", "html", "text"]);
949
+ var DOM_ELEMENT_TYPES = /* @__PURE__ */ new Set(["note", "html", "text"]);
913
950
  var ARROWHEAD_LENGTH = 12;
914
951
  var ARROWHEAD_ANGLE = Math.PI / 6;
915
952
  var ElementRenderer = class {
916
953
  store = null;
954
+ imageCache = /* @__PURE__ */ new Map();
955
+ onImageLoad = null;
917
956
  setStore(store) {
918
957
  this.store = store;
919
958
  }
959
+ setOnImageLoad(callback) {
960
+ this.onImageLoad = callback;
961
+ }
920
962
  isDomElement(element) {
921
963
  return DOM_ELEMENT_TYPES.has(element.type);
922
964
  }
@@ -931,6 +973,9 @@ var ElementRenderer = class {
931
973
  case "shape":
932
974
  this.renderShape(ctx, element);
933
975
  break;
976
+ case "image":
977
+ this.renderImage(ctx, element);
978
+ break;
934
979
  }
935
980
  }
936
981
  renderStroke(ctx, stroke) {
@@ -1066,6 +1111,20 @@ var ElementRenderer = class {
1066
1111
  }
1067
1112
  }
1068
1113
  }
1114
+ renderImage(ctx, image) {
1115
+ const img = this.getImage(image.src);
1116
+ if (!img) return;
1117
+ ctx.drawImage(img, image.position.x, image.position.y, image.size.w, image.size.h);
1118
+ }
1119
+ getImage(src) {
1120
+ const cached = this.imageCache.get(src);
1121
+ if (cached) return cached.complete ? cached : null;
1122
+ const img = new Image();
1123
+ img.src = src;
1124
+ this.imageCache.set(src, img);
1125
+ img.onload = () => this.onImageLoad?.();
1126
+ return null;
1127
+ }
1069
1128
  };
1070
1129
 
1071
1130
  // src/elements/note-editor.ts
@@ -1417,6 +1476,7 @@ function createStroke(input) {
1417
1476
  position: input.position ?? { x: 0, y: 0 },
1418
1477
  zIndex: input.zIndex ?? 0,
1419
1478
  locked: input.locked ?? false,
1479
+ layerId: input.layerId ?? "",
1420
1480
  points: input.points,
1421
1481
  color: input.color ?? "#000000",
1422
1482
  width: input.width ?? 2,
@@ -1430,6 +1490,7 @@ function createNote(input) {
1430
1490
  position: input.position,
1431
1491
  zIndex: input.zIndex ?? 0,
1432
1492
  locked: input.locked ?? false,
1493
+ layerId: input.layerId ?? "",
1433
1494
  size: input.size ?? { w: 200, h: 100 },
1434
1495
  text: input.text ?? "",
1435
1496
  backgroundColor: input.backgroundColor ?? "#ffeb3b",
@@ -1443,6 +1504,7 @@ function createArrow(input) {
1443
1504
  position: input.position ?? { x: 0, y: 0 },
1444
1505
  zIndex: input.zIndex ?? 0,
1445
1506
  locked: input.locked ?? false,
1507
+ layerId: input.layerId ?? "",
1446
1508
  from: input.from,
1447
1509
  to: input.to,
1448
1510
  bend: input.bend ?? 0,
@@ -1460,6 +1522,7 @@ function createImage(input) {
1460
1522
  position: input.position,
1461
1523
  zIndex: input.zIndex ?? 0,
1462
1524
  locked: input.locked ?? false,
1525
+ layerId: input.layerId ?? "",
1463
1526
  size: input.size,
1464
1527
  src: input.src
1465
1528
  };
@@ -1471,6 +1534,7 @@ function createHtmlElement(input) {
1471
1534
  position: input.position,
1472
1535
  zIndex: input.zIndex ?? 0,
1473
1536
  locked: input.locked ?? false,
1537
+ layerId: input.layerId ?? "",
1474
1538
  size: input.size
1475
1539
  };
1476
1540
  }
@@ -1481,6 +1545,7 @@ function createShape(input) {
1481
1545
  position: input.position,
1482
1546
  zIndex: input.zIndex ?? 0,
1483
1547
  locked: input.locked ?? false,
1548
+ layerId: input.layerId ?? "",
1484
1549
  shape: input.shape ?? "rectangle",
1485
1550
  size: input.size,
1486
1551
  strokeColor: input.strokeColor ?? "#000000",
@@ -1495,6 +1560,7 @@ function createText(input) {
1495
1560
  position: input.position,
1496
1561
  zIndex: input.zIndex ?? 0,
1497
1562
  locked: input.locked ?? false,
1563
+ layerId: input.layerId ?? "",
1498
1564
  size: input.size ?? { w: 200, h: 28 },
1499
1565
  text: input.text ?? "",
1500
1566
  fontSize: input.fontSize ?? 16,
@@ -1503,6 +1569,155 @@ function createText(input) {
1503
1569
  };
1504
1570
  }
1505
1571
 
1572
+ // src/layers/layer-manager.ts
1573
+ var LayerManager = class {
1574
+ constructor(store) {
1575
+ this.store = store;
1576
+ const defaultLayer = {
1577
+ id: createId("layer"),
1578
+ name: "Layer 1",
1579
+ visible: true,
1580
+ locked: false,
1581
+ order: 0,
1582
+ opacity: 1
1583
+ };
1584
+ this.layers.set(defaultLayer.id, defaultLayer);
1585
+ this._activeLayerId = defaultLayer.id;
1586
+ this.syncLayerOrder();
1587
+ }
1588
+ layers = /* @__PURE__ */ new Map();
1589
+ _activeLayerId;
1590
+ bus = new EventBus();
1591
+ get activeLayer() {
1592
+ const layer = this.layers.get(this._activeLayerId);
1593
+ if (!layer) throw new Error("Active layer not found");
1594
+ return { ...layer };
1595
+ }
1596
+ get activeLayerId() {
1597
+ return this._activeLayerId;
1598
+ }
1599
+ getLayers() {
1600
+ return [...this.layers.values()].sort((a, b) => a.order - b.order).map((l) => ({ ...l }));
1601
+ }
1602
+ getLayer(id) {
1603
+ const layer = this.layers.get(id);
1604
+ return layer ? { ...layer } : void 0;
1605
+ }
1606
+ isLayerVisible(id) {
1607
+ return this.layers.get(id)?.visible ?? true;
1608
+ }
1609
+ isLayerLocked(id) {
1610
+ return this.layers.get(id)?.locked ?? false;
1611
+ }
1612
+ createLayer(name) {
1613
+ const maxOrder = Math.max(...[...this.layers.values()].map((l) => l.order), -1);
1614
+ const autoName = name ?? `Layer ${this.layers.size + 1}`;
1615
+ const layer = {
1616
+ id: createId("layer"),
1617
+ name: autoName,
1618
+ visible: true,
1619
+ locked: false,
1620
+ order: maxOrder + 1,
1621
+ opacity: 1
1622
+ };
1623
+ this.addLayerDirect(layer);
1624
+ return { ...layer };
1625
+ }
1626
+ removeLayer(id) {
1627
+ if (this.layers.size <= 1) {
1628
+ throw new Error("Cannot remove the last layer");
1629
+ }
1630
+ if (this._activeLayerId === id) {
1631
+ const remaining = [...this.layers.values()].filter((l) => l.id !== id).sort((a, b) => b.order - a.order);
1632
+ const fallback = remaining[0];
1633
+ if (fallback) this._activeLayerId = fallback.id;
1634
+ }
1635
+ const elements = this.store.getAll().filter((el) => el.layerId === id);
1636
+ for (const el of elements) {
1637
+ this.store.update(el.id, { layerId: this._activeLayerId });
1638
+ }
1639
+ this.removeLayerDirect(id);
1640
+ }
1641
+ renameLayer(id, name) {
1642
+ this.updateLayerDirect(id, { name });
1643
+ }
1644
+ reorderLayer(id, newOrder) {
1645
+ if (!this.layers.has(id)) return;
1646
+ this.updateLayerDirect(id, { order: newOrder });
1647
+ }
1648
+ setLayerVisible(id, visible) {
1649
+ if (!visible && id === this._activeLayerId) {
1650
+ const fallback = this.findFallbackLayer(id);
1651
+ if (!fallback) return false;
1652
+ this._activeLayerId = fallback.id;
1653
+ }
1654
+ this.updateLayerDirect(id, { visible });
1655
+ return true;
1656
+ }
1657
+ setLayerLocked(id, locked) {
1658
+ if (locked && id === this._activeLayerId) {
1659
+ const fallback = this.findFallbackLayer(id);
1660
+ if (!fallback) return false;
1661
+ this._activeLayerId = fallback.id;
1662
+ }
1663
+ this.updateLayerDirect(id, { locked });
1664
+ return true;
1665
+ }
1666
+ setActiveLayer(id) {
1667
+ if (!this.layers.has(id)) return;
1668
+ this._activeLayerId = id;
1669
+ this.bus.emit("change", null);
1670
+ }
1671
+ moveElementToLayer(elementId, layerId) {
1672
+ if (!this.layers.has(layerId)) return;
1673
+ this.store.update(elementId, { layerId });
1674
+ this.bus.emit("change", null);
1675
+ }
1676
+ snapshot() {
1677
+ return this.getLayers();
1678
+ }
1679
+ loadSnapshot(layers) {
1680
+ this.layers.clear();
1681
+ for (const layer of layers) {
1682
+ this.layers.set(layer.id, { ...layer });
1683
+ }
1684
+ const first = this.getLayers()[0];
1685
+ if (first) this._activeLayerId = first.id;
1686
+ this.syncLayerOrder();
1687
+ this.bus.emit("change", null);
1688
+ }
1689
+ on(event, callback) {
1690
+ return this.bus.on(event, callback);
1691
+ }
1692
+ addLayerDirect(layer) {
1693
+ this.layers.set(layer.id, { ...layer });
1694
+ this.syncLayerOrder();
1695
+ this.bus.emit("change", null);
1696
+ }
1697
+ removeLayerDirect(id) {
1698
+ this.layers.delete(id);
1699
+ this.syncLayerOrder();
1700
+ this.bus.emit("change", null);
1701
+ }
1702
+ updateLayerDirect(id, props) {
1703
+ const layer = this.layers.get(id);
1704
+ if (!layer) return;
1705
+ Object.assign(layer, props);
1706
+ if ("order" in props) this.syncLayerOrder();
1707
+ this.bus.emit("change", null);
1708
+ }
1709
+ syncLayerOrder() {
1710
+ const order = /* @__PURE__ */ new Map();
1711
+ for (const layer of this.layers.values()) {
1712
+ order.set(layer.id, layer.order);
1713
+ }
1714
+ this.store.setLayerOrder(order);
1715
+ }
1716
+ findFallbackLayer(excludeId) {
1717
+ return [...this.layers.values()].filter((l) => l.id !== excludeId && l.visible && !l.locked).sort((a, b) => b.order - a.order)[0];
1718
+ }
1719
+ };
1720
+
1506
1721
  // src/canvas/viewport.ts
1507
1722
  var Viewport = class {
1508
1723
  constructor(container, options = {}) {
@@ -1511,9 +1726,11 @@ var Viewport = class {
1511
1726
  this.background = new Background(options.background);
1512
1727
  this._gridSize = options.background?.spacing ?? 24;
1513
1728
  this.store = new ElementStore();
1729
+ this.layerManager = new LayerManager(this.store);
1514
1730
  this.toolManager = new ToolManager();
1515
1731
  this.renderer = new ElementRenderer();
1516
1732
  this.renderer.setStore(this.store);
1733
+ this.renderer.setOnImageLoad(() => this.requestRender());
1517
1734
  this.noteEditor = new NoteEditor();
1518
1735
  this.noteEditor.setOnStop((id) => this.onTextEditStop(id));
1519
1736
  this.history = new HistoryStack();
@@ -1534,7 +1751,10 @@ var Viewport = class {
1534
1751
  this.wrapper.style.cursor = cursor;
1535
1752
  },
1536
1753
  snapToGrid: false,
1537
- gridSize: this._gridSize
1754
+ gridSize: this._gridSize,
1755
+ activeLayerId: this.layerManager.activeLayerId,
1756
+ isLayerVisible: (id) => this.layerManager.isLayerVisible(id),
1757
+ isLayerLocked: (id) => this.layerManager.isLayerLocked(id)
1538
1758
  };
1539
1759
  this.inputHandler = new InputHandler(this.wrapper, this.camera, {
1540
1760
  toolManager: this.toolManager,
@@ -1555,6 +1775,10 @@ var Viewport = class {
1555
1775
  this.store.on("update", () => this.requestRender()),
1556
1776
  this.store.on("clear", () => this.clearDomNodes())
1557
1777
  ];
1778
+ this.layerManager.on("change", () => {
1779
+ this.toolContext.activeLayerId = this.layerManager.activeLayerId;
1780
+ this.requestRender();
1781
+ });
1558
1782
  this.wrapper.addEventListener("dblclick", this.onDblClick);
1559
1783
  this.wrapper.addEventListener("dragover", this.onDragOver);
1560
1784
  this.wrapper.addEventListener("drop", this.onDrop);
@@ -1564,6 +1788,7 @@ var Viewport = class {
1564
1788
  }
1565
1789
  camera;
1566
1790
  store;
1791
+ layerManager;
1567
1792
  toolManager;
1568
1793
  history;
1569
1794
  domLayer;
@@ -1599,7 +1824,7 @@ var Viewport = class {
1599
1824
  this.needsRender = true;
1600
1825
  }
1601
1826
  exportState() {
1602
- return exportState(this.store.snapshot(), this.camera);
1827
+ return exportState(this.store.snapshot(), this.camera, this.layerManager.snapshot());
1603
1828
  }
1604
1829
  exportJSON() {
1605
1830
  return JSON.stringify(this.exportState());
@@ -1609,6 +1834,9 @@ var Viewport = class {
1609
1834
  this.noteEditor.destroy(this.store);
1610
1835
  this.clearDomNodes();
1611
1836
  this.store.loadSnapshot(state.elements);
1837
+ if (state.layers && state.layers.length > 0) {
1838
+ this.layerManager.loadSnapshot(state.layers);
1839
+ }
1612
1840
  this.history.clear();
1613
1841
  this.historyRecorder.resume();
1614
1842
  this.camera.moveTo(state.camera.position.x, state.camera.position.y);
@@ -1632,7 +1860,7 @@ var Viewport = class {
1632
1860
  return result;
1633
1861
  }
1634
1862
  addImage(src, position, size = { w: 300, h: 200 }) {
1635
- const image = createImage({ position, size, src });
1863
+ const image = createImage({ position, size, src, layerId: this.layerManager.activeLayerId });
1636
1864
  this.historyRecorder.begin();
1637
1865
  this.store.add(image);
1638
1866
  this.historyRecorder.commit();
@@ -1640,7 +1868,7 @@ var Viewport = class {
1640
1868
  return image.id;
1641
1869
  }
1642
1870
  addHtmlElement(dom, position, size = { w: 200, h: 150 }) {
1643
- const el = createHtmlElement({ position, size });
1871
+ const el = createHtmlElement({ position, size, layerId: this.layerManager.activeLayerId });
1644
1872
  this.htmlContent.set(el.id, dom);
1645
1873
  this.historyRecorder.begin();
1646
1874
  this.store.add(el);
@@ -1683,9 +1911,17 @@ var Viewport = class {
1683
1911
  ctx.save();
1684
1912
  ctx.translate(this.camera.position.x, this.camera.position.y);
1685
1913
  ctx.scale(this.camera.zoom, this.camera.zoom);
1686
- for (const element of this.store.getAll()) {
1914
+ const allElements = this.store.getAll();
1915
+ let domZIndex = 0;
1916
+ for (const element of allElements) {
1917
+ if (!this.layerManager.isLayerVisible(element.layerId)) {
1918
+ if (this.renderer.isDomElement(element)) {
1919
+ this.hideDomNode(element.id);
1920
+ }
1921
+ continue;
1922
+ }
1687
1923
  if (this.renderer.isDomElement(element)) {
1688
- this.syncDomNode(element);
1924
+ this.syncDomNode(element, domZIndex++);
1689
1925
  } else {
1690
1926
  this.renderer.renderCanvasElement(ctx, element);
1691
1927
  }
@@ -1817,7 +2053,7 @@ var Viewport = class {
1817
2053
  reader.readAsDataURL(file);
1818
2054
  }
1819
2055
  };
1820
- syncDomNode(element) {
2056
+ syncDomNode(element, zIndex = 0) {
1821
2057
  let node = this.domNodes.get(element.id);
1822
2058
  if (!node) {
1823
2059
  node = document.createElement("div");
@@ -1831,10 +2067,12 @@ var Viewport = class {
1831
2067
  }
1832
2068
  const size = "size" in element ? element.size : null;
1833
2069
  Object.assign(node.style, {
2070
+ display: "block",
1834
2071
  left: `${element.position.x}px`,
1835
2072
  top: `${element.position.y}px`,
1836
2073
  width: size ? `${size.w}px` : "auto",
1837
- height: size ? `${size.h}px` : "auto"
2074
+ height: size ? `${size.h}px` : "auto",
2075
+ zIndex: String(zIndex)
1838
2076
  });
1839
2077
  this.renderDomContent(node, element);
1840
2078
  }
@@ -1869,26 +2107,6 @@ var Viewport = class {
1869
2107
  node.style.color = element.textColor;
1870
2108
  }
1871
2109
  }
1872
- if (element.type === "image") {
1873
- if (!node.dataset["initialized"]) {
1874
- node.dataset["initialized"] = "true";
1875
- const img = document.createElement("img");
1876
- img.src = element.src;
1877
- Object.assign(img.style, {
1878
- width: "100%",
1879
- height: "100%",
1880
- objectFit: "contain",
1881
- pointerEvents: "none"
1882
- });
1883
- img.draggable = false;
1884
- node.appendChild(img);
1885
- } else {
1886
- const img = node.querySelector("img");
1887
- if (img && img.src !== element.src) {
1888
- img.src = element.src;
1889
- }
1890
- }
1891
- }
1892
2110
  if (element.type === "html" && !node.dataset["initialized"]) {
1893
2111
  const content = this.htmlContent.get(element.id);
1894
2112
  if (content) {
@@ -1971,6 +2189,10 @@ var Viewport = class {
1971
2189
  }
1972
2190
  }
1973
2191
  }
2192
+ hideDomNode(id) {
2193
+ const node = this.domNodes.get(id);
2194
+ if (node) node.style.display = "none";
2195
+ }
1974
2196
  removeDomNode(id) {
1975
2197
  this.htmlContent.delete(id);
1976
2198
  const node = this.domNodes.get(id);
@@ -2117,7 +2339,8 @@ var PencilTool = class {
2117
2339
  const stroke = createStroke({
2118
2340
  points: simplified,
2119
2341
  color: this.color,
2120
- width: this.width
2342
+ width: this.width,
2343
+ layerId: ctx.activeLayerId ?? ""
2121
2344
  });
2122
2345
  ctx.store.add(stroke);
2123
2346
  this.points = [];
@@ -2181,6 +2404,8 @@ var EraserTool = class {
2181
2404
  const strokes = ctx.store.getElementsByType("stroke");
2182
2405
  let erased = false;
2183
2406
  for (const stroke of strokes) {
2407
+ if (ctx.isLayerVisible && !ctx.isLayerVisible(stroke.layerId)) continue;
2408
+ if (ctx.isLayerLocked && ctx.isLayerLocked(stroke.layerId)) continue;
2184
2409
  if (this.strokeIntersects(stroke, world)) {
2185
2410
  ctx.store.remove(stroke.id);
2186
2411
  erased = true;
@@ -2666,6 +2891,8 @@ var SelectTool = class {
2666
2891
  findElementsInRect(marquee, ctx) {
2667
2892
  const ids = [];
2668
2893
  for (const el of ctx.store.getAll()) {
2894
+ if (ctx.isLayerVisible && !ctx.isLayerVisible(el.layerId)) continue;
2895
+ if (ctx.isLayerLocked && ctx.isLayerLocked(el.layerId)) continue;
2669
2896
  const bounds = this.getElementBounds(el);
2670
2897
  if (bounds && this.rectsOverlap(marquee, bounds)) {
2671
2898
  ids.push(el.id);
@@ -2700,6 +2927,8 @@ var SelectTool = class {
2700
2927
  hitTest(world, ctx) {
2701
2928
  const elements = ctx.store.getAll().reverse();
2702
2929
  for (const el of elements) {
2930
+ if (ctx.isLayerVisible && !ctx.isLayerVisible(el.layerId)) continue;
2931
+ if (ctx.isLayerLocked && ctx.isLayerLocked(el.layerId)) continue;
2703
2932
  if (this.isInsideBounds(world, el)) return el;
2704
2933
  }
2705
2934
  return null;
@@ -2744,11 +2973,20 @@ var ArrowTool = class {
2744
2973
  if (options.color !== void 0) this.color = options.color;
2745
2974
  if (options.width !== void 0) this.width = options.width;
2746
2975
  }
2976
+ layerFilter(ctx) {
2977
+ if (!ctx.isLayerVisible && !ctx.isLayerLocked) return void 0;
2978
+ return (el) => {
2979
+ if (ctx.isLayerVisible && !ctx.isLayerVisible(el.layerId)) return false;
2980
+ if (ctx.isLayerLocked && ctx.isLayerLocked(el.layerId)) return false;
2981
+ return true;
2982
+ };
2983
+ }
2747
2984
  onPointerDown(state, ctx) {
2748
2985
  this.drawing = true;
2749
2986
  const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
2750
2987
  const threshold = BIND_THRESHOLD2 / ctx.camera.zoom;
2751
- const target = findBindTarget(world, ctx.store, threshold);
2988
+ const filter = this.layerFilter(ctx);
2989
+ const target = findBindTarget(world, ctx.store, threshold, void 0, filter);
2752
2990
  if (target) {
2753
2991
  this.start = getElementCenter(target);
2754
2992
  this.fromBinding = { elementId: target.id };
@@ -2766,7 +3004,8 @@ var ArrowTool = class {
2766
3004
  const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
2767
3005
  const threshold = BIND_THRESHOLD2 / ctx.camera.zoom;
2768
3006
  const excludeId = this.fromBinding?.elementId;
2769
- const target = findBindTarget(world, ctx.store, threshold, excludeId);
3007
+ const filter = this.layerFilter(ctx);
3008
+ const target = findBindTarget(world, ctx.store, threshold, excludeId, filter);
2770
3009
  if (target) {
2771
3010
  this.end = getElementCenter(target);
2772
3011
  this.toTarget = target;
@@ -2787,7 +3026,8 @@ var ArrowTool = class {
2787
3026
  color: this.color,
2788
3027
  width: this.width,
2789
3028
  fromBinding: this.fromBinding,
2790
- toBinding: this.toTarget ? { elementId: this.toTarget.id } : void 0
3029
+ toBinding: this.toTarget ? { elementId: this.toTarget.id } : void 0,
3030
+ layerId: ctx.activeLayerId ?? ""
2791
3031
  });
2792
3032
  ctx.store.add(arrow);
2793
3033
  this.fromTarget = null;
@@ -2874,7 +3114,8 @@ var NoteTool = class {
2874
3114
  position: world,
2875
3115
  size: { ...this.size },
2876
3116
  backgroundColor: this.backgroundColor,
2877
- textColor: this.textColor
3117
+ textColor: this.textColor,
3118
+ layerId: ctx.activeLayerId ?? ""
2878
3119
  });
2879
3120
  ctx.store.add(note);
2880
3121
  ctx.requestRender();
@@ -2918,7 +3159,8 @@ var TextTool = class {
2918
3159
  position: world,
2919
3160
  fontSize: this.fontSize,
2920
3161
  color: this.color,
2921
- textAlign: this.textAlign
3162
+ textAlign: this.textAlign,
3163
+ layerId: ctx.activeLayerId ?? ""
2922
3164
  });
2923
3165
  ctx.store.add(textEl);
2924
3166
  ctx.requestRender();
@@ -3014,7 +3256,8 @@ var ShapeTool = class {
3014
3256
  shape: this.shape,
3015
3257
  strokeColor: this.strokeColor,
3016
3258
  strokeWidth: this.strokeWidth,
3017
- fillColor: this.fillColor
3259
+ fillColor: this.fillColor,
3260
+ layerId: ctx.activeLayerId ?? ""
3018
3261
  });
3019
3262
  ctx.store.add(shape);
3020
3263
  ctx.requestRender();
@@ -3075,8 +3318,48 @@ var ShapeTool = class {
3075
3318
  };
3076
3319
  };
3077
3320
 
3321
+ // src/history/layer-commands.ts
3322
+ var CreateLayerCommand = class {
3323
+ constructor(manager, layer) {
3324
+ this.manager = manager;
3325
+ this.layer = layer;
3326
+ }
3327
+ execute(_store) {
3328
+ this.manager.addLayerDirect(this.layer);
3329
+ }
3330
+ undo(_store) {
3331
+ this.manager.removeLayerDirect(this.layer.id);
3332
+ }
3333
+ };
3334
+ var RemoveLayerCommand = class {
3335
+ constructor(manager, layer) {
3336
+ this.manager = manager;
3337
+ this.layer = layer;
3338
+ }
3339
+ execute(_store) {
3340
+ this.manager.removeLayerDirect(this.layer.id);
3341
+ }
3342
+ undo(_store) {
3343
+ this.manager.addLayerDirect(this.layer);
3344
+ }
3345
+ };
3346
+ var UpdateLayerCommand = class {
3347
+ constructor(manager, layerId, previous, current) {
3348
+ this.manager = manager;
3349
+ this.layerId = layerId;
3350
+ this.previous = previous;
3351
+ this.current = current;
3352
+ }
3353
+ execute(_store) {
3354
+ this.manager.updateLayerDirect(this.layerId, { ...this.current });
3355
+ }
3356
+ undo(_store) {
3357
+ this.manager.updateLayerDirect(this.layerId, { ...this.previous });
3358
+ }
3359
+ };
3360
+
3078
3361
  // src/index.ts
3079
- var VERSION = "0.5.0";
3362
+ var VERSION = "0.6.1";
3080
3363
  export {
3081
3364
  AddElementCommand,
3082
3365
  ArrowTool,
@@ -3084,6 +3367,7 @@ export {
3084
3367
  Background,
3085
3368
  BatchCommand,
3086
3369
  Camera,
3370
+ CreateLayerCommand,
3087
3371
  ElementRenderer,
3088
3372
  ElementStore,
3089
3373
  EraserTool,
@@ -3093,15 +3377,18 @@ export {
3093
3377
  HistoryStack,
3094
3378
  ImageTool,
3095
3379
  InputHandler,
3380
+ LayerManager,
3096
3381
  NoteEditor,
3097
3382
  NoteTool,
3098
3383
  PencilTool,
3099
3384
  RemoveElementCommand,
3385
+ RemoveLayerCommand,
3100
3386
  SelectTool,
3101
3387
  ShapeTool,
3102
3388
  TextTool,
3103
3389
  ToolManager,
3104
3390
  UpdateElementCommand,
3391
+ UpdateLayerCommand,
3105
3392
  VERSION,
3106
3393
  Viewport,
3107
3394
  clearStaleBindings,