From 4f9b036cc0058db671d2ce3b20061fbdd1a58fd6 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Mon, 2 Mar 2026 23:07:41 -0800 Subject: [PATCH] fix: preserve shape x/y/size on reload + fix eraser Automerge persistence createRenderRoot() was unconditionally reading x/y/width/height from HTML attributes, overwriting values already set via JS properties before DOM insertion. This caused all shapes to stack at (0,0) with auto dimensions on page reload. Now only reads from attributes when they exist. Also fixed eraser: hardDeleteShape() was only in the click handler which never fired because pointerdown already removed the target element. Moved Automerge deletion into the pointerdown handler. Co-Authored-By: Claude Opus 4.6 --- lib/folk-shape.ts | 18 +++++++++++++----- website/canvas.html | 9 +++++---- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/lib/folk-shape.ts b/lib/folk-shape.ts index 493aaf6..81cf085 100644 --- a/lib/folk-shape.ts +++ b/lib/folk-shape.ts @@ -383,11 +383,19 @@ export class FolkShape extends FolkElement { this.#updateCursors(); - this.x = Number(this.getAttribute("x")) || 0; - this.y = Number(this.getAttribute("y")) || 0; - this.width = Number(this.getAttribute("width")) || "auto"; - this.height = Number(this.getAttribute("height")) || "auto"; - this.rotation = (Number(this.getAttribute("rotation")) || 0) * (Math.PI / 180); + // Only initialize from HTML attributes if they exist — when properties + // are set via JS before DOM insertion, attributes are absent and reading + // them would overwrite the already-set values with 0/"auto". + const attrX = this.getAttribute("x"); + const attrY = this.getAttribute("y"); + const attrW = this.getAttribute("width"); + const attrH = this.getAttribute("height"); + const attrR = this.getAttribute("rotation"); + if (attrX !== null) this.x = Number(attrX) || 0; + if (attrY !== null) this.y = Number(attrY) || 0; + if (attrW !== null) this.width = Number(attrW) || "auto"; + if (attrH !== null) this.height = Number(attrH) || "auto"; + if (attrR !== null) this.rotation = (Number(attrR) || 0) * (Math.PI / 180); this.#rect.transformOrigin = { x: 0, y: 0 }; this.#rect.rotateOrigin = { x: 0.5, y: 0.5 }; diff --git a/website/canvas.html b/website/canvas.html index a92eed1..919ba56 100644 --- a/website/canvas.html +++ b/website/canvas.html @@ -3655,6 +3655,8 @@ // Find and remove the nearest SVG element under cursor const hit = document.elementFromPoint(e.clientX, e.clientY); if (hit && hit !== wbOverlay && wbOverlay.contains(hit)) { + const wbId = hit.getAttribute("data-wb-id"); + if (wbId) sync.hardDeleteShape(wbId); hit.remove(); } } @@ -3766,15 +3768,14 @@ } }); - // Eraser: click on existing SVG strokes to delete them + remove from Automerge + // Eraser click fallback — deletion is handled in pointerdown above, + // but catch any clicks that slip through (e.g. keyboard-triggered) wbOverlay.addEventListener("click", (e) => { if (wbTool !== "eraser") return; const hit = e.target; if (hit && hit !== wbOverlay && wbOverlay.contains(hit)) { const wbId = hit.getAttribute("data-wb-id"); - if (wbId) { - sync.hardDeleteShape(wbId); - } + if (wbId) sync.hardDeleteShape(wbId); hit.remove(); } });