Fixed click handling bug with editor node canvas

This commit is contained in:
Richard Osborne
2025-12-08 21:33:14 +01:00
parent 0485a1f837
commit 0a95c3906b
9 changed files with 352 additions and 19 deletions

View File

@@ -1,5 +1,5 @@
import React, { useState, useRef, useLayoutEffect, useEffect } from 'react';
import ReactDOM from 'react-dom';
import { createRoot } from 'react-dom/client';
import { StylesModel } from '@noodl-models/StylesModel';
@@ -41,21 +41,22 @@ function TextStylePicker(props) {
if (!styleToEdit || !popupAnchor) return;
const div = document.createElement('div');
ReactDOM.render(<TextStylePopup style={styleToEdit} stylesModel={stylesModel} />, div);
const root = createRoot(div);
root.render(<TextStylePopup style={styleToEdit} stylesModel={stylesModel} />);
const popout = PopupLayer.instance.showPopout({
content: { el: $(div) },
attachTo: $(popupAnchor),
position: 'right',
onClose: () => {
ReactDOM.unmountComponentAtNode(div);
root.unmount();
}
});
return () => {
PopupLayer.instance.hidePopout(popout);
};
}, [styleToEdit, popupAnchor]);
}, [styleToEdit, popupAnchor, stylesModel]);
let filteredStyles = textStyles;

View File

@@ -142,16 +142,29 @@ export default class CommentLayer {
return;
}
this.backgroundRoot = createRoot(this.backgroundDiv);
// Create roots only once, reuse for subsequent renders
if (!this.backgroundRoot) {
this.backgroundRoot = createRoot(this.backgroundDiv);
}
this.backgroundRoot.render(React.createElement(CommentLayerView.Background, this.props));
this.foregroundRoot = createRoot(this.foregroundDiv);
if (!this.foregroundRoot) {
this.foregroundRoot = createRoot(this.foregroundDiv);
}
this.foregroundRoot.render(React.createElement(CommentLayerView.Foreground, this.props));
}
renderTo(backgroundDiv, foregroundDiv) {
// Clean up existing roots if we're switching to new divs
if (this.backgroundDiv) {
this.backgroundRoot.unmount();
this.foregroundRoot.unmount();
if (this.backgroundRoot) {
this.backgroundRoot.unmount();
this.backgroundRoot = null;
}
if (this.foregroundRoot) {
this.foregroundRoot.unmount();
this.foregroundRoot = null;
}
}
this.backgroundDiv = backgroundDiv;
@@ -301,12 +314,20 @@ export default class CommentLayer {
}
dispose() {
this.foregroundRoot.unmount();
this.backgroundRoot.unmount();
if (this.foregroundRoot) {
this.foregroundRoot.unmount();
this.foregroundRoot = null;
}
if (this.backgroundRoot) {
this.backgroundRoot.unmount();
this.backgroundRoot = null;
}
//hack to remove all event listeners without having to keep track of them
const newForegroundDiv = this.foregroundDiv.cloneNode(true);
this.foregroundDiv.parentNode.replaceChild(newForegroundDiv, this.foregroundDiv);
if (this.foregroundDiv && this.foregroundDiv.parentNode) {
const newForegroundDiv = this.foregroundDiv.cloneNode(true);
this.foregroundDiv.parentNode.replaceChild(newForegroundDiv, this.foregroundDiv);
}
if (this.model) {
this.model.off(this);

View File

@@ -84,6 +84,12 @@ export class CreateNewNodePanel extends View {
render() {
const div = document.createElement('div');
// Set explicit dimensions so PopupLayer can measure correctly
// before React 18's async rendering completes
// These dimensions match NodePicker.module.scss: width: 800px, height: 600px
div.style.width = '800px';
div.style.height = '600px';
this.renderReact(div);

View File

@@ -1,7 +1,7 @@
const DebugInspector = require('../utils/debuginspector');
const { ProjectModel } = require('../models/projectmodel');
const { InspectPopup } = require('./nodegrapheditor/InspectJSONView/InspectPopup');
const ReactDOM = require('react-dom');
const { createRoot } = require('react-dom/client');
const React = require('react');
const { EventDispatcher } = require('../../../shared/utils/EventDispatcher');
@@ -57,11 +57,18 @@ DebugInspectorPopup.prototype.render = function () {
this.togglePinned();
};
ReactDOM.render(React.createElement(InspectPopup, { debugValue, onPinClicked, pinned: this.model.pinned }), this.div);
// Create root only once, reuse for subsequent renders
if (!this.root) {
this.root = createRoot(this.div);
}
this.root.render(React.createElement(InspectPopup, { debugValue, onPinClicked, pinned: this.model.pinned }));
};
DebugInspectorPopup.prototype.dispose = function () {
ReactDOM.unmountComponentAtNode(this.div);
if (this.root) {
this.root.unmount();
this.root = null;
}
if (this.div.parentElement) {
this.div.parentElement.removeChild(this.div);
}

View File

@@ -227,7 +227,8 @@ export class NodeGraphEditor extends View {
nodesIdsAnimating: string[];
isPlayingNodeAnimations: boolean;
toolbarRoots: Root[];
toolbarRoots: Root[] = [];
titleRoot: Root = null;
constructor(args) {
super();
@@ -1421,7 +1422,12 @@ export class NodeGraphEditor extends View {
updateTitle() {
const rootElem = this.el[0].querySelector('.nodegraph-component-trail-root');
const root = createRoot(rootElem);
// Create root only once, reuse for subsequent renders
if (!this.titleRoot) {
this.titleRoot = createRoot(rootElem);
}
if (this.activeComponent) {
const fullName = this.activeComponent.fullName;
const nameParts = fullName.split('/');
@@ -1466,9 +1472,9 @@ export class NodeGraphEditor extends View {
canNavigateForward: this.navigationHistory.canNavigateForward
};
root.render(React.createElement(NodeGraphComponentTrail, props));
this.titleRoot.render(React.createElement(NodeGraphComponentTrail, props));
} else {
root.unmount();
this.titleRoot.render(null);
}
}