@fieldnotes/core 0.8.5 → 0.8.6

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
@@ -724,6 +724,15 @@ var ElementStore = class {
724
724
  on(event, listener) {
725
725
  return this.bus.on(event, listener);
726
726
  }
727
+ onChange(listener) {
728
+ const unsubs = [
729
+ this.bus.on("add", listener),
730
+ this.bus.on("remove", listener),
731
+ this.bus.on("update", listener),
732
+ this.bus.on("clear", listener)
733
+ ];
734
+ return () => unsubs.forEach((fn) => fn());
735
+ }
727
736
  };
728
737
 
729
738
  // src/elements/arrow-geometry.ts
@@ -2175,6 +2184,10 @@ var LayerManager = class {
2175
2184
  this.updateLayerDirect(id, { locked });
2176
2185
  return true;
2177
2186
  }
2187
+ setLayerOpacity(id, opacity) {
2188
+ if (!this.layers.has(id)) return;
2189
+ this.updateLayerDirect(id, { opacity: Math.max(0, Math.min(1, opacity)) });
2190
+ }
2178
2191
  setActiveLayer(id) {
2179
2192
  if (!this.layers.has(id)) return;
2180
2193
  this._activeLayerId = id;
@@ -2230,6 +2243,294 @@ var LayerManager = class {
2230
2243
  }
2231
2244
  };
2232
2245
 
