Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions src/js/components/canvas.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export default class Canvas extends Component{

this.canvasRef = React.createRef();
this.boundHandleKeyPress = this.handleKeyPress.bind(this)
this.boundOnCursorChange = this.onCursorChange.bind(this)
this.state = { contextMenu: null };
}

Expand All @@ -15,6 +16,9 @@ export default class Canvas extends Component{
// set the paint callback
this.props.core.canvas.setExternalPaintCallbackFunction(this.paint.bind(this))

// set the cursor callback
this.props.core.canvas.setCursorCallbackFunction(this.boundOnCursorChange)

Comment on lines +19 to +21

Copilot AI Apr 5, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this.onCursorChange.bind(this) creates a new function each time componentDidMount/componentDidUpdate runs. Consider binding once (e.g., this.boundOnCursorChange = this.onCursorChange.bind(this) in the constructor) and reusing that reference when registering/unregistering the callback; this avoids repeated allocations and makes cleanup reliable if the core API expects the same function reference.

Copilot uses AI. Check for mistakes.
// add keydown eventlistener
document.addEventListener("keydown", this.boundHandleKeyPress)

Expand All @@ -28,14 +32,28 @@ export default class Canvas extends Component{

componentDidUpdate(prevProps) {
if (prevProps.core !== this.props.core) {

Copilot AI Apr 5, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When core changes, the previous prevProps.core.canvas still retains the old cursor callback. That can keep this component alive and can also trigger onCursorChange after the canvas is no longer the active core (or after unmount), leading to errors. Clear the cursor callback on prevProps.core.canvas before registering the new one (mirroring cleanup done in componentWillUnmount).

Suggested change
if (prevProps.core !== this.props.core) {
if (prevProps.core !== this.props.core) {
prevProps.core.canvas.setCursorCallbackFunction(undefined);

Copilot uses AI. Check for mistakes.
prevProps.core.canvas.setCursorCallbackFunction(undefined);
this.props.core.canvas.setExternalPaintCallbackFunction(this.paint.bind(this));
this.props.core.canvas.setCursorCallbackFunction(this.boundOnCursorChange);
this.paint();
}
}

componentWillUnmount() {
document.removeEventListener("keydown", this.boundHandleKeyPress)
this.resizeObserver.disconnect();
this.props.core.canvas.setCursorCallbackFunction(undefined);
}

onCursorChange(state) {
if (!this.canvasRef.current) return;
const cursors = {
DEFAULT: 'crosshair',
GRAB: 'grab',
GRABBING: 'grabbing',
SELECTION: 'cell',
};
this.canvasRef.current.style.cursor = cursors[state] ?? 'crosshair';

Copilot AI Apr 5, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

onCursorChange assumes this.canvasRef.current is always available. If a cursor update arrives after unmount (or while swapping cores), this will throw. Add a quick guard (e.g., return early when the ref is missing) to make the callback resilient.

Suggested change
this.canvasRef.current.style.cursor = cursors[state] ?? 'crosshair';
const canvas = this.canvasRef.current;
if (!canvas) {
return;
}
canvas.style.cursor = cursors[state] ?? 'crosshair';

Copilot uses AI. Check for mistakes.
}

paint() {
Expand Down
Loading