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 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-03-02 23:07:41 -08:00
parent 4819852b14
commit 4f9b036cc0
2 changed files with 18 additions and 9 deletions

View File

@ -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 };

View File

@ -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();
}
});