2246
+ // src/canvas/interact-mode.ts
2247
+ var InteractMode = class {
2248
+ interactingElementId = null;
2249
+ getNode;
2250
+ constructor(deps) {
2251
+ this.getNode = deps.getNode;
2252
+ }
2253
+ startInteracting(id) {
2254
+ this.stopInteracting();
2255
+ const node = this.getNode(id);
2256
+ if (!node) return;
2257
+ this.interactingElementId = id;
2258
+ node.style.pointerEvents = "auto";
2259
+ node.addEventListener("pointerdown", this.onNodePointerDown);
2260
+ window.addEventListener("keydown", this.onKeyDown);
2261
+ window.addEventListener("pointerdown", this.onPointerDown);
2262
+ }
2263
+ stopInteracting() {
2264
+ if (!this.interactingElementId) return;
2265
+ const node = this.getNode(this.interactingElementId);
2266
+ if (node) {
2267
+ node.style.pointerEvents = "none";
2268
+ node.removeEventListener("pointerdown", this.onNodePointerDown);
2269
+ }
2270
+ this.interactingElementId = null;
2271
+ window.removeEventListener("keydown", this.onKeyDown);
2272
+ window.removeEventListener("pointerdown", this.onPointerDown);
2273
+ }
2274
+ isInteracting() {
2275
+ return this.interactingElementId !== null;
2276
+ }
2277
+ destroy() {
2278
+ this.stopInteracting();
2279
+ }
2280
+ onNodePointerDown = (e) => {
2281
+ e.stopPropagation();
2282
+ };
2283
+ onKeyDown = (e) => {
2284
+ if (e.key === "Escape") {
2285
+ this.stopInteracting();
2286
+ }
2287
+ };
2288
+ onPointerDown = (e) => {
2289
+ if (!this.interactingElementId) return;
2290
+ const target = e.target;
2291
+ if (!(target instanceof Element)) {
2292
+ this.stopInteracting();
2293
+ return;
2294
+ }
2295
+ const node = this.getNode(this.interactingElementId);
2296
+ if (node && !node.contains(target)) {
2297
+ this.stopInteracting();
2298
+ }
2299
+ };
2300
+ };
2301
+
2302
+ // src/canvas/dom-node-manager.ts
2303
+ var DomNodeManager = class {
2304
+ domNodes = /* @__PURE__ */ new Map();
2305
+ htmlContent = /* @__PURE__ */ new Map();
2306
+ domLayer;
2307
+ onEditRequest;
2308
+ isEditingElement;
2309
+ constructor(deps) {
2310
+ this.domLayer = deps.domLayer;
2311
+ this.onEditRequest = deps.onEditRequest;
2312
+ this.isEditingElement = deps.isEditingElement;
2313
+ }
2314
+ getNode(id) {
2315
+ return this.domNodes.get(id);
2316
+ }
2317
+ storeHtmlContent(elementId, dom) {
2318
+ this.htmlContent.set(elementId, dom);
2319
+ }
2320
+ syncDomNode(element, zIndex = 0) {
2321
+ let node = this.domNodes.get(element.id);
2322
+ if (!node) {
2323
+ node = document.createElement("div");
2324
+ node.dataset["elementId"] = element.id;
2325
+ Object.assign(node.style, {
2326
+ position: "absolute",
2327
+ pointerEvents: "auto"
2328
+ });
2329
+ this.domLayer.appendChild(node);
2330
+ this.domNodes.set(element.id, node);
2331
+ }
2332
+ const size = "size" in element ? element.size : null;
2333
+ Object.assign(node.style, {
2334
+ display: "block",
2335
+ left: `${element.position.x}px`,
2336
+ top: `${element.position.y}px`,
2337
+ width: size ? `${size.w}px` : "auto",
2338
+ height: size ? `${size.h}px` : "auto",
2339
+ zIndex: String(zIndex)
2340
+ });
2341
+ this.renderDomContent(node, element);
2342
+ }
2343
+ hideDomNode(id) {
2344
+ const node = this.domNodes.get(id);
2345
+ if (node) node.style.display = "none";
2346
+ }
2347
+ removeDomNode(id) {
2348
+ this.htmlContent.delete(id);
2349
+ const node = this.domNodes.get(id);
2350
+ if (node) {
2351
+ node.remove();
2352
+ this.domNodes.delete(id);
2353
+ }
2354
+ }
2355
+ clearDomNodes() {
2356
+ this.domNodes.forEach((node) => node.remove());
2357
+ this.domNodes.clear();
2358
+ this.htmlContent.clear();
2359
+ }
2360
+ reattachHtmlContent(store) {
2361
+ for (const el of store.getElementsByType("html")) {
2362
+ if (el.domId) {
2363
+ const dom = document.getElementById(el.domId);
2364
+ if (dom) {
2365
+ this.htmlContent.set(el.id, dom);
2366
+ }
2367
+ }
2368
+ }
2369
+ }
2370
+ renderDomContent(node, element) {
2371
+ if (element.type === "note") {
2372
+ if (!node.dataset["initialized"]) {
2373
+ node.dataset["initialized"] = "true";
2374
+ Object.assign(node.style, {
2375
+ backgroundColor: element.backgroundColor,
2376
+ color: element.textColor,
2377
+ padding: "8px",
2378
+ borderRadius: "4px",
2379
+ boxShadow: "0 2px 8px rgba(0,0,0,0.15)",
2380
+ fontSize: "14px",
2381
+ overflow: "hidden",
2382
+ cursor: "default",
2383
+ userSelect: "none",
2384
+ wordWrap: "break-word"
2385
+ });
2386
+ node.textContent = element.text || "";
2387
+ node.addEventListener("dblclick", (e) => {
2388
+ e.stopPropagation();
2389
+ const id = node.dataset["elementId"];
2390
+ if (id) this.onEditRequest(id);
2391
+ });
2392
+ }
2393
+ if (!this.isEditingElement(element.id)) {
2394
+ if (node.textContent !== element.text) {
2395
+ node.textContent = element.text || "";
2396
+ }
2397
+ node.style.backgroundColor = element.backgroundColor;
2398
+ node.style.color = element.textColor;
2399
+ }
2400
+ }
2401
+ if (element.type === "html" && !node.dataset["initialized"]) {
2402
+ const content = this.htmlContent.get(element.id);
2403
+ if (content) {
2404
+ node.dataset["initialized"] = "true";
2405
+ Object.assign(node.style, {
2406
+ overflow: "hidden",
2407
+ pointerEvents: "none"
2408
+ });
2409
+ node.appendChild(content);
2410
+ }
2411
+ }
2412
+ if (element.type === "text") {
2413
+ if (!node.dataset["initialized"]) {
2414
+ node.dataset["initialized"] = "true";
2415
+ Object.assign(node.style, {
2416
+ padding: "2px",
2417
+ fontSize: `${element.fontSize}px`,
2418
+ color: element.color,
2419
+ textAlign: element.textAlign,
2420
+ background: "none",
2421
+ border: "none",
2422
+ boxShadow: "none",
2423
+ overflow: "visible",
2424
+ cursor: "default",
2425
+ userSelect: "none",
2426
+ wordWrap: "break-word",
2427
+ whiteSpace: "pre-wrap",
2428
+ lineHeight: "1.4"
2429
+ });
2430
+ node.textContent = element.text || "";
2431
+ node.addEventListener("dblclick", (e) => {
2432
+ e.stopPropagation();
2433
+ const id = node.dataset["elementId"];
2434
+ if (id) this.onEditRequest(id);
2435
+ });
2436
+ }
2437
+ if (!this.isEditingElement(element.id)) {
2438
+ if (node.textContent !== element.text) {
2439
+ node.textContent = element.text || "";
2440
+ }
2441
+ Object.assign(node.style, {
2442
+ fontSize: `${element.fontSize}px`,
2443
+ color: element.color,
2444
+ textAlign: element.textAlign
2445
+ });
2446
+ }
2447
+ }
2448
+ }
2449
+ };
2450
+
2451
+ // src/canvas/render-loop.ts
2452
+ var RenderLoop = class {
2453
+ needsRender = false;
2454
+ animFrameId = 0;
2455
+ canvasEl;
2456
+ camera;
2457
+ background;
2458
+ store;
2459
+ renderer;
2460
+ toolManager;
2461
+ layerManager;
2462
+ domNodeManager;
2463
+ constructor(deps) {
2464
+ this.canvasEl = deps.canvasEl;
2465
+ this.camera = deps.camera;
2466
+ this.background = deps.background;
2467
+ this.store = deps.store;
2468
+ this.renderer = deps.renderer;
2469
+ this.toolManager = deps.toolManager;
2470
+ this.layerManager = deps.layerManager;
2471
+ this.domNodeManager = deps.domNodeManager;
2472
+ }
2473
+ requestRender() {
2474
+ this.needsRender = true;
2475
+ }
2476
+ flush() {
2477
+ if (this.needsRender) {
2478
+ this.render();
2479
+ this.needsRender = false;
2480
+ }
2481
+ }
2482
+ start() {
2483
+ const loop = () => {
2484
+ if (this.needsRender) {
2485
+ this.render();
2486
+ this.needsRender = false;
2487
+ }
2488
+ this.animFrameId = requestAnimationFrame(loop);
2489
+ };
2490
+ this.animFrameId = requestAnimationFrame(loop);
2491
+ }
2492
+ stop() {
2493
+ cancelAnimationFrame(this.animFrameId);
2494
+ }
2495
+ setCanvasSize(width, height) {
2496
+ this.canvasEl.width = width;
2497
+ this.canvasEl.height = height;
2498
+ }
2499
+ render() {
2500
+ const ctx = this.canvasEl.getContext("2d");
2501
+ if (!ctx) return;
2502
+ const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
2503
+ ctx.save();
2504
+ ctx.scale(dpr, dpr);
2505
+ this.renderer.setCanvasSize(this.canvasEl.clientWidth, this.canvasEl.clientHeight);
2506
+ this.background.render(ctx, this.camera);
2507
+ ctx.save();
2508
+ ctx.translate(this.camera.position.x, this.camera.position.y);
2509
+ ctx.scale(this.camera.zoom, this.camera.zoom);
2510
+ const allElements = this.store.getAll();
2511
+ let domZIndex = 0;
2512
+ for (const element of allElements) {
2513
+ if (!this.layerManager.isLayerVisible(element.layerId)) {
2514
+ if (this.renderer.isDomElement(element)) {
2515
+ this.domNodeManager.hideDomNode(element.id);
2516
+ }
2517
+ continue;
2518
+ }
2519
+ if (this.renderer.isDomElement(element)) {
2520
+ this.domNodeManager.syncDomNode(element, domZIndex++);
2521
+ } else {
2522
+ this.renderer.renderCanvasElement(ctx, element);
2523
+ }
2524
+ }
2525
+ const activeTool = this.toolManager.activeTool;
2526
+ if (activeTool?.renderOverlay) {
2527
+ activeTool.renderOverlay(ctx);
2528
+ }
2529
+ ctx.restore();
2530
+ ctx.restore();
2531
+ }
2532
+ };
2533
+
2233
2534
  // src/canvas/viewport.ts
