@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.js CHANGED
@@ -641,6 +641,15 @@ var ElementStore = class {
641
641
  on(event, listener) {
642
642
  return this.bus.on(event, listener);
643
643
  }
644
+ onChange(listener) {
645
+ const unsubs = [
646
+ this.bus.on("add", listener),
647
+ this.bus.on("remove", listener),
648
+ this.bus.on("update", listener),
649
+ this.bus.on("clear", listener)
650
+ ];
651
+ return () => unsubs.forEach((fn) => fn());
652
+ }
644
653
  };
645
654
 
646
655
  // src/elements/arrow-geometry.ts
@@ -2092,6 +2101,10 @@ var LayerManager = class {
2092
2101
  this.updateLayerDirect(id, { locked });
2093
2102
  return true;
2094
2103
  }
2104
+ setLayerOpacity(id, opacity) {
2105
+ if (!this.layers.has(id)) return;
2106
+ this.updateLayerDirect(id, { opacity: Math.max(0, Math.min(1, opacity)) });
2107
+ }
2095
2108
  setActiveLayer(id) {
2096
2109
  if (!this.layers.has(id)) return;
2097
2110
  this._activeLayerId = id;
@@ -2147,6 +2160,294 @@ var LayerManager = class {
2147
2160
  }
2148
2161
  };
2149
2162
 
