From 3422b1c41af726d3eb66874c4cc323fa65b5fb0c Mon Sep 17 00:00:00 2001 From: Amark19 Date: Tue, 25 Aug 2026 13:06:32 +0530 Subject: [PATCH] fix: restore arrow endpoint handles after reload/undo (not bbox controls) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An arrow's custom endpoint controls (+ hasBorders:false, perPixelTargetFind) are installed in buildArrowGroup but aren't serialized — so an arrow restored from IndexedDB, or rebuilt by an undo/redo loadFromJSON, fell back to default bbox handles in the wrong spots. Add ArrowEndpointHandler: on selection, re-attach the endpoint controls to a selected arrow (mirrors LineEndpointHandler), which covers every load path. Co-Authored-By: Claude Opus 4.8 --- .../EventHandlers/arrowEndpointHandler.js | 33 +++++++++++++++++++ src/Handlers/EventHandlers/index.js | 1 + src/components/Canvas/Canvas.jsx | 2 ++ 3 files changed, 36 insertions(+) create mode 100644 src/Handlers/EventHandlers/arrowEndpointHandler.js diff --git a/src/Handlers/EventHandlers/arrowEndpointHandler.js b/src/Handlers/EventHandlers/arrowEndpointHandler.js new file mode 100644 index 0000000..042f6b0 --- /dev/null +++ b/src/Handlers/EventHandlers/arrowEndpointHandler.js @@ -0,0 +1,33 @@ +import { useEffect } from "react"; +import { useCanvasContext } from "../../hooks"; +import { isArrow } from "../../utils/shapeLabel"; +import { attachEndpointControls } from "../../utils/arrowEndpoints"; + +// Give a selected arrow its endpoint drag-handles back. buildArrowGroup installs +// them (plus hasBorders:false / perPixelTargetFind), but controls aren't +// serialized — so an arrow restored from a saved scene, or rebuilt by an +// undo/redo loadFromJSON, would otherwise show default bbox handles in the wrong +// spots. Re-attaching on selection covers every load path (mirrors +// LineEndpointHandler). +function ArrowEndpointHandler() { + const { canvas } = useCanvasContext(); + + useEffect(() => { + if (!canvas) return undefined; + const onSelect = () => { + const t = canvas.getActiveObject(); + if (t && isArrow(t) && !t.group) { + attachEndpointControls(t); + t.set({ perPixelTargetFind: true, objectCaching: false }); + } + }; + canvas.on("selection:created", onSelect); + canvas.on("selection:updated", onSelect); + return () => { + canvas.off("selection:created", onSelect); + canvas.off("selection:updated", onSelect); + }; + }, [canvas]); +} + +export default ArrowEndpointHandler; diff --git a/src/Handlers/EventHandlers/index.js b/src/Handlers/EventHandlers/index.js index 573db58..09292b0 100644 --- a/src/Handlers/EventHandlers/index.js +++ b/src/Handlers/EventHandlers/index.js @@ -4,6 +4,7 @@ export { default as SelectionHandler } from "./selectionHandler"; export { default as TextEventHandler } from "./textEventHandler"; export { default as LabelHandler } from "./labelHandler"; export { default as LineEndpointHandler } from "./lineEndpointHandler"; +export { default as ArrowEndpointHandler } from "./arrowEndpointHandler"; export { default as DarkColorHandler } from "./darkColorHandler"; export { default as ZoomHandler } from "./zoomHandler"; export { default as PersistenceHandler } from "./persistenceHandler"; diff --git a/src/components/Canvas/Canvas.jsx b/src/components/Canvas/Canvas.jsx index 000abf8..386cdec 100644 --- a/src/components/Canvas/Canvas.jsx +++ b/src/components/Canvas/Canvas.jsx @@ -9,6 +9,7 @@ import { SelectionHandler, LabelHandler, LineEndpointHandler, + ArrowEndpointHandler, TextEventHandler, ZoomHandler, PersistenceHandler, @@ -29,6 +30,7 @@ function Canvas() { // shape+text regroup happens before the generic empty-text cleanup. LabelHandler(); LineEndpointHandler(); + ArrowEndpointHandler(); TextEventHandler(); ZoomHandler(); PersistenceHandler();