@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.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,7 +203,8 @@ var AutoSave = class {
181
203
  }
182
204
  save() {
183
205
  if (typeof localStorage === "undefined") return;
184
- const state = exportState(this.store.snapshot(), this.camera);
206
+ const layers = this.layerManager?.snapshot() ?? [];
207
+ const state = exportState(this.store.snapshot(), this.camera, layers);
185
208
  localStorage.setItem(this.key, JSON.stringify(state));
186
209
  }
187
210
  };
@@ -558,11 +581,20 @@ var InputHandler = class {
558
581
  var ElementStore = class {
559
582
  elements = /* @__PURE__ */ new Map();
560
583
  bus = new EventBus();
584
+ layerOrderMap = /* @__PURE__ */ new Map();
561
585
  get count() {
562
586
  return this.elements.size;
563
587
  }
588
+ setLayerOrder(order) {
589
+ this.layerOrderMap = new Map(order);
590
+ }
564
591
  getAll() {
565
- return [...this.elements.values()].sort((a, b) => a.zIndex - b.zIndex);
592
+ return [...this.elements.values()].sort((a, b) => {
593
+ const layerA = this.layerOrderMap.get(a.layerId) ?? 0;
594
+ const layerB = this.layerOrderMap.get(b.layerId) ?? 0;
595
+ if (layerA !== layerB) return layerA - layerB;
596
+ return a.zIndex - b.zIndex;
597
+ });
566
598
  }
567
599
  getById(id) {
568
600
  return this.elements.get(id);
@@ -746,12 +778,13 @@ function getEdgeIntersection(bounds, outsidePoint) {
746
778
  y: cy + dy * scale
747
779
  };
748
780
  }
749
- function findBindTarget(point, store, threshold, excludeId) {
781
+ function findBindTarget(point, store, threshold, excludeId, filter) {
750
782
  let closest = null;
751
783
  let closestDist = Infinity;
752
784
  for (const el of store.getAll()) {
753
785
  if (!isBindable(el)) continue;
754
786
  if (excludeId && el.id === excludeId) continue;
787
+ if (filter && !filter(el)) continue;
755
788
  const bounds = getElementBounds(el);
756
789
  if (!bounds) continue;
757
790
  const dist = distToBounds(point, bounds);
@@ -909,14 +942,19 @@ function smoothToSegments(points) {
909
942
  }
910
943
 
911
944
  // src/elements/element-renderer.ts
912
- var DOM_ELEMENT_TYPES = /* @__PURE__ */ new Set(["note", "image", "html", "text"]);
945
+ var DOM_ELEMENT_TYPES = /* @__PURE__ */ new Set(["note", "html", "text"]);
913
946
  var ARROWHEAD_LENGTH = 12;
914
947
  var ARROWHEAD_ANGLE = Math.PI / 6;
915
948
  var ElementRenderer = class {
916
949
  store = null;
950
+ imageCache = /* @__PURE__ */ new Map();
951
+ onImageLoad = null;
917
952
  setStore(store) {
918
953
  this.store = store;
919
954
  }
955
+ setOnImageLoad(callback) {
956
+ this.onImageLoad = callback;
957
+ }
920
958
  isDomElement(element) {
921
959
  return DOM_ELEMENT_TYPES.has(element.type);
922
960
  }
@@ -931,6 +969,9 @@ var ElementRenderer = class {
931
969
  case "shape":
932
970
  this.renderShape(ctx, element);
933
971
  break;
972
+ case "image":
973
+ this.renderImage(ctx, element);
974
+ break;
934
975
  }
935
976
  }
936
977
  renderStroke(ctx, stroke) {
@@ -1066,6 +1107,20 @@ var ElementRenderer = class {
1066
1107
  }
1067
1108
  }
1068
1109
  }
1110
+ renderImage(ctx, image) {
1111
+ const img = this.getImage(image.src);
1112
+ if (!img) return;
1113
+ ctx.drawImage(img, image.position.x, image.position.y, image.size.w, image.size.h);
1114
+ }
1115
+ getImage(src) {
1116
+ const cached = this.imageCache.get(src);
1117
+ if (cached) return cached.complete ? cached : null;
1118
+ const img = new Image();
1119
+ img.src = src;
1120
+ this.imageCache.set(src, img);
1121
+ img.onload = () => this.onImageLoad?.();
1122
+ return null;
1123
+ }
1069
1124
  };
1070
1125
 
1071
1126
  // src/elements/note-editor.ts
@@ -1417,6 +1472,7 @@ function createStroke(input) {
1417
1472
  position: input.position ?? { x: 0, y: 0 },
1418
1473
  zIndex: input.zIndex ?? 0,
1419
1474
  locked: input.locked ?? false,
1475
+ layerId: input.layerId ?? "",
1420
1476
  points: input.points,
1421
1477
  color: input.color ?? "#000000",
1422
1478
  width: input.width ?? 2,
@@ -1430,6 +1486,7 @@ function createNote(input) {
1430
1486
  position: input.position,
1431
1487
  zIndex: input.zIndex ?? 0,
1432
1488
  locked: input.locked ?? false,
1489
+ layerId: input.layerId ?? "",
1433
1490
  size: input.size ?? { w: 200, h: 100 },
1434
1491
  text: input.text ?? "",
1435
1492
  backgroundColor: input.backgroundColor ?? "#ffeb3b",
@@ -1443,6 +1500,7 @@ function createArrow(input) {
1443
1500
  position: input.position ?? { x: 0, y: 0 },
1444
1501
  zIndex: input.zIndex ?? 0,
1445
1502
  locked: input.locked ?? false,
1503
+ layerId: input.layerId ?? "",
1446
1504
  from: input.from,
1447
1505
  to: input.to,
1448
1506
  bend: input.bend ?? 0,
@@ -1460,6 +1518,7 @@ function createImage(input) {
1460
1518
  position: input.position,
1461
1519
  zIndex: input.zIndex ?? 0,
1462
1520
  locked: input.locked ?? false,
1521
+ layerId: input.layerId ?? "",
1463
1522
  size: input.size,
1464
1523
  src: input.src
1465
1524
  };
@@ -1471,6 +1530,7 @@ function createHtmlElement(input) {
1471
1530
  position: input.position,
1472
1531
  zIndex: input.zIndex ?? 0,
1473
1532
  locked: input.locked ?? false,
1533
+ layerId: input.layerId ?? "",
1474
1534
  size: input.size
1475
1535
  };
1476
1536
  }
@@ -1481,6 +1541,7 @@ function createShape(input) {
1481
1541
  position: input.position,
1482
1542
  zIndex: input.zIndex ?? 0,
1483
1543
  locked: input.locked ?? false,
1544
+ layerId: input.layerId ?? "",
1484
1545
  shape: input.shape ?? "rectangle",
1485
1546
  size: input.size,
1486
1547
  strokeColor: input.strokeColor ?? "#000000",
@@ -1495,6 +1556,7 @@ function createText(input) {
1495
1556
  position: input.position,
1496
1557
  zIndex: input.zIndex ?? 0,
1497
1558
  locked: input.locked ?? false,
1559
+ layerId: input.layerId ?? "",
1498
1560
  size: input.size ?? { w: 200, h: 28 },
1499
1561
  text: input.text ?? "",
1500
1562
  fontSize: input.fontSize ?? 16,
@@ -1503,6 +1565,155 @@ function createText(input) {
1503
1565
  };
1504
1566
  }
1505
1567
 
1568
+ // src/layers/layer-manager.ts
1569
+ var LayerManager = class {
1570
+ constructor(store) {
1571
+ this.store = store;
1572
+ const defaultLayer = {
1573
+ id: createId("layer"),
1574
+ name: "Layer 1",
1575
+ visible: true,
1576
+ locked: false,
1577
+ order: 0,
1578
+ opacity: 1
1579
+ };
1580
+ this.layers.set(defaultLayer.id, defaultLayer);
1581
+ this._activeLayerId = defaultLayer.id;
1582
+ this.syncLayerOrder();
1583
+ }
1584
+ layers = /* @__PURE__ */ new Map();
1585
+ _activeLayerId;
1586
+ bus = new EventBus();
1587
+ get activeLayer() {
1588
+ const layer = this.layers.get(this._activeLayerId);
1589
+ if (!layer) throw new Error("Active layer not found");
1590
+ return { ...layer };
1591
+ }
1592
+ get activeLayerId() {
1593
+ return this._activeLayerId;
1594
+ }
1595
+ getLayers() {
1596
+ return [...this.layers.values()].sort((a, b) => a.order - b.order).map((l) => ({ ...l }));
1597
+ }
1598
+ getLayer(id) {
1599
+ const layer = this.layers.get(id);
1600
+ return layer ? { ...layer } : void 0;
1601
+ }
1602
+ isLayerVisible(id) {
1603
+ return this.layers.get(id)?.visible ?? true;
1604
+ }
1605
+ isLayerLocked(id) {
1606
+ return this.layers.get(id)?.locked ?? false;
1607
+ }
1608
+ createLayer(name) {
1609
+ const maxOrder = Math.max(...[...this.layers.values()].map((l) => l.order), -1);
1610
+ const autoName = name ?? `Layer ${this.layers.size + 1}`;
1611
+ const layer = {
1612
+ id: createId("layer"),
1613
+ name: autoName,
1614
+ visible: true,
1615
+ locked: false,
1616
+ order: maxOrder + 1,
1617
+ opacity: 1
1618
+ };
1619
+ this.addLayerDirect(layer);
1620
+ return { ...layer };
1621
+ }
1622
+ removeLayer(id) {
1623
+ if (this.layers.size <= 1) {
1624
+ throw new Error("Cannot remove the last layer");
1625
+ }
1626
+ if (this._activeLayerId === id) {
1627
+ const remaining = [...this.layers.values()].filter((l) => l.id !== id).sort((a, b) => b.order - a.order);
1628
+ const fallback = remaining[0];
1629
+ if (fallback) this._activeLayerId = fallback.id;
1630
+ }
1631
+ const elements = this.store.getAll().filter((el) => el.layerId === id);
1632
+ for (const el of elements) {
1633
+ this.store.update(el.id, { layerId: this._activeLayerId });
1634
+ }
1635
+ this.removeLayerDirect(id);
1636
+ }
1637
+ renameLayer(id, name) {
1638
+ this.updateLayerDirect(id, { name });
1639
+ }
1640
+ reorderLayer(id, newOrder) {
1641
+ if (!this.layers.has(id)) return;
1642
+ this.updateLayerDirect(id, { order: newOrder });
1643
+ }
1644
+ setLayerVisible(id, visible) {
1645
+ if (!visible && id === this._activeLayerId) {
1646
+ const fallback = this.findFallbackLayer(id);
1647
+ if (!fallback) return false;
1648
+ this._activeLayerId = fallback.id;
1649
+ }
1650
+ this.updateLayerDirect(id, { visible });
1651
+ return true;
1652
+ }
1653
+ setLayerLocked(id, locked) {
1654
+ if (locked && id === this._activeLayerId) {
1655
+ const fallback = this.findFallbackLayer(id);
1656
+ if (!fallback) return false;
1657
+ this._activeLayerId = fallback.id;
1658
+ }
1659
+ this.updateLayerDirect(id, { locked });
1660
+ return true;
1661
+ }
1662
+ setActiveLayer(id) {
1663
+ if (!this.layers.has(id)) return;
1664
+ this._activeLayerId = id;
1665
+ this.bus.emit("change", null);
1666
+ }
1667
+ moveElementToLayer(elementId, layerId) {
1668
+ if (!this.layers.has(layerId)) return;
1669
+ this.store.update(elementId, { layerId });
1670
+ this.bus.emit("change", null);
1671
+ }
1672
+ snapshot() {
1673
+ return this.getLayers();
1674
+ }
1675
+ loadSnapshot(layers) {
1676
+ this.layers.clear();
1677
+ for (const layer of layers) {
1678
+ this.layers.set(layer.id, { ...layer });
1679
+ }
1680
+ const first = this.getLayers()[0];
1681
+ if (first) this._activeLayerId = first.id;
1682
+ this.syncLayerOrder();
1683
+ this.bus.emit("change", null);
1684
+ }
1685
+ on(event, callback) {
1686
+ return this.bus.on(event, callback);
1687
+ }
1688
+ addLayerDirect(layer) {
1689
+ this.layers.set(layer.id, { ...layer });
1690
+ this.syncLayerOrder();
1691
+ this.bus.emit("change", null);
1692
+ }
1693
+ removeLayerDirect(id) {
1694
+ this.layers.delete(id);
1695
+ this.syncLayerOrder();
1696
+ this.bus.emit("change", null);
1697
+ }
1698
+ updateLayerDirect(id, props) {
1699
+ const layer = this.layers.get(id);
1700
+ if (!layer) return;
1701
+ Object.assign(layer, props);
1702
+ if ("order" in props) this.syncLayerOrder();
1703
+ this.bus.emit("change", null);
1704
+ }
1705
+ syncLayerOrder() {
1706
+ const order = /* @__PURE__ */ new Map();
1707
+ for (const layer of this.layers.values()) {
1708
+ order.set(layer.id, layer.order);
1709
+ }
1710
+ this.store.setLayerOrder(order);
1711
+ }
1712
+ findFallbackLayer(excludeId) {
1713
+ return [...this.layers.values()].filter((l) => l.id !== excludeId && l.visible && !l.locked).sort((a, b) => b.order - a.order)[0];
1714
+ }
1715
+ };
1716
+
1506
1717
  // src/canvas/viewport.ts
1507
1718
  var Viewport = class {
1508
1719
  constructor(container, options = {}) {
@@ -1511,9 +1722,11 @@ var Viewport = class {
1511
1722
  this.background = new Background(options.background);
1512
1723
  this._gridSize = options.background?.spacing ?? 24;
1513
1724
  this.store = new ElementStore();
1725
+ this.layerManager = new LayerManager(this.store);
1514
1726
  this.toolManager = new ToolManager();
1515
1727
  this.renderer = new ElementRenderer();
1516
1728
  this.renderer.setStore(this.store);
1729
+ this.renderer.setOnImageLoad(() => this.requestRender());
1517
1730
  this.noteEditor = new NoteEditor();
1518
1731
  this.noteEditor.setOnStop((id) => this.onTextEditStop(id));
1519
1732
  this.history = new HistoryStack();
@@ -1534,7 +1747,10 @@ var Viewport = class {
1534
1747
  this.wrapper.style.cursor = cursor;
1535
1748
  },
1536
1749
  snapToGrid: false,
1537
- gridSize: this._gridSize
1750
+ gridSize: this._gridSize,
1751
+ activeLayerId: this.layerManager.activeLayerId,
1752
+ isLayerVisible: (id) => this.layerManager.isLayerVisible(id),
1753
+ isLayerLocked: (id) => this.layerManager.isLayerLocked(id)
1538
1754
  };
1539
1755
  this.inputHandler = new InputHandler(this.wrapper, this.camera, {
1540
1756
  toolManager: this.toolManager,
@@ -1555,6 +1771,10 @@ var Viewport = class {
1555
1771
  this.store.on("update", () => this.requestRender()),
1556
1772
  this.store.on("clear", () => this.clearDomNodes())
1557
1773
  ];
1774
+ this.layerManager.on("change", () => {
1775
+ this.toolContext.activeLayerId = this.layerManager.activeLayerId;
1776
+ this.requestRender();
1777
+ });
1558
1778
  this.wrapper.addEventListener("dblclick", this.onDblClick);
1559
1779
  this.wrapper.addEventListener("dragover", this.onDragOver);
1560
1780
  this.wrapper.addEventListener("drop", this.onDrop);
@@ -1564,6 +1784,7 @@ var Viewport = class {
1564
1784
  }
1565
1785
  camera;
1566
1786
  store;
1787
+ layerManager;
1567
1788
  toolManager;
1568
1789
  history;
1569
1790
  domLayer;
@@ -1599,7 +1820,7 @@ var Viewport = class {
1599
1820
  this.needsRender = true;
1600
1821
  }
1601
1822
  exportState() {
1602
- return exportState(this.store.snapshot(), this.camera);
1823
+ return exportState(this.store.snapshot(), this.camera, this.layerManager.snapshot());
1603
1824
  }
1604
1825
  exportJSON() {
1605
1826
  return JSON.stringify(this.exportState());
@@ -1609,6 +1830,9 @@ var Viewport = class {
1609
1830
  this.noteEditor.destroy(this.store);
1610
1831
  this.clearDomNodes();
1611
1832
  this.store.loadSnapshot(state.elements);
1833
+ if (state.layers && state.layers.length > 0) {
1834
+ this.layerManager.loadSnapshot(state.layers);
1835
+ }
1612
1836
  this.history.clear();
1613
1837
  this.historyRecorder.resume();
1614
1838
  this.camera.moveTo(state.camera.position.x, state.camera.position.y);
@@ -1632,7 +1856,7 @@ var Viewport = class {
1632
1856
  return result;
1633
1857
  }
1634
1858
  addImage(src, position, size = { w: 300, h: 200 }) {
1635
- const image = createImage({ position, size, src });
1859
+ const image = createImage({ position, size, src, layerId: this.layerManager.activeLayerId });
1636
1860
  this.historyRecorder.begin();
1637
1861
  this.store.add(image);
1638
1862
  this.historyRecorder.commit();
@@ -1640,7 +1864,7 @@ var Viewport = class {
1640
1864
  return image.id;
1641
1865
  }
1642
1866
  addHtmlElement(dom, position, size = { w: 200, h: 150 }) {
1643
- const el = createHtmlElement({ position, size });
1867
+ const el = createHtmlElement({ position, size, layerId: this.layerManager.activeLayerId });
1644
1868
  this.htmlContent.set(el.id, dom);
1645
1869
  this.historyRecorder.begin();
1646
1870
  this.store.add(el);
@@ -1683,9 +1907,17 @@ var Viewport = class {
1683
1907
  ctx.save();
1684
1908
  ctx.translate(this.camera.position.x, this.camera.position.y);
1685
1909
  ctx.scale(this.camera.zoom, this.camera.zoom);
1686
- for (const element of this.store.getAll()) {
1910
+ const allElements = this.store.getAll();
1911
+ let domZIndex = 0;
1912
+ for (const element of allElements) {
1913
+ if (!this.layerManager.isLayerVisible(element.layerId)) {
1914
+ if (this.renderer.isDomElement(element)) {
1915
+ this.hideDomNode(element.id);
1916
+ }
1917
+ continue;
1918
+ }
1687
1919
  if (this.renderer.isDomElement(element)) {
1688
- this.syncDomNode(element);
1920
+ this.syncDomNode(element, domZIndex++);
1689
1921
  } else {
1690
1922
  this.renderer.renderCanvasElement(ctx, element);
1691
1923
  }
@@ -1817,7 +2049,7 @@ var Viewport = class {
1817
2049
  reader.readAsDataURL(file);
1818
2050
  }
1819
2051
  };
1820
- syncDomNode(element) {
2052
+ syncDomNode(element, zIndex = 0) {
1821
2053
  let node = this.domNodes.get(element.id);
1822
2054
  if (!node) {
1823
2055
  node = document.createElement("div");
@@ -1831,10 +2063,12 @@ var Viewport = class {
1831
2063
  }
1832
2064
  const size = "size" in element ? element.size : null;
1833
2065
  Object.assign(node.style, {
2066
+ display: "block",
1834
2067
  left: `${element.position.x}px`,
1835
2068
  top: `${element.position.y}px`,
1836
2069
  width: size ? `${size.w}px` : "auto",
1837
- height: size ? `${size.h}px` : "auto"
2070
+ height: size ? `${size.h}px` : "auto",
2071
+ zIndex: String(zIndex)
1838
2072
  });
1839
2073
  this.renderDomContent(node, element);
1840
2074
  }
@@ -1869,26 +2103,6 @@ var Viewport = class {
1869
2103
  node.style.color = element.textColor;
1870
2104
  }
1871
2105
  }
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
2106
  if (element.type === "html" && !node.dataset["initialized"]) {
1893
2107
  const content = this.htmlContent.get(element.id);
1894
2108
  if (content) {
@@ -1971,6 +2185,10 @@ var Viewport = class {
1971
2185
  }
1972
2186
  }
1973
2187
  }
2188
+ hideDomNode(id) {
2189
+ const node = this.domNodes.get(id);
2190
+ if (node) node.style.display = "none";
2191
+ }
1974
2192
  removeDomNode(id) {
1975
2193
  this.htmlContent.delete(id);
1976
2194
  const node = this.domNodes.get(id);
@@ -2117,7 +2335,8 @@ var PencilTool = class {
2117
2335
  const stroke = createStroke({
2118
2336
  points: simplified,
2119
2337
  color: this.color,
2120
- width: this.width
2338
+ width: this.width,
2339
+ layerId: ctx.activeLayerId ?? ""
2121
2340
  });
2122
2341
  ctx.store.add(stroke);
2123
2342
  this.points = [];
@@ -2181,6 +2400,8 @@ var EraserTool = class {
2181
2400
  const strokes = ctx.store.getElementsByType("stroke");
2182
2401
  let erased = false;
2183
2402
  for (const stroke of strokes) {
2403
+ if (ctx.isLayerVisible && !ctx.isLayerVisible(stroke.layerId)) continue;
2404
+ if (ctx.isLayerLocked && ctx.isLayerLocked(stroke.layerId)) continue;
2184
2405
  if (this.strokeIntersects(stroke, world)) {
2185
2406
  ctx.store.remove(stroke.id);
2186
2407
  erased = true;
@@ -2666,6 +2887,8 @@ var SelectTool = class {
2666
2887
  findElementsInRect(marquee, ctx) {
2667
2888
  const ids = [];
2668
2889
  for (const el of ctx.store.getAll()) {
2890
+ if (ctx.isLayerVisible && !ctx.isLayerVisible(el.layerId)) continue;
2891
+ if (ctx.isLayerLocked && ctx.isLayerLocked(el.layerId)) continue;
2669
2892
  const bounds = this.getElementBounds(el);
2670
2893
  if (bounds && this.rectsOverlap(marquee, bounds)) {
2671
2894
  ids.push(el.id);
@@ -2700,6 +2923,8 @@ var SelectTool = class {
2700
2923
  hitTest(world, ctx) {
2701
2924
  const elements = ctx.store.getAll().reverse();
2702
2925
  for (const el of elements) {
2926
+ if (ctx.isLayerVisible && !ctx.isLayerVisible(el.layerId)) continue;
2927
+ if (ctx.isLayerLocked && ctx.isLayerLocked(el.layerId)) continue;
2703
2928
  if (this.isInsideBounds(world, el)) return el;
2704
2929
  }
2705
2930
  return null;
@@ -2744,11 +2969,20 @@ var ArrowTool = class {
2744
2969
  if (options.color !== void 0) this.color = options.color;
2745
2970
  if (options.width !== void 0) this.width = options.width;
2746
2971
  }
2972
+ layerFilter(ctx) {
2973
+ if (!ctx.isLayerVisible && !ctx.isLayerLocked) return void 0;
2974
+ return (el) => {
2975
+ if (ctx.isLayerVisible && !ctx.isLayerVisible(el.layerId)) return false;
2976
+ if (ctx.isLayerLocked && ctx.isLayerLocked(el.layerId)) return false;
2977
+ return true;
2978
+ };
2979
+ }
2747
2980
  onPointerDown(state, ctx) {
2748
2981
  this.drawing = true;
2749
2982
  const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
2750
2983
  const threshold = BIND_THRESHOLD2 / ctx.camera.zoom;
2751
- const target = findBindTarget(world, ctx.store, threshold);
2984
+ const filter = this.layerFilter(ctx);
2985
+ const target = findBindTarget(world, ctx.store, threshold, void 0, filter);
2752
2986
  if (target) {
2753
2987
  this.start = getElementCenter(target);
2754
2988
  this.fromBinding = { elementId: target.id };
@@ -2766,7 +3000,8 @@ var ArrowTool = class {
2766
3000
  const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
2767
3001
  const threshold = BIND_THRESHOLD2 / ctx.camera.zoom;
2768
3002
  const excludeId = this.fromBinding?.elementId;
2769
- const target = findBindTarget(world, ctx.store, threshold, excludeId);
3003
+ const filter = this.layerFilter(ctx);
3004
+ const target = findBindTarget(world, ctx.store, threshold, excludeId, filter);
2770
3005
  if (target) {
2771
3006
  this.end = getElementCenter(target);
2772
3007
  this.toTarget = target;
@@ -2787,7 +3022,8 @@ var ArrowTool = class {
2787
3022
  color: this.color,
2788
3023
  width: this.width,
2789
3024
  fromBinding: this.fromBinding,
2790
- toBinding: this.toTarget ? { elementId: this.toTarget.id } : void 0
3025
+ toBinding: this.toTarget ? { elementId: this.toTarget.id } : void 0,
3026
+ layerId: ctx.activeLayerId ?? ""
2791
3027
  });
2792
3028
  ctx.store.add(arrow);
2793
3029
  this.fromTarget = null;
@@ -2874,7 +3110,8 @@ var NoteTool = class {
2874
3110
  position: world,
2875
3111
  size: { ...this.size },
2876
3112
  backgroundColor: this.backgroundColor,
2877
- textColor: this.textColor
3113
+ textColor: this.textColor,
3114
+ layerId: ctx.activeLayerId ?? ""
2878
3115
  });
2879
3116
  ctx.store.add(note);
2880
3117
  ctx.requestRender();
@@ -2918,7 +3155,8 @@ var TextTool = class {
2918
3155
  position: world,
2919
3156
  fontSize: this.fontSize,
2920
3157
  color: this.color,
2921
- textAlign: this.textAlign
3158
+ textAlign: this.textAlign,
3159
+ layerId: ctx.activeLayerId ?? ""
2922
3160
  });
2923
3161
  ctx.store.add(textEl);
2924
3162
  ctx.requestRender();
@@ -3014,7 +3252,8 @@ var ShapeTool = class {
3014
3252
  shape: this.shape,
3015
3253
  strokeColor: this.strokeColor,
3016
3254
  strokeWidth: this.strokeWidth,
3017
- fillColor: this.fillColor
3255
+ fillColor: this.fillColor,
3256
+ layerId: ctx.activeLayerId ?? ""
3018
3257
  });
3019
3258
  ctx.store.add(shape);
3020
3259
  ctx.requestRender();
@@ -3075,8 +3314,48 @@ var ShapeTool = class {
3075
3314
  };
3076
3315
  };
3077
3316
 
3317
+ // src/history/layer-commands.ts
3318
+ var CreateLayerCommand = class {
3319
+ constructor(manager, layer) {
3320
+ this.manager = manager;
3321
+ this.layer = layer;
3322
+ }
3323
+ execute(_store) {
3324
+ this.manager.addLayerDirect(this.layer);
3325
+ }
3326
+ undo(_store) {
3327
+ this.manager.removeLayerDirect(this.layer.id);
3328
+ }
3329
+ };
3330
+ var RemoveLayerCommand = class {
3331
+ constructor(manager, layer) {
3332
+ this.manager = manager;
3333
+ this.layer = layer;
3334
+ }
3335
+ execute(_store) {
3336
+ this.manager.removeLayerDirect(this.layer.id);
3337
+ }
3338
+ undo(_store) {
3339
+ this.manager.addLayerDirect(this.layer);
3340
+ }
3341
+ };
3342
+ var UpdateLayerCommand = class {
3343
+ constructor(manager, layerId, previous, current) {
3344
+ this.manager = manager;
3345
+ this.layerId = layerId;
3346
+ this.previous = previous;
3347
+ this.current = current;
3348
+ }
3349
+ execute(_store) {
3350
+ this.manager.updateLayerDirect(this.layerId, { ...this.current });
3351
+ }
3352
+ undo(_store) {
3353
+ this.manager.updateLayerDirect(this.layerId, { ...this.previous });
3354
+ }
3355
+ };
3356
+
3078
3357
  // src/index.ts
3079
- var VERSION = "0.5.0";
3358
+ var VERSION = "0.6.0";
3080
3359
  export {
3081
3360
  AddElementCommand,
3082
3361
  ArrowTool,
@@ -3084,6 +3363,7 @@ export {
3084
3363
  Background,
3085
3364
  BatchCommand,
3086
3365
  Camera,
3366
+ CreateLayerCommand,
3087
3367
  ElementRenderer,
3088
3368
  ElementStore,
3089
3369
  EraserTool,
@@ -3093,15 +3373,18 @@ export {
3093
3373
  HistoryStack,
3094
3374
  ImageTool,
3095
3375
  InputHandler,
3376
+ LayerManager,
3096
3377
  NoteEditor,
3097
3378
  NoteTool,
3098
3379
  PencilTool,
3099
3380
  RemoveElementCommand,
3381
+ RemoveLayerCommand,
3100
3382
  SelectTool,
3101
3383
  ShapeTool,
3102
3384
  TextTool,
3103
3385
  ToolManager,
3104
3386
  UpdateElementCommand,
3387
+ UpdateLayerCommand,
3105
3388
  VERSION,
3106
3389
  Viewport,
3107
3390
  clearStaleBindings,