2163
+ // src/canvas/interact-mode.ts
2164
+ var InteractMode = class {
2165
+ interactingElementId = null;
2166
+ getNode;
2167
+ constructor(deps) {
2168
+ this.getNode = deps.getNode;
2169
+ }
2170
+ startInteracting(id) {
2171
+ this.stopInteracting();
2172
+ const node = this.getNode(id);
2173
+ if (!node) return;
2174
+ this.interactingElementId = id;
2175
+ node.style.pointerEvents = "auto";
2176
+ node.addEventListener("pointerdown", this.onNodePointerDown);
2177
+ window.addEventListener("keydown", this.onKeyDown);
2178
+ window.addEventListener("pointerdown", this.onPointerDown);
2179
+ }
2180
+ stopInteracting() {
2181
+ if (!this.interactingElementId) return;
2182
+ const node = this.getNode(this.interactingElementId);
2183
+ if (node) {
2184
+ node.style.pointerEvents = "none";
2185
+ node.removeEventListener("pointerdown", this.onNodePointerDown);
2186
+ }
2187
+ this.interactingElementId = null;
2188
+ window.removeEventListener("keydown", this.onKeyDown);
2189
+ window.removeEventListener("pointerdown", this.onPointerDown);
2190
+ }
2191
+ isInteracting() {
2192
+ return this.interactingElementId !== null;
2193
+ }
2194
+ destroy() {
2195
+ this.stopInteracting();
2196
+ }
2197
+ onNodePointerDown = (e) => {
2198
+ e.stopPropagation();
2199
+ };
2200
+ onKeyDown = (e) => {
2201
+ if (e.key === "Escape") {
2202
+ this.stopInteracting();
2203
+ }
2204
+ };
2205
+ onPointerDown = (e) => {
2206
+ if (!this.interactingElementId) return;
2207
+ const target = e.target;
2208
+ if (!(target instanceof Element)) {
2209
+ this.stopInteracting();
2210
+ return;
2211
+ }
2212
+ const node = this.getNode(this.interactingElementId);
2213
+ if (node && !node.contains(target)) {
2214
+ this.stopInteracting();
2215
+ }
2216
+ };
2217
+ };
2218
+
2219
+ // src/canvas/dom-node-manager.ts
2220
+ var DomNodeManager = class {
2221
+ domNodes = /* @__PURE__ */ new Map();
2222
+ htmlContent = /* @__PURE__ */ new Map();
2223
+ domLayer;
2224
+ onEditRequest;
2225
+ isEditingElement;
2226
+ constructor(deps) {
2227
+ this.domLayer = deps.domLayer;
2228
+ this.onEditRequest = deps.onEditRequest;
2229
+ this.isEditingElement = deps.isEditingElement;
2230
+ }
2231
+ getNode(id) {
2232
+ return this.domNodes.get(id);
2233
+ }
2234
+ storeHtmlContent(elementId, dom) {
2235
+ this.htmlContent.set(elementId, dom);
2236
+ }
2237
+ syncDomNode(element, zIndex = 0) {
2238
+ let node = this.domNodes.get(element.id);
2239
+ if (!node) {
2240
+ node = document.createElement("div");
2241
+ node.dataset["elementId"] = element.id;
2242
+ Object.assign(node.style, {
2243
+ position: "absolute",
2244
+ pointerEvents: "auto"
2245
+ });
2246
+ this.domLayer.appendChild(node);
2247
+ this.domNodes.set(element.id, node);
2248
+ }
2249
+ const size = "size" in element ? element.size : null;
2250
+ Object.assign(node.style, {
2251
+ display: "block",
2252
+ left: `${element.position.x}px`,
2253
+ top: `${element.position.y}px`,
2254
+ width: size ? `${size.w}px` : "auto",
2255
+ height: size ? `${size.h}px` : "auto",
2256
+ zIndex: String(zIndex)
2257
+ });
2258
+ this.renderDomContent(node, element);
2259
+ }
2260
+ hideDomNode(id) {
2261
+ const node = this.domNodes.get(id);
2262
+ if (node) node.style.display = "none";
2263
+ }
2264
+ removeDomNode(id) {
2265
+ this.htmlContent.delete(id);
2266
+ const node = this.domNodes.get(id);
2267
+ if (node) {
2268
+ node.remove();
2269
+ this.domNodes.delete(id);
2270
+ }
2271
+ }
2272
+ clearDomNodes() {
2273
+ this.domNodes.forEach((node) => node.remove());
2274
+ this.domNodes.clear();
2275
+ this.htmlContent.clear();
2276
+ }
2277
+ reattachHtmlContent(store) {
2278
+ for (const el of store.getElementsByType("html")) {
2279
+ if (el.domId) {
2280
+ const dom = document.getElementById(el.domId);
2281
+ if (dom) {
2282
+ this.htmlContent.set(el.id, dom);
2283
+ }
2284
+ }
2285
+ }
2286
+ }
2287
+ renderDomContent(node, element) {
2288
+ if (element.type === "note") {
2289
+ if (!node.dataset["initialized"]) {
2290
+ node.dataset["initialized"] = "true";
2291
+ Object.assign(node.style, {
2292
+ backgroundColor: element.backgroundColor,
2293
+ color: element.textColor,
2294
+ padding: "8px",
2295
+ borderRadius: "4px",
2296
+ boxShadow: "0 2px 8px rgba(0,0,0,0.15)",
2297
+ fontSize: "14px",
2298
+ overflow: "hidden",
2299
+ cursor: "default",
2300
+ userSelect: "none",
2301
+ wordWrap: "break-word"
2302
+ });
2303
+ node.textContent = element.text || "";
2304
+ node.addEventListener("dblclick", (e) => {
2305
+ e.stopPropagation();
2306
+ const id = node.dataset["elementId"];
2307
+ if (id) this.onEditRequest(id);
2308
+ });
2309
+ }
2310
+ if (!this.isEditingElement(element.id)) {
2311
+ if (node.textContent !== element.text) {
2312
+ node.textContent = element.text || "";
2313
+ }
2314
+ node.style.backgroundColor = element.backgroundColor;
2315
+ node.style.color = element.textColor;
2316
+ }
2317
+ }
2318
+ if (element.type === "html" && !node.dataset["initialized"]) {
2319
+ const content = this.htmlContent.get(element.id);
2320
+ if (content) {
2321
+ node.dataset["initialized"] = "true";
2322
+ Object.assign(node.style, {
2323
+ overflow: "hidden",
2324
+ pointerEvents: "none"
2325
+ });
2326
+ node.appendChild(content);
2327
+ }
2328
+ }
2329
+ if (element.type === "text") {
2330
+ if (!node.dataset["initialized"]) {
2331
+ node.dataset["initialized"] = "true";
2332
+ Object.assign(node.style, {
2333
+ padding: "2px",
2334
+ fontSize: `${element.fontSize}px`,
2335
+ color: element.color,
2336
+ textAlign: element.textAlign,
2337
+ background: "none",
2338
+ border: "none",
2339
+ boxShadow: "none",
2340
+ overflow: "visible",
2341
+ cursor: "default",
2342
+ userSelect: "none",
2343
+ wordWrap: "break-word",
2344
+ whiteSpace: "pre-wrap",
2345
+ lineHeight: "1.4"
2346
+ });
2347
+ node.textContent = element.text || "";
2348
+ node.addEventListener("dblclick", (e) => {
2349
+ e.stopPropagation();
2350
+ const id = node.dataset["elementId"];
2351
+ if (id) this.onEditRequest(id);
2352
+ });
2353
+ }
2354
+ if (!this.isEditingElement(element.id)) {
2355
+ if (node.textContent !== element.text) {
2356
+ node.textContent = element.text || "";
2357
+ }
2358
+ Object.assign(node.style, {
2359
+ fontSize: `${element.fontSize}px`,
2360
+ color: element.color,
2361
+ textAlign: element.textAlign
2362
+ });
2363
+ }
2364
+ }
2365
+ }
2366
+ };
2367
+
2368
+ // src/canvas/render-loop.ts
2369
+ var RenderLoop = class {
2370
+ needsRender = false;
2371
+ animFrameId = 0;
2372
+ canvasEl;
2373
+ camera;
2374
+ background;
2375
+ store;
2376
+ renderer;
2377
+ toolManager;
2378
+ layerManager;
2379
+ domNodeManager;
2380
+ constructor(deps) {
2381
+ this.canvasEl = deps.canvasEl;
2382
+ this.camera = deps.camera;
2383
+ this.background = deps.background;
2384
+ this.store = deps.store;
2385
+ this.renderer = deps.renderer;
2386
+ this.toolManager = deps.toolManager;
2387
+ this.layerManager = deps.layerManager;
2388
+ this.domNodeManager = deps.domNodeManager;
2389
+ }
2390
+ requestRender() {
2391
+ this.needsRender = true;
2392
+ }
2393
+ flush() {
2394
+ if (this.needsRender) {
2395
+ this.render();
2396
+ this.needsRender = false;
2397
+ }
2398
+ }
2399
+ start() {
2400
+ const loop = () => {
2401
+ if (this.needsRender) {
2402
+ this.render();
2403
+ this.needsRender = false;
2404
+ }
2405
+ this.animFrameId = requestAnimationFrame(loop);
2406
+ };
2407
+ this.animFrameId = requestAnimationFrame(loop);
2408
+ }
2409
+ stop() {
2410
+ cancelAnimationFrame(this.animFrameId);
2411
+ }
2412
+ setCanvasSize(width, height) {
2413
+ this.canvasEl.width = width;
2414
+ this.canvasEl.height = height;
2415
+ }
2416
+ render() {
2417
+ const ctx = this.canvasEl.getContext("2d");
2418
+ if (!ctx) return;
2419
+ const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
2420
+ ctx.save();
2421
+ ctx.scale(dpr, dpr);
2422
+ this.renderer.setCanvasSize(this.canvasEl.clientWidth, this.canvasEl.clientHeight);
2423
+ this.background.render(ctx, this.camera);
2424
+ ctx.save();
2425
+ ctx.translate(this.camera.position.x, this.camera.position.y);
2426
+ ctx.scale(this.camera.zoom, this.camera.zoom);
2427
+ const allElements = this.store.getAll();
2428
+ let domZIndex = 0;
2429
+ for (const element of allElements) {
2430
+ if (!this.layerManager.isLayerVisible(element.layerId)) {
2431
+ if (this.renderer.isDomElement(element)) {
2432
+ this.domNodeManager.hideDomNode(element.id);
2433
+ }
2434
+ continue;
2435
+ }
2436
+ if (this.renderer.isDomElement(element)) {
2437
+ this.domNodeManager.syncDomNode(element, domZIndex++);
2438
+ } else {
2439
+ this.renderer.renderCanvasElement(ctx, element);
2440
+ }
2441
+ }
2442
+ const activeTool = this.toolManager.activeTool;
2443
+ if (activeTool?.renderOverlay) {
2444
+ activeTool.renderOverlay(ctx);
2445
+ }
2446
+ ctx.restore();
2447
+ ctx.restore();
2448
+ }
2449
+ };
2450
+
2150
2451
  // src/canvas/viewport.ts