2234
2535
  var Viewport = class {
2235
2536
  constructor(container, options = {}) {
@@ -2275,6 +2576,24 @@ var Viewport = class {
2275
2576
  historyRecorder: this.historyRecorder,
2276
2577
  historyStack: this.history
2277
2578
  });
2579
+ this.domNodeManager = new DomNodeManager({
2580
+ domLayer: this.domLayer,
2581
+ onEditRequest: (id) => this.startEditingElement(id),
2582
+ isEditingElement: (id) => this.noteEditor.isEditing && this.noteEditor.editingElementId === id
2583
+ });
2584
+ this.interactMode = new InteractMode({
2585
+ getNode: (id) => this.domNodeManager.getNode(id)
2586
+ });
2587
+ this.renderLoop = new RenderLoop({
2588
+ canvasEl: this.canvasEl,
2589
+ camera: this.camera,
2590
+ background: this.background,
2591
+ store: this.store,
2592
+ renderer: this.renderer,
2593
+ toolManager: this.toolManager,
2594
+ layerManager: this.layerManager,
2595
+ domNodeManager: this.domNodeManager
2596
+ });
2278
2597
  this.unsubCamera = this.camera.onChange(() => {
2279
2598
  this.applyCameraTransform();
2280
2599
  this.requestRender();
@@ -2283,10 +2602,14 @@ var Viewport = class {
2283
2602
  this.store.on("add", () => this.requestRender()),
2284
2603
  this.store.on("remove", (el) => {
2285
2604
  this.unbindArrowsFrom(el);
2286
- this.removeDomNode(el.id);
2605
+ this.domNodeManager.removeDomNode(el.id);
2606
+ this.requestRender();
2287
2607
  }),
2288
2608
  this.store.on("update", () => this.requestRender()),
2289
- this.store.on("clear", () => this.clearDomNodes())
2609
+ this.store.on("clear", () => {
2610
+ this.domNodeManager.clearDomNodes();
2611
+ this.requestRender();
2612
+ })
2290
2613
  ];
2291
2614
  this.layerManager.on("change", () => {
2292
2615
  this.toolContext.activeLayerId = this.layerManager.activeLayerId;
@@ -2297,7 +2620,7 @@ var Viewport = class {
2297
2620
  this.wrapper.addEventListener("drop", this.onDrop);
2298
2621
  this.observeResize();
2299
2622
  this.syncCanvasSize();
2300
- this.startRenderLoop();
2623
+ this.renderLoop.start();
2301
2624
  }
2302
2625
  camera;
2303
2626
  store;
@@ -2316,13 +2639,11 @@ var Viewport = class {
2316
2639
  historyRecorder;
2317
2640
  toolContext;
2318
2641
  resizeObserver = null;
2319
- animFrameId = 0;
2320
2642
  _snapToGrid = false;
2321
2643
  _gridSize;
2322
- needsRender = true;
2323
- domNodes = /* @__PURE__ */ new Map();
2324
- htmlContent = /* @__PURE__ */ new Map();
2325
- interactingElementId = null;
2644
+ renderLoop;
2645
+ domNodeManager;
2646
+ interactMode;
2326
2647
  get ctx() {
2327
2648
  return this.canvasEl.getContext("2d");
2328
2649
  }
@@ -2334,7 +2655,7 @@ var Viewport = class {
2334
2655
  this.toolContext.snapToGrid = enabled;
2335
2656
  }
2336
2657
  requestRender() {
2337
- this.needsRender = true;
2658
+ this.renderLoop.requestRender();
2338
2659
  }
2339
2660
  exportState() {
2340
2661
  return exportState(this.store.snapshot(), this.camera, this.layerManager.snapshot());
@@ -2348,12 +2669,12 @@ var Viewport = class {
2348
2669
  loadState(state) {
2349
2670
  this.historyRecorder.pause();
2350
2671
  this.noteEditor.destroy(this.store);
2351
- this.clearDomNodes();
2672
+ this.domNodeManager.clearDomNodes();
2352
2673
  this.store.loadSnapshot(state.elements);
2353
2674
  if (state.layers && state.layers.length > 0) {
2354
2675
  this.layerManager.loadSnapshot(state.layers);
2355
2676
  }
2356
- this.reattachHtmlContent();
2677
+ this.domNodeManager.reattachHtmlContent(this.store);
2357
2678
  this.history.clear();
2358
2679
  this.historyRecorder.resume();
2359
2680
  this.camera.moveTo(state.camera.position.x, state.camera.position.y);
@@ -2392,7 +2713,7 @@ var Viewport = class {
2392
2713
  domId,
2393
2714
  layerId: this.layerManager.activeLayerId
2394
2715
  });
2395
- this.htmlContent.set(el.id, dom);
2716
+ this.domNodeManager.storeHtmlContent(el.id, dom);
2396
2717
  this.historyRecorder.begin();
2397
2718
  this.store.add(el);
2398
2719
  this.historyRecorder.commit();
@@ -2428,8 +2749,8 @@ var Viewport = class {
2428
2749
  this.requestRender();
2429
2750
  }
2430
2751
  destroy() {
2431
- cancelAnimationFrame(this.animFrameId);
2432
- this.stopInteracting();
2752
+ this.renderLoop.stop();
2753
+ this.interactMode.destroy();
2433
2754
  this.noteEditor.destroy(this.store);
2434
2755
  this.historyRecorder.destroy();
2435
2756
  this.wrapper.removeEventListener("dblclick", this.onDblClick);
@@ -2442,54 +2763,11 @@ var Viewport = class {
2442
2763
  this.resizeObserver = null;
2443
2764
  this.wrapper.remove();
2444
2765
  }
2445
- startRenderLoop() {
2446
- const loop = () => {
2447
- if (this.needsRender) {
2448
- this.render();
2449
- this.needsRender = false;
2450
- }
2451
- this.animFrameId = requestAnimationFrame(loop);
2452
- };
2453
- this.animFrameId = requestAnimationFrame(loop);
2454
- }
2455
- render() {
2456
- const ctx = this.ctx;
2457
- if (!ctx) return;
2458
- const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
2459
- ctx.save();
2460
- ctx.scale(dpr, dpr);
2461
- this.renderer.setCanvasSize(this.canvasEl.clientWidth, this.canvasEl.clientHeight);
2462
- this.background.render(ctx, this.camera);
2463
- ctx.save();
2464
- ctx.translate(this.camera.position.x, this.camera.position.y);
2465
- ctx.scale(this.camera.zoom, this.camera.zoom);
2466
- const allElements = this.store.getAll();
2467
- let domZIndex = 0;
2468
- for (const element of allElements) {
2469
- if (!this.layerManager.isLayerVisible(element.layerId)) {
2470
- if (this.renderer.isDomElement(element)) {
2471
- this.hideDomNode(element.id);
2472
- }
2473
- continue;
2474
- }
2475
- if (this.renderer.isDomElement(element)) {
2476
- this.syncDomNode(element, domZIndex++);
2477
- } else {
2478
- this.renderer.renderCanvasElement(ctx, element);
2479
- }
2480
- }
2481
- const activeTool = this.toolManager.activeTool;
2482
- if (activeTool?.renderOverlay) {
2483
- activeTool.renderOverlay(ctx);
2484
- }
2485
- ctx.restore();
2486
- ctx.restore();
2487
- }
2488
2766
  startEditingElement(id) {
2489
2767
  const element = this.store.getById(id);
2490
2768
  if (!element || element.type !== "note" && element.type !== "text") return;
2491
- this.render();
2492
- const node = this.domNodes.get(id);
2769
+ this.renderLoop.flush();
2770
+ const node = this.domNodeManager.getNode(id);
2493
2771
  if (node) {
2494
2772
  this.noteEditor.startEditing(node, id, this.store);
2495
2773
  }
@@ -2503,7 +2781,7 @@ var Viewport = class {
2503
2781
  this.historyRecorder.commit();
2504
2782
  return;
2505
2783
  }
2506
- const node = this.domNodes.get(elementId);
2784
+ const node = this.domNodeManager.getNode(elementId);
2507
2785
  if (node && "size" in element) {
2508
2786
  const measuredHeight = node.scrollHeight;
2509
2787
  if (measuredHeight !== element.size.h) {
@@ -2531,7 +2809,7 @@ var Viewport = class {
2531
2809
  const world = this.camera.screenToWorld(screen);
2532
2810
  const hit = this.hitTestWorld(world);
2533
2811
  if (hit?.type === "html") {
2534
- this.startInteracting(hit.id);
2812
+ this.interactMode.startInteracting(hit.id);
2535
2813
  }
2536
2814
  };
2537
2815
  hitTestWorld(world) {
@@ -2546,44 +2824,9 @@ var Viewport = class {
2546
2824
  }
2547
2825
  return null;
2548
2826
  }
2549
- startInteracting(id) {
2550
- this.stopInteracting();
2551
- const node = this.domNodes.get(id);
2552
- if (!node) return;
2553
- this.interactingElementId = id;
2554
- node.style.pointerEvents = "auto";
2555
- node.addEventListener("pointerdown", this.onInteractNodePointerDown);
2556
- window.addEventListener("keydown", this.onInteractKeyDown);
2557
- window.addEventListener("pointerdown", this.onInteractPointerDown);
2558
- }
2559
2827
  stopInteracting() {
2560
- if (!this.interactingElementId) return;
2561
- const node = this.domNodes.get(this.interactingElementId);
2562
- if (node) {
2563
- node.style.pointerEvents = "none";
2564
- node.removeEventListener("pointerdown", this.onInteractNodePointerDown);
2565
- }
2566
- this.interactingElementId = null;
2567
- window.removeEventListener("keydown", this.onInteractKeyDown);
2568
- window.removeEventListener("pointerdown", this.onInteractPointerDown);
2828
+ this.interactMode.stopInteracting();
2569
2829
  }
2570
- onInteractNodePointerDown = (e) => {
2571
- e.stopPropagation();
2572
- };
2573
- onInteractKeyDown = (e) => {
2574
- if (e.key === "Escape") {
2575
- this.stopInteracting();
2576
- }
2577
- };
2578
- onInteractPointerDown = (e) => {
2579
- if (!this.interactingElementId) return;
2580
- const target = e.target;
2581
- if (!target) return;
2582
- const node = this.domNodes.get(this.interactingElementId);
2583
- if (node && !node.contains(target)) {
2584
- this.stopInteracting();
2585
- }
2586
- };
2587
2830
  onDragOver = (e) => {
2588
2831
  e.preventDefault();
2589
2832
  };
@@ -2605,108 +2848,6 @@ var Viewport = class {
2605
2848
  reader.readAsDataURL(file);
2606
2849
  }
2607
2850
  };
2608
- syncDomNode(element, zIndex = 0) {
2609
- let node = this.domNodes.get(element.id);
2610
- if (!node) {
2611
- node = document.createElement("div");
2612
- node.dataset["elementId"] = element.id;
2613
- Object.assign(node.style, {
2614
- position: "absolute",
2615
- pointerEvents: "auto"
2616
- });
2617
- this.domLayer.appendChild(node);
2618
- this.domNodes.set(element.id, node);
2619
- }
2620
- const size = "size" in element ? element.size : null;
2621
- Object.assign(node.style, {
2622
- display: "block",
2623
- left: `${element.position.x}px`,
2624
- top: `${element.position.y}px`,
2625
- width: size ? `${size.w}px` : "auto",
2626
- height: size ? `${size.h}px` : "auto",
2627
- zIndex: String(zIndex)
2628
- });
2629
- this.renderDomContent(node, element);
2630
- }
2631
- renderDomContent(node, element) {
2632
- if (element.type === "note") {
2633
- if (!node.dataset["initialized"]) {
2634
- node.dataset["initialized"] = "true";
2635
- Object.assign(node.style, {
2636
- backgroundColor: element.backgroundColor,
2637
- color: element.textColor,
2638
- padding: "8px",
2639
- borderRadius: "4px",
2640
- boxShadow: "0 2px 8px rgba(0,0,0,0.15)",
2641
- fontSize: "14px",
2642
- overflow: "hidden",
2643
- cursor: "default",
2644
- userSelect: "none",
2645
- wordWrap: "break-word"
2646
- });
2647
- node.textContent = element.text || "";
2648
- node.addEventListener("dblclick", (e) => {
2649
- e.stopPropagation();
2650
- const id = node.dataset["elementId"];
2651
- if (id) this.startEditingElement(id);
2652
- });
2653
- }
2654
- if (!this.noteEditor.isEditing || this.noteEditor.editingElementId !== element.id) {
2655
- if (node.textContent !== element.text) {
2656
- node.textContent = element.text || "";
2657
- }
2658
- node.style.backgroundColor = element.backgroundColor;
2659
- node.style.color = element.textColor;
2660
- }
2661
- }
2662
- if (element.type === "html" && !node.dataset["initialized"]) {
2663
- const content = this.htmlContent.get(element.id);
2664
- if (content) {
2665
- node.dataset["initialized"] = "true";
2666
- Object.assign(node.style, {
2667
- overflow: "hidden",
2668
- pointerEvents: "none"
2669
- });
2670
- node.appendChild(content);
2671
- }
2672
- }
2673
- if (element.type === "text") {
2674
- if (!node.dataset["initialized"]) {
2675
- node.dataset["initialized"] = "true";
2676
- Object.assign(node.style, {
2677
- padding: "2px",
2678
- fontSize: `${element.fontSize}px`,
2679
- color: element.color,
2680
- textAlign: element.textAlign,
2681
- background: "none",
2682
- border: "none",
2683
- boxShadow: "none",
2684
- overflow: "visible",
2685
- cursor: "default",
2686
- userSelect: "none",
2687
- wordWrap: "break-word",
2688
- whiteSpace: "pre-wrap",
2689
- lineHeight: "1.4"
2690
- });
2691
- node.textContent = element.text || "";
2692
- node.addEventListener("dblclick", (e) => {
2693
- e.stopPropagation();
2694
- const id = node.dataset["elementId"];
2695
- if (id) this.startEditingElement(id);
2696
- });
2697
- }
2698
- if (!this.noteEditor.isEditing || this.noteEditor.editingElementId !== element.id) {
2699
- if (node.textContent !== element.text) {
2700
- node.textContent = element.text || "";
2701
- }
2702
- Object.assign(node.style, {
2703
- fontSize: `${element.fontSize}px`,
2704
- color: element.color,
2705
- textAlign: element.textAlign
2706
- });
2707
- }
2708
- }
2709
- }
2710
2851
  unbindArrowsFrom(removedElement) {
2711
2852
  const boundArrows = findBoundArrows(removedElement.id, this.store);
2712
2853
  const bounds = getElementBounds(removedElement);
@@ -2741,35 +2882,6 @@ var Viewport = class {
2741
2882
  }
2742
2883
  }
2743
2884
  }
2744
- hideDomNode(id) {
2745
- const node = this.domNodes.get(id);
2746
- if (node) node.style.display = "none";
2747
- }
2748
- removeDomNode(id) {
2749
- this.htmlContent.delete(id);
2750
- const node = this.domNodes.get(id);
2751
- if (node) {
2752
- node.remove();
2753
- this.domNodes.delete(id);
2754
- }
2755
- this.requestRender();
2756
- }
2757
- clearDomNodes() {
2758
- this.domNodes.forEach((node) => node.remove());
2759
- this.domNodes.clear();
2760
- this.htmlContent.clear();
2761
- this.requestRender();
2762
- }
2763
- reattachHtmlContent() {
2764
- for (const el of this.store.getElementsByType("html")) {
2765
- if (el.domId) {
2766
- const dom = document.getElementById(el.domId);
2767
- if (dom) {
2768
- this.htmlContent.set(el.id, dom);
2769
- }
2770
- }
2771
- }
2772
- }
2773
2885
  createWrapper() {
2774
2886
  const el = document.createElement("div");
2775
2887
  Object.assign(el.style, {
@@ -2810,8 +2922,7 @@ var Viewport = class {
2810
2922
  syncCanvasSize() {
2811
2923
  const rect = this.container.getBoundingClientRect();
2812
2924
  const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
2813
- this.canvasEl.width = rect.width * dpr;
2814
- this.canvasEl.height = rect.height * dpr;
2925
+ this.renderLoop.setCanvasSize(rect.width * dpr, rect.height * dpr);
2815
2926
  this.requestRender();
2816
2927
  }
2817
2928
  observeResize() {
@@ -2861,6 +2972,7 @@ var PencilTool = class {
2861
2972
  color;
2862
2973
  width;
2863
2974
  smoothing;
2975
+ optionListeners = /* @__PURE__ */ new Set();
2864
2976
  constructor(options = {}) {
2865
2977
  this.color = options.color ?? "#000000";
2866
2978
  this.width = options.width ?? 2;
@@ -2872,10 +2984,18 @@ var PencilTool = class {
2872
2984
  onDeactivate(ctx) {
2873
2985
  ctx.setCursor?.("default");
2874
2986
  }
2987
+ getOptions() {
2988
+ return { color: this.color, width: this.width, smoothing: this.smoothing };
2989
+ }
2990
+ onOptionsChange(listener) {
2991
+ this.optionListeners.add(listener);
2992
+ return () => this.optionListeners.delete(listener);
2993
+ }
2875
2994
  setOptions(options) {
2876
2995
  if (options.color !== void 0) this.color = options.color;
2877
2996
  if (options.width !== void 0) this.width = options.width;
2878
2997
  if (options.smoothing !== void 0) this.smoothing = options.smoothing;
2998
+ this.notifyOptionsChange();
2879
2999
  }
2880
3000
  onPointerDown(state, ctx) {
2881
3001
  this.drawing = true;
@@ -2908,6 +3028,9 @@ var PencilTool = class {
2908
3028
  this.points = [];
2909
3029
  ctx.requestRender();
2910
3030
  }
3031
+ notifyOptionsChange() {
3032
+ for (const listener of this.optionListeners) listener();
3033
+ }
2911
3034
  renderOverlay(ctx) {
2912
3035
  if (!this.drawing || this.points.length < 2) return;
2913
3036
  ctx.save();
@@ -2944,6 +3067,9 @@ var EraserTool = class {
2944
3067
  this.radius = options.radius ?? DEFAULT_RADIUS;
2945
3068
  this.cursor = makeEraserCursor(this.radius);
2946
3069
  }
3070
+ getOptions() {
3071
+ return { radius: this.radius };
3072
+ }
2947
3073
  onActivate(ctx) {
2948
3074
  ctx.setCursor?.(this.cursor);
2949
3075
  }
@@ -3532,13 +3658,25 @@ var ArrowTool = class {
3532
3658
  fromBinding;
3533
3659
  fromTarget = null;
3534
3660
  toTarget = null;
3661
+ optionListeners = /* @__PURE__ */ new Set();
3535
3662
  constructor(options = {}) {
3536
3663
  this.color = options.color ?? "#000000";
3537
3664
  this.width = options.width ?? 2;
3538
3665
  }
3666
+ getOptions() {
3667
+ return { color: this.color, width: this.width };
3668
+ }
3669
+ onOptionsChange(listener) {
3670
+ this.optionListeners.add(listener);
3671
+ return () => this.optionListeners.delete(listener);
3672
+ }
3539
3673
  setOptions(options) {
3540
3674
  if (options.color !== void 0) this.color = options.color;
3541
3675
  if (options.width !== void 0) this.width = options.width;
3676
+ this.notifyOptionsChange();
3677
+ }
3678
+ notifyOptionsChange() {
3679
+ for (const listener of this.optionListeners) listener();
3542
3680
  }
3543
3681
  layerFilter(ctx) {
3544
3682
  const activeLayerId = ctx.activeLayerId;
@@ -3660,15 +3798,31 @@ var NoteTool = class {
3660
3798
  backgroundColor;
3661
3799
  textColor;
3662
3800
  size;
3801
+ optionListeners = /* @__PURE__ */ new Set();
3663
3802
  constructor(options = {}) {
3664
3803
  this.backgroundColor = options.backgroundColor ?? "#ffeb3b";
3665
3804
  this.textColor = options.textColor ?? "#000000";
3666
3805
  this.size = options.size ?? { w: 200, h: 100 };
3667
3806
  }
3807
+ getOptions() {
3808
+ return {
3809
+ backgroundColor: this.backgroundColor,
3810
+ textColor: this.textColor,
3811
+ size: { ...this.size }
3812
+ };
3813
+ }
3814
+ onOptionsChange(listener) {
3815
+ this.optionListeners.add(listener);
3816
+ return () => this.optionListeners.delete(listener);
3817
+ }
3668
3818
  setOptions(options) {
3669
3819
  if (options.backgroundColor !== void 0) this.backgroundColor = options.backgroundColor;
3670
3820
  if (options.textColor !== void 0) this.textColor = options.textColor;
3671
3821
  if (options.size !== void 0) this.size = options.size;
3822
+ this.notifyOptionsChange();
3823
+ }
3824
+ notifyOptionsChange() {
3825
+ for (const listener of this.optionListeners) listener();
3672
3826
  }
3673
3827
  onPointerDown(_state, _ctx) {
3674
3828
  }
@@ -3699,15 +3853,27 @@ var TextTool = class {
3699
3853
  fontSize;
3700
3854
  color;
3701
3855
  textAlign;
3856
+ optionListeners = /* @__PURE__ */ new Set();
3702
3857
  constructor(options = {}) {
3703
3858
  this.fontSize = options.fontSize ?? 16;
3704
3859
  this.color = options.color ?? "#1a1a1a";
3705
3860
  this.textAlign = options.textAlign ?? "left";
3706
3861
  }
3862
+ getOptions() {
3863
+ return { fontSize: this.fontSize, color: this.color, textAlign: this.textAlign };
3864
+ }
3865
+ onOptionsChange(listener) {
3866
+ this.optionListeners.add(listener);
3867
+ return () => this.optionListeners.delete(listener);
3868
+ }
3707
3869
  setOptions(options) {
3708
3870
  if (options.fontSize !== void 0) this.fontSize = options.fontSize;
3709
3871
  if (options.color !== void 0) this.color = options.color;
3710
3872
  if (options.textAlign !== void 0) this.textAlign = options.textAlign;
3873
+ this.notifyOptionsChange();
3874
+ }
3875
+ notifyOptionsChange() {
3876
+ for (const listener of this.optionListeners) listener();
3711
3877
  }
3712
3878
  onActivate(ctx) {
3713
3879
  ctx.setCursor?.("text");
@@ -3779,17 +3945,31 @@ var ShapeTool = class {
3779
3945
  strokeColor;
3780
3946
  strokeWidth;
3781
3947
  fillColor;
3948
+ optionListeners = /* @__PURE__ */ new Set();
3782
3949
  constructor(options = {}) {
3783
3950
  this.shape = options.shape ?? "rectangle";
3784
3951
  this.strokeColor = options.strokeColor ?? "#000000";
3785
3952
  this.strokeWidth = options.strokeWidth ?? 2;
3786
3953
  this.fillColor = options.fillColor ?? "none";
3787
3954
  }
3955
+ getOptions() {
3956
+ return {
3957
+ shape: this.shape,
3958
+ strokeColor: this.strokeColor,
3959
+ strokeWidth: this.strokeWidth,
3960
+ fillColor: this.fillColor
3961
+ };
3962
+ }
3963
+ onOptionsChange(listener) {
3964
+ this.optionListeners.add(listener);
3965
+ return () => this.optionListeners.delete(listener);
3966
+ }
3788
3967
  setOptions(options) {
3789
3968
  if (options.shape !== void 0) this.shape = options.shape;
3790
3969
  if (options.strokeColor !== void 0) this.strokeColor = options.strokeColor;
3791
3970
  if (options.strokeWidth !== void 0) this.strokeWidth = options.strokeWidth;
3792
3971
  if (options.fillColor !== void 0) this.fillColor = options.fillColor;
3972
+ this.notifyOptionsChange();
3793
3973
  }
3794
3974
  onActivate(_ctx) {
3795
3975
  if (typeof window !== "undefined") {
@@ -3876,6 +4056,9 @@ var ShapeTool = class {
3876
4056
  }
3877
4057
  return { position: { x, y }, size: { w, h } };
3878
4058
  }
4059
+ notifyOptionsChange() {
4060
+ for (const listener of this.optionListeners) listener();
4061
+ }
3879
4062
  snap(point, ctx) {
3880
4063
  return ctx.snapToGrid && ctx.gridSize ? snapPoint(point, ctx.gridSize) : point;
3881
4064
  }
@@ -3928,7 +4111,7 @@ var UpdateLayerCommand = class {
3928
4111
  };
3929
4112
 
3930
4113
  // src/index.ts
3931
- var VERSION = "0.8.5";
4114
+ var VERSION = "0.8.6";
3932
4115
  // Annotate the CommonJS export names for ESM import in node:
3933
4116
  0 && (module.exports = {
3934
4117
  AddElementCommand,