2151
2452
  var Viewport = class {
2152
2453
  constructor(container, options = {}) {
@@ -2192,6 +2493,24 @@ var Viewport = class {
2192
2493
  historyRecorder: this.historyRecorder,
2193
2494
  historyStack: this.history
2194
2495
  });
2496
+ this.domNodeManager = new DomNodeManager({
2497
+ domLayer: this.domLayer,
2498
+ onEditRequest: (id) => this.startEditingElement(id),
2499
+ isEditingElement: (id) => this.noteEditor.isEditing && this.noteEditor.editingElementId === id
2500
+ });
2501
+ this.interactMode = new InteractMode({
2502
+ getNode: (id) => this.domNodeManager.getNode(id)
2503
+ });
2504
+ this.renderLoop = new RenderLoop({
2505
+ canvasEl: this.canvasEl,
2506
+ camera: this.camera,
2507
+ background: this.background,
2508
+ store: this.store,
2509
+ renderer: this.renderer,
2510
+ toolManager: this.toolManager,
2511
+ layerManager: this.layerManager,
2512
+ domNodeManager: this.domNodeManager
2513
+ });
2195
2514
  this.unsubCamera = this.camera.onChange(() => {
2196
2515
  this.applyCameraTransform();
2197
2516
  this.requestRender();
@@ -2200,10 +2519,14 @@ var Viewport = class {
2200
2519
  this.store.on("add", () => this.requestRender()),
2201
2520
  this.store.on("remove", (el) => {
2202
2521
  this.unbindArrowsFrom(el);
2203
- this.removeDomNode(el.id);
2522
+ this.domNodeManager.removeDomNode(el.id);
2523
+ this.requestRender();
2204
2524
  }),
2205
2525
  this.store.on("update", () => this.requestRender()),
2206
- this.store.on("clear", () => this.clearDomNodes())
2526
+ this.store.on("clear", () => {
2527
+ this.domNodeManager.clearDomNodes();
2528
+ this.requestRender();
2529
+ })
2207
2530
  ];
2208
2531
  this.layerManager.on("change", () => {
2209
2532
  this.toolContext.activeLayerId = this.layerManager.activeLayerId;
@@ -2214,7 +2537,7 @@ var Viewport = class {
2214
2537
  this.wrapper.addEventListener("drop", this.onDrop);
2215
2538
  this.observeResize();
2216
2539
  this.syncCanvasSize();
2217
- this.startRenderLoop();
2540
+ this.renderLoop.start();
2218
2541
  }
2219
2542
  camera;
2220
2543
  store;
@@ -2233,13 +2556,11 @@ var Viewport = class {
2233
2556
  historyRecorder;
2234
2557
  toolContext;
2235
2558
  resizeObserver = null;
2236
- animFrameId = 0;
2237
2559
  _snapToGrid = false;
2238
2560
  _gridSize;
2239
- needsRender = true;
2240
- domNodes = /* @__PURE__ */ new Map();
2241
- htmlContent = /* @__PURE__ */ new Map();
2242
- interactingElementId = null;
2561
+ renderLoop;
2562
+ domNodeManager;
2563
+ interactMode;
2243
2564
  get ctx() {
2244
2565
  return this.canvasEl.getContext("2d");
2245
2566
  }
@@ -2251,7 +2572,7 @@ var Viewport = class {
2251
2572
  this.toolContext.snapToGrid = enabled;
2252
2573
  }
2253
2574
  requestRender() {
2254
- this.needsRender = true;
2575
+ this.renderLoop.requestRender();
2255
2576
  }
2256
2577
  exportState() {
2257
2578
  return exportState(this.store.snapshot(), this.camera, this.layerManager.snapshot());
@@ -2265,12 +2586,12 @@ var Viewport = class {
2265
2586
  loadState(state) {
2266
2587
  this.historyRecorder.pause();
2267
2588
  this.noteEditor.destroy(this.store);
2268
- this.clearDomNodes();
2589
+ this.domNodeManager.clearDomNodes();
2269
2590
  this.store.loadSnapshot(state.elements);
2270
2591
  if (state.layers && state.layers.length > 0) {
2271
2592
  this.layerManager.loadSnapshot(state.layers);
2272
2593
  }
2273
- this.reattachHtmlContent();
2594
+ this.domNodeManager.reattachHtmlContent(this.store);
2274
2595
  this.history.clear();
2275
2596
  this.historyRecorder.resume();
2276
2597
  this.camera.moveTo(state.camera.position.x, state.camera.position.y);
@@ -2309,7 +2630,7 @@ var Viewport = class {
2309
2630
  domId,
2310
2631
  layerId: this.layerManager.activeLayerId
2311
2632
  });
2312
- this.htmlContent.set(el.id, dom);
2633
+ this.domNodeManager.storeHtmlContent(el.id, dom);
2313
2634
  this.historyRecorder.begin();
2314
2635
  this.store.add(el);
2315
2636
  this.historyRecorder.commit();
@@ -2345,8 +2666,8 @@ var Viewport = class {
2345
2666
  this.requestRender();
2346
2667
  }
2347
2668
  destroy() {
2348
- cancelAnimationFrame(this.animFrameId);
2349
- this.stopInteracting();
2669
+ this.renderLoop.stop();
2670
+ this.interactMode.destroy();
2350
2671
  this.noteEditor.destroy(this.store);
2351
2672
  this.historyRecorder.destroy();
2352
2673
  this.wrapper.removeEventListener("dblclick", this.onDblClick);
@@ -2359,54 +2680,11 @@ var Viewport = class {
2359
2680
  this.resizeObserver = null;
2360
2681
  this.wrapper.remove();
2361
2682
  }
2362
- startRenderLoop() {
2363
- const loop = () => {
2364
- if (this.needsRender) {
2365
- this.render();
2366
- this.needsRender = false;
2367
- }
2368
- this.animFrameId = requestAnimationFrame(loop);
2369
- };
2370
- this.animFrameId = requestAnimationFrame(loop);
2371
- }
2372
- render() {
2373
- const ctx = this.ctx;
2374
- if (!ctx) return;
2375
- const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
2376
- ctx.save();
2377
- ctx.scale(dpr, dpr);
2378
- this.renderer.setCanvasSize(this.canvasEl.clientWidth, this.canvasEl.clientHeight);
2379
- this.background.render(ctx, this.camera);
2380
- ctx.save();
2381
- ctx.translate(this.camera.position.x, this.camera.position.y);
2382
- ctx.scale(this.camera.zoom, this.camera.zoom);
2383
- const allElements = this.store.getAll();
2384
- let domZIndex = 0;
2385
- for (const element of allElements) {
2386
- if (!this.layerManager.isLayerVisible(element.layerId)) {
2387
- if (this.renderer.isDomElement(element)) {
2388
- this.hideDomNode(element.id);
2389
- }
2390
- continue;
2391
- }
2392
- if (this.renderer.isDomElement(element)) {
2393
- this.syncDomNode(element, domZIndex++);
2394
- } else {
2395
- this.renderer.renderCanvasElement(ctx, element);
2396
- }
2397
- }
2398
- const activeTool = this.toolManager.activeTool;
2399
- if (activeTool?.renderOverlay) {
2400
- activeTool.renderOverlay(ctx);
2401
- }
2402
- ctx.restore();
2403
- ctx.restore();
2404
- }
2405
2683
  startEditingElement(id) {
2406
2684
  const element = this.store.getById(id);
2407
2685
  if (!element || element.type !== "note" && element.type !== "text") return;
2408
- this.render();
2409
- const node = this.domNodes.get(id);
2686
+ this.renderLoop.flush();
2687
+ const node = this.domNodeManager.getNode(id);
2410
2688
  if (node) {
2411
2689
  this.noteEditor.startEditing(node, id, this.store);
2412
2690
  }
@@ -2420,7 +2698,7 @@ var Viewport = class {
2420
2698
  this.historyRecorder.commit();
2421
2699
  return;
2422
2700
  }
2423
- const node = this.domNodes.get(elementId);
2701
+ const node = this.domNodeManager.getNode(elementId);
2424
2702
  if (node && "size" in element) {
2425
2703
  const measuredHeight = node.scrollHeight;
2426
2704
  if (measuredHeight !== element.size.h) {
@@ -2448,7 +2726,7 @@ var Viewport = class {
2448
2726
  const world = this.camera.screenToWorld(screen);
2449
2727
  const hit = this.hitTestWorld(world);
2450
2728
  if (hit?.type === "html") {
2451
- this.startInteracting(hit.id);
2729
+ this.interactMode.startInteracting(hit.id);
2452
2730
  }
2453
2731
  };
2454
2732
  hitTestWorld(world) {
@@ -2463,44 +2741,9 @@ var Viewport = class {
2463
2741
  }
2464
2742
  return null;
2465
2743
  }
2466
- startInteracting(id) {
2467
- this.stopInteracting();
2468
- const node = this.domNodes.get(id);
2469
- if (!node) return;
2470
- this.interactingElementId = id;
2471
- node.style.pointerEvents = "auto";
2472
- node.addEventListener("pointerdown", this.onInteractNodePointerDown);
2473
- window.addEventListener("keydown", this.onInteractKeyDown);
2474
- window.addEventListener("pointerdown", this.onInteractPointerDown);
2475
- }
2476
2744
  stopInteracting() {
2477
- if (!this.interactingElementId) return;
2478
- const node = this.domNodes.get(this.interactingElementId);
2479
- if (node) {
2480
- node.style.pointerEvents = "none";
2481
- node.removeEventListener("pointerdown", this.onInteractNodePointerDown);
2482
- }
2483
- this.interactingElementId = null;
2484
- window.removeEventListener("keydown", this.onInteractKeyDown);
2485
- window.removeEventListener("pointerdown", this.onInteractPointerDown);
2745
+ this.interactMode.stopInteracting();
2486
2746
  }
2487
- onInteractNodePointerDown = (e) => {
2488
- e.stopPropagation();
2489
- };
2490
- onInteractKeyDown = (e) => {
2491
- if (e.key === "Escape") {
2492
- this.stopInteracting();
2493
- }
2494
- };
2495
- onInteractPointerDown = (e) => {
2496
- if (!this.interactingElementId) return;
2497
- const target = e.target;
2498
- if (!target) return;
2499
- const node = this.domNodes.get(this.interactingElementId);
2500
- if (node && !node.contains(target)) {
2501
- this.stopInteracting();
2502
- }
2503
- };
2504
2747
  onDragOver = (e) => {
2505
2748
  e.preventDefault();
2506
2749
  };
@@ -2522,108 +2765,6 @@ var Viewport = class {
2522
2765
  reader.readAsDataURL(file);
2523
2766
  }
2524
2767
  };
2525
- syncDomNode(element, zIndex = 0) {
2526
- let node = this.domNodes.get(element.id);
2527
- if (!node) {
2528
- node = document.createElement("div");
2529
- node.dataset["elementId"] = element.id;
2530
- Object.assign(node.style, {
2531
- position: "absolute",
2532
- pointerEvents: "auto"
2533
- });
2534
- this.domLayer.appendChild(node);
2535
- this.domNodes.set(element.id, node);
2536
- }
2537
- const size = "size" in element ? element.size : null;
2538
- Object.assign(node.style, {
2539
- display: "block",
2540
- left: `${element.position.x}px`,
2541
- top: `${element.position.y}px`,
2542
- width: size ? `${size.w}px` : "auto",
2543
- height: size ? `${size.h}px` : "auto",
2544
- zIndex: String(zIndex)
2545
- });
2546
- this.renderDomContent(node, element);
2547
- }
2548
- renderDomContent(node, element) {
2549
- if (element.type === "note") {
2550
- if (!node.dataset["initialized"]) {
2551
- node.dataset["initialized"] = "true";
2552
- Object.assign(node.style, {
2553
- backgroundColor: element.backgroundColor,
2554
- color: element.textColor,
2555
- padding: "8px",
2556
- borderRadius: "4px",
2557
- boxShadow: "0 2px 8px rgba(0,0,0,0.15)",
2558
- fontSize: "14px",
2559
- overflow: "hidden",
2560
- cursor: "default",
2561
- userSelect: "none",
2562
- wordWrap: "break-word"
2563
- });
2564
- node.textContent = element.text || "";
2565
- node.addEventListener("dblclick", (e) => {
2566
- e.stopPropagation();
2567
- const id = node.dataset["elementId"];
2568
- if (id) this.startEditingElement(id);
2569
- });
2570
- }
2571
- if (!this.noteEditor.isEditing || this.noteEditor.editingElementId !== element.id) {
2572
- if (node.textContent !== element.text) {
2573
- node.textContent = element.text || "";
2574
- }
2575
- node.style.backgroundColor = element.backgroundColor;
2576
- node.style.color = element.textColor;
2577
- }
2578
- }
2579
- if (element.type === "html" && !node.dataset["initialized"]) {
2580
- const content = this.htmlContent.get(element.id);
2581
- if (content) {
2582
- node.dataset["initialized"] = "true";
2583
- Object.assign(node.style, {
2584
- overflow: "hidden",
2585
- pointerEvents: "none"
2586
- });
2587
- node.appendChild(content);
2588
- }
2589
- }
2590
- if (element.type === "text") {
2591
- if (!node.dataset["initialized"]) {
2592
- node.dataset["initialized"] = "true";
2593
- Object.assign(node.style, {
2594
- padding: "2px",
2595
- fontSize: `${element.fontSize}px`,
2596
- color: element.color,
2597
- textAlign: element.textAlign,
2598
- background: "none",
2599
- border: "none",
2600
- boxShadow: "none",
2601
- overflow: "visible",
2602
- cursor: "default",
2603
- userSelect: "none",
2604
- wordWrap: "break-word",
2605
- whiteSpace: "pre-wrap",
2606
- lineHeight: "1.4"
2607
- });
2608
- node.textContent = element.text || "";
2609
- node.addEventListener("dblclick", (e) => {
2610
- e.stopPropagation();
2611
- const id = node.dataset["elementId"];
2612
- if (id) this.startEditingElement(id);
2613
- });
2614
- }
2615
- if (!this.noteEditor.isEditing || this.noteEditor.editingElementId !== element.id) {
2616
- if (node.textContent !== element.text) {
2617
- node.textContent = element.text || "";
2618
- }
2619
- Object.assign(node.style, {
2620
- fontSize: `${element.fontSize}px`,
2621
- color: element.color,
2622
- textAlign: element.textAlign
2623
- });
2624
- }
2625
- }
2626
- }
2627
2768
  unbindArrowsFrom(removedElement) {
2628
2769
  const boundArrows = findBoundArrows(removedElement.id, this.store);
2629
2770
  const bounds = getElementBounds(removedElement);
@@ -2658,35 +2799,6 @@ var Viewport = class {
2658
2799
  }
2659
2800
  }
2660
2801
  }
2661
- hideDomNode(id) {
2662
- const node = this.domNodes.get(id);
2663
- if (node) node.style.display = "none";
2664
- }
2665
- removeDomNode(id) {
2666
- this.htmlContent.delete(id);
2667
- const node = this.domNodes.get(id);
2668
- if (node) {
2669
- node.remove();
2670
- this.domNodes.delete(id);
2671
- }
2672
- this.requestRender();
2673
- }
2674
- clearDomNodes() {
2675
- this.domNodes.forEach((node) => node.remove());
2676
- this.domNodes.clear();
2677
- this.htmlContent.clear();
2678
- this.requestRender();
2679
- }
2680
- reattachHtmlContent() {
2681
- for (const el of this.store.getElementsByType("html")) {
2682
- if (el.domId) {
2683
- const dom = document.getElementById(el.domId);
2684
- if (dom) {
2685
- this.htmlContent.set(el.id, dom);
2686
- }
2687
- }
2688
- }
2689
- }
2690
2802
  createWrapper() {
2691
2803
  const el = document.createElement("div");
2692
2804
  Object.assign(el.style, {
@@ -2727,8 +2839,7 @@ var Viewport = class {
2727
2839
  syncCanvasSize() {
2728
2840
  const rect = this.container.getBoundingClientRect();
2729
2841
  const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
2730
- this.canvasEl.width = rect.width * dpr;
2731
- this.canvasEl.height = rect.height * dpr;
2842
+ this.renderLoop.setCanvasSize(rect.width * dpr, rect.height * dpr);
2732
2843
  this.requestRender();
2733
2844
  }
2734
2845
  observeResize() {
@@ -2778,6 +2889,7 @@ var PencilTool = class {
2778
2889
  color;
2779
2890
  width;
2780
2891
  smoothing;
2892
+ optionListeners = /* @__PURE__ */ new Set();
2781
2893
  constructor(options = {}) {
2782
2894
  this.color = options.color ?? "#000000";
2783
2895
  this.width = options.width ?? 2;
@@ -2789,10 +2901,18 @@ var PencilTool = class {
2789
2901
  onDeactivate(ctx) {
2790
2902
  ctx.setCursor?.("default");
2791
2903
  }
2904
+ getOptions() {
2905
+ return { color: this.color, width: this.width, smoothing: this.smoothing };
2906
+ }
2907
+ onOptionsChange(listener) {
2908
+ this.optionListeners.add(listener);
2909
+ return () => this.optionListeners.delete(listener);
2910
+ }
2792
2911
  setOptions(options) {
2793
2912
  if (options.color !== void 0) this.color = options.color;
2794
2913
  if (options.width !== void 0) this.width = options.width;
2795
2914
  if (options.smoothing !== void 0) this.smoothing = options.smoothing;
2915
+ this.notifyOptionsChange();
2796
2916
  }
2797
2917
  onPointerDown(state, ctx) {
2798
2918
  this.drawing = true;
@@ -2825,6 +2945,9 @@ var PencilTool = class {
2825
2945
  this.points = [];
2826
2946
  ctx.requestRender();
2827
2947
  }
2948
+ notifyOptionsChange() {
2949
+ for (const listener of this.optionListeners) listener();
2950
+ }
2828
2951
  renderOverlay(ctx) {
2829
2952
  if (!this.drawing || this.points.length < 2) return;
2830
2953
  ctx.save();
@@ -2861,6 +2984,9 @@ var EraserTool = class {
2861
2984
  this.radius = options.radius ?? DEFAULT_RADIUS;
2862
2985
  this.cursor = makeEraserCursor(this.radius);
2863
2986
  }
2987
+ getOptions() {
2988
+ return { radius: this.radius };
2989
+ }
2864
2990
  onActivate(ctx) {
2865
2991
  ctx.setCursor?.(this.cursor);
2866
2992
  }
@@ -3449,13 +3575,25 @@ var ArrowTool = class {
3449
3575
  fromBinding;
3450
3576
  fromTarget = null;
3451
3577
  toTarget = null;
3578
+ optionListeners = /* @__PURE__ */ new Set();
3452
3579
  constructor(options = {}) {
3453
3580
  this.color = options.color ?? "#000000";
3454
3581
  this.width = options.width ?? 2;
3455
3582
  }
3583
+ getOptions() {
3584
+ return { color: this.color, width: this.width };
3585
+ }
3586
+ onOptionsChange(listener) {
3587
+ this.optionListeners.add(listener);
3588
+ return () => this.optionListeners.delete(listener);
3589
+ }
3456
3590
  setOptions(options) {
3457
3591
  if (options.color !== void 0) this.color = options.color;
3458
3592
  if (options.width !== void 0) this.width = options.width;
3593
+ this.notifyOptionsChange();
3594
+ }
3595
+ notifyOptionsChange() {
3596
+ for (const listener of this.optionListeners) listener();
3459
3597
  }
3460
3598
  layerFilter(ctx) {
3461
3599
  const activeLayerId = ctx.activeLayerId;
@@ -3577,15 +3715,31 @@ var NoteTool = class {
3577
3715
  backgroundColor;
3578
3716
  textColor;
3579
3717
  size;
3718
+ optionListeners = /* @__PURE__ */ new Set();
3580
3719
  constructor(options = {}) {
3581
3720
  this.backgroundColor = options.backgroundColor ?? "#ffeb3b";
3582
3721
  this.textColor = options.textColor ?? "#000000";
3583
3722
  this.size = options.size ?? { w: 200, h: 100 };
3584
3723
  }
3724
+ getOptions() {
3725
+ return {
3726
+ backgroundColor: this.backgroundColor,
3727
+ textColor: this.textColor,
3728
+ size: { ...this.size }
3729
+ };
3730
+ }
3731
+ onOptionsChange(listener) {
3732
+ this.optionListeners.add(listener);
3733
+ return () => this.optionListeners.delete(listener);
3734
+ }
3585
3735
  setOptions(options) {
3586
3736
  if (options.backgroundColor !== void 0) this.backgroundColor = options.backgroundColor;
3587
3737
  if (options.textColor !== void 0) this.textColor = options.textColor;
3588
3738
  if (options.size !== void 0) this.size = options.size;
3739
+ this.notifyOptionsChange();
3740
+ }
3741
+ notifyOptionsChange() {
3742
+ for (const listener of this.optionListeners) listener();
3589
3743
  }
3590
3744
  onPointerDown(_state, _ctx) {
3591
3745
  }
@@ -3616,15 +3770,27 @@ var TextTool = class {
3616
3770
  fontSize;
3617
3771
  color;
3618
3772
  textAlign;
3773
+ optionListeners = /* @__PURE__ */ new Set();
3619
3774
  constructor(options = {}) {
3620
3775
  this.fontSize = options.fontSize ?? 16;
3621
3776
  this.color = options.color ?? "#1a1a1a";
3622
3777
  this.textAlign = options.textAlign ?? "left";
3623
3778
  }
3779
+ getOptions() {
3780
+ return { fontSize: this.fontSize, color: this.color, textAlign: this.textAlign };
3781
+ }
3782
+ onOptionsChange(listener) {
3783
+ this.optionListeners.add(listener);
3784
+ return () => this.optionListeners.delete(listener);
3785
+ }
3624
3786
  setOptions(options) {
3625
3787
  if (options.fontSize !== void 0) this.fontSize = options.fontSize;
3626
3788
  if (options.color !== void 0) this.color = options.color;
3627
3789
  if (options.textAlign !== void 0) this.textAlign = options.textAlign;
3790
+ this.notifyOptionsChange();
3791
+ }
3792
+ notifyOptionsChange() {
3793
+ for (const listener of this.optionListeners) listener();
3628
3794
  }
3629
3795
  onActivate(ctx) {
3630
3796
  ctx.setCursor?.("text");
@@ -3696,17 +3862,31 @@ var ShapeTool = class {
3696
3862
  strokeColor;
3697
3863
  strokeWidth;
3698
3864
  fillColor;
3865
+ optionListeners = /* @__PURE__ */ new Set();
3699
3866
  constructor(options = {}) {
3700
3867
  this.shape = options.shape ?? "rectangle";
3701
3868
  this.strokeColor = options.strokeColor ?? "#000000";
3702
3869
  this.strokeWidth = options.strokeWidth ?? 2;
3703
3870
  this.fillColor = options.fillColor ?? "none";
3704
3871
  }
3872
+ getOptions() {
3873
+ return {
3874
+ shape: this.shape,
3875
+ strokeColor: this.strokeColor,
3876
+ strokeWidth: this.strokeWidth,
3877
+ fillColor: this.fillColor
3878
+ };
3879
+ }
3880
+ onOptionsChange(listener) {
3881
+ this.optionListeners.add(listener);
3882
+ return () => this.optionListeners.delete(listener);
3883
+ }
3705
3884
  setOptions(options) {
3706
3885
  if (options.shape !== void 0) this.shape = options.shape;
3707
3886
  if (options.strokeColor !== void 0) this.strokeColor = options.strokeColor;
3708
3887
  if (options.strokeWidth !== void 0) this.strokeWidth = options.strokeWidth;
3709
3888
  if (options.fillColor !== void 0) this.fillColor = options.fillColor;
3889
+ this.notifyOptionsChange();
3710
3890
  }
3711
3891
  onActivate(_ctx) {
3712
3892
  if (typeof window !== "undefined") {
@@ -3793,6 +3973,9 @@ var ShapeTool = class {
3793
3973
  }
3794
3974
  return { position: { x, y }, size: { w, h } };
3795
3975
  }
3976
+ notifyOptionsChange() {
3977
+ for (const listener of this.optionListeners) listener();
3978
+ }
3796
3979
  snap(point, ctx) {
3797
3980
  return ctx.snapToGrid && ctx.gridSize ? snapPoint(point, ctx.gridSize) : point;
3798
3981
  }
@@ -3845,7 +4028,7 @@ var UpdateLayerCommand = class {
3845
4028
  };
3846
4029
 
3847
4030
  // src/index.ts
3848
- var VERSION = "0.8.5";
4031
+ var VERSION = "0.8.6";
3849
4032
  export {
3850
4033
  AddElementCommand,
3851
4034
  ArrowTool,