From 3680069b08195a495f9d04db9417a40a6e89cbf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lder=20Gon=C3=A7alves?= Date: Fri, 28 Nov 2025 16:12:39 +0000 Subject: [PATCH 01/28] Setup Sample App --- Sample.Avalonia/MainView/MainView.tsx | 28 ++++++++++++++++++--- Sample.Avalonia/MainView/MainViewAdapter.cs | 24 ++++++++++++++++++ Sample.Avalonia/MainWindow.xaml | 1 + Sample.Avalonia/MainWindow.xaml.cs | 1 + Sample.Avalonia/TabView.cs | 9 +++++++ Sample.Avalonia/UsersView/UsersView.scss | 6 +++++ Sample.Avalonia/UsersView/UsersView.tsx | 25 ++++++++++++++++++ 7 files changed, 90 insertions(+), 4 deletions(-) create mode 100644 Sample.Avalonia/MainView/MainViewAdapter.cs create mode 100644 Sample.Avalonia/UsersView/UsersView.scss create mode 100644 Sample.Avalonia/UsersView/UsersView.tsx diff --git a/Sample.Avalonia/MainView/MainView.tsx b/Sample.Avalonia/MainView/MainView.tsx index efb6092a..b4a2d7b3 100644 --- a/Sample.Avalonia/MainView/MainView.tsx +++ b/Sample.Avalonia/MainView/MainView.tsx @@ -4,7 +4,7 @@ import ViewPlugin from "./../ViewPlugin/ViewPlugin"; import { IPluginsContext } from "PluginsProvider"; import "./MainView.scss"; // import a stylesheet import TaskListView from "./../TaskListView/TaskListView"; // import another component -import * as BackgroundImage from "./Tasks.png"; // import images +import * as BackgroundImage from "./Tasks.png"; export interface ITaskCreationDetails { text: string; @@ -18,8 +18,10 @@ export enum BackgroundKind { // component properties ... the interface name must start with I prefix and end with Properties suffix export interface IMainViewProperties { + getInnerViewName(): string; getTasksCount(): Promise; taskListShown(): void; + innerViewEditorShown(): void; inputChanged(): void; addTaskButtonClicked(taskDetails: ITaskCreationDetails): void; readonly titleMessage: string; @@ -29,10 +31,11 @@ export interface IMainViewProperties { // component methods that can be called on .NET ... the interface name must start with I prefix and end with Behaviors suffix export interface IMainViewBehaviors { refresh(): void; + refreshInnerView(): void; } export interface IChildViews { - ListView: TaskListView; + ListView: TaskListView } enum TaskListShowStatus { @@ -44,6 +47,7 @@ enum TaskListShowStatus { interface MainViewState { tasksCount: number; taskListShowStatus: TaskListShowStatus; + editorViewName: string; } export default class MainView extends React.Component implements IMainViewBehaviors { @@ -59,12 +63,18 @@ export default class MainView extends React.Component { this.state = { + editorViewName: null, tasksCount: 0, - taskListShowStatus: TaskListShowStatus.Show + taskListShowStatus: TaskListShowStatus.Show, }; this.refresh(); } + public async refreshInnerView(): Promise { + const editorViewName = await this.props.getInnerViewName(); + this.setState({ editorViewName }, () => this.props.innerViewEditorShown()); + } + public refresh(): void { (async () => { const tasksCount = await this.props.getTasksCount(); @@ -79,7 +89,7 @@ export default class MainView extends React.Component; + } + public render(): JSX.Element { return (
@@ -127,6 +145,8 @@ export default class MainView extends React.ComponentShow/Block/Hide Tasks {this.renderListView()}
{this.state.tasksCount} task(s)
+ + {this.renderEditor()}
); } diff --git a/Sample.Avalonia/MainView/MainViewAdapter.cs b/Sample.Avalonia/MainView/MainViewAdapter.cs new file mode 100644 index 00000000..79630363 --- /dev/null +++ b/Sample.Avalonia/MainView/MainViewAdapter.cs @@ -0,0 +1,24 @@ +using ReactViewControl; + +namespace Sample.Avalonia; + +partial class MainView { + private uint counter; + private string currentEditorViewName; + + public IViewModule ToggleEditorView() { + if (counter == 0) { + GetInnerViewName += () => currentEditorViewName; + } + + var childViewName = "canvas-" + counter++; + currentEditorViewName = childViewName; + + IViewModule view = counter % 2 == 0 + ? MainModule.GetOrAddChildView(childViewName) + : MainModule.GetOrAddChildView(childViewName); + + RefreshInnerView(); + return view; + } +} \ No newline at end of file diff --git a/Sample.Avalonia/MainWindow.xaml b/Sample.Avalonia/MainWindow.xaml index 0968b1b7..55276dd4 100644 --- a/Sample.Avalonia/MainWindow.xaml +++ b/Sample.Avalonia/MainWindow.xaml @@ -30,6 +30,7 @@ + diff --git a/Sample.Avalonia/MainWindow.xaml.cs b/Sample.Avalonia/MainWindow.xaml.cs index c10f7701..2de49883 100644 --- a/Sample.Avalonia/MainWindow.xaml.cs +++ b/Sample.Avalonia/MainWindow.xaml.cs @@ -72,6 +72,7 @@ public void CreateTab() { private void OnToggleThemeStyleSheetMenuItemClick(object sender, RoutedEventArgs e) => Settings.IsLightTheme = !Settings.IsLightTheme; private void OnShowDevToolsMenuItemClick(object sender, RoutedEventArgs e) => SelectedView.ShowDevTools(); + private void OnToggleEditorView(object sender, RoutedEventArgs e) => SelectedView.ToggleCustomInnerView(); private void OnToggleIsEnabledMenuItemClick(object sender, RoutedEventArgs e) => SelectedView.ToggleIsEnabled(); diff --git a/Sample.Avalonia/TabView.cs b/Sample.Avalonia/TabView.cs index 198200a8..1e18ad5e 100644 --- a/Sample.Avalonia/TabView.cs +++ b/Sample.Avalonia/TabView.cs @@ -13,6 +13,7 @@ internal class TabView : ContentControl { private MainView mainView; private TaskListViewModule taskListView; + private IViewModule innerView; private int taskCounter; private readonly List taskList = new() { @@ -30,6 +31,9 @@ public TabView(int id) { mainView.AddTaskButtonClicked += OnMainViewAddTaskButtonClicked; mainView.GetTasksCount += () => taskList.Count; mainView.TaskListShown += () => taskListView.Load(); + mainView.InnerViewEditorShown += () => { + innerView.Load(); + }; mainView.WithPlugin().NotifyViewLoaded += viewName => AppendLog(viewName + " loaded"); taskListView = (TaskListViewModule)mainView.ListView; @@ -41,6 +45,7 @@ public TabView(int id) { taskListView.WithPlugin().NotifyViewLoaded += (viewName) => AppendLog(viewName + " loaded (child)"); taskListView.Load(); + innerView = mainView.ToggleEditorView(); Content = mainView; } @@ -62,6 +67,10 @@ private void OnMainViewAddTaskButtonClicked(TaskCreationDetails taskDetails) { public void ToggleIsEnabled() => mainView.IsEnabled = !mainView.IsEnabled; public ReactViewControl.EditCommands EditCommands => mainView.EditCommands; + + public void ToggleCustomInnerView() { + innerView = mainView.ToggleEditorView(); + } private void AppendLog(string log) { Dispatcher.UIThread.Post(() => { diff --git a/Sample.Avalonia/UsersView/UsersView.scss b/Sample.Avalonia/UsersView/UsersView.scss new file mode 100644 index 00000000..ed7f27ee --- /dev/null +++ b/Sample.Avalonia/UsersView/UsersView.scss @@ -0,0 +1,6 @@ +body { + background: none; + background: red; + width: 100px; + height: 100px; +} diff --git a/Sample.Avalonia/UsersView/UsersView.tsx b/Sample.Avalonia/UsersView/UsersView.tsx new file mode 100644 index 00000000..53f3239f --- /dev/null +++ b/Sample.Avalonia/UsersView/UsersView.tsx @@ -0,0 +1,25 @@ +import * as React from "react"; +import { IPluginsContext } from "PluginsProvider"; +import "./UsersView.scss"; + +export interface IUsersViewProperties { +} + +export interface IUsersViewBehaviors { + +} + +export default class UsersView extends React.Component implements IUsersViewBehaviors { + + constructor(props: IUsersViewProperties, context: IPluginsContext) { + super(props, context); + } + + public render(): JSX.Element { + return ( +
+ Hello World!! This is the users inner view! +
+ ); + } +} From 94cea36669b68f1eedf4006d1c0d08014a4b7c18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lder=20Gon=C3=A7alves?= Date: Fri, 28 Nov 2025 16:13:56 +0000 Subject: [PATCH 02/28] Update TaskListView.tsx --- Sample.Avalonia/TaskListView/TaskListView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sample.Avalonia/TaskListView/TaskListView.tsx b/Sample.Avalonia/TaskListView/TaskListView.tsx index 341f602c..3f9b3cb8 100644 --- a/Sample.Avalonia/TaskListView/TaskListView.tsx +++ b/Sample.Avalonia/TaskListView/TaskListView.tsx @@ -71,7 +71,7 @@ export default class TaskListView extends React.Component { - const tasks = await this.props.getTasks(); + const tasks = (await this.props.getTasks()) || []; this.setState({ tasks }); })(); } From 36dccd13a5fd3fa766cd433792c7c2a0805ca54e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lder=20Gon=C3=A7alves?= Date: Fri, 28 Nov 2025 16:17:08 +0000 Subject: [PATCH 03/28] Update TaskListView.tsx --- Sample.Avalonia/TaskListView/TaskListView.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/Sample.Avalonia/TaskListView/TaskListView.tsx b/Sample.Avalonia/TaskListView/TaskListView.tsx index 3f9b3cb8..5b914863 100644 --- a/Sample.Avalonia/TaskListView/TaskListView.tsx +++ b/Sample.Avalonia/TaskListView/TaskListView.tsx @@ -99,6 +99,7 @@ export default class TaskListView extends React.Component +

Tasks View is Rendered!

{this.renderItems()} ); From b74b07773c9d5e2c0e347f744aa51d687c206057 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lder=20Gon=C3=A7alves?= Date: Fri, 28 Nov 2025 16:18:31 +0000 Subject: [PATCH 04/28] Update MainView.tsx --- Sample.Avalonia/MainView/MainView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sample.Avalonia/MainView/MainView.tsx b/Sample.Avalonia/MainView/MainView.tsx index b4a2d7b3..3f7cf51a 100644 --- a/Sample.Avalonia/MainView/MainView.tsx +++ b/Sample.Avalonia/MainView/MainView.tsx @@ -132,7 +132,7 @@ export default class MainView extends React.Component; } From 7ca088c5c60f9bd3f5c2fb4db3747d13d576df0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lder=20Gon=C3=A7alves?= Date: Tue, 2 Dec 2025 18:50:38 +0000 Subject: [PATCH 05/28] First version --- .../Loader/Internal/Loader.View.tsx | 24 +------- .../Loader/Internal/ViewPortal.tsx | 42 ++++++++++---- .../Loader/Internal/ViewPortalsCollection.tsx | 42 -------------- .../Loader/Public/ViewFrame.tsx | 55 ++++++++++++++----- 4 files changed, 72 insertions(+), 91 deletions(-) delete mode 100644 ReactViewResources/Loader/Internal/ViewPortalsCollection.tsx diff --git a/ReactViewResources/Loader/Internal/Loader.View.tsx b/ReactViewResources/Loader/Internal/Loader.View.tsx index 0c7f425e..13187fca 100644 --- a/ReactViewResources/Loader/Internal/Loader.View.tsx +++ b/ReactViewResources/Loader/Internal/Loader.View.tsx @@ -3,14 +3,12 @@ import * as ReactDOM from "react-dom"; import { ViewMetadataContext } from "../Internal/ViewMetadataContext"; import { PluginsContext, PluginsContextHolder } from "../Public/PluginsContext"; import { formatUrl, ResourceLoader } from "../Public/ResourceLoader"; -import { handleError } from "./ErrorHandler"; -import { notifyViewDestroyed, notifyViewInitialized } from "./NativeAPI"; import { ViewMetadata } from "./ViewMetadata"; -import { ViewPortalsCollection } from "./ViewPortalsCollection"; -import { addView, deleteView } from "./ViewsCollection"; export function createView(componentClass: any, properties: {}, view: ViewMetadata, componentName: string) { componentClass.contextType = PluginsContext; + + console.log("Loader.View: createView", view.name, componentName); const makeResourceUrl = (resourceKey: string, ...params: string[]) => formatUrl(view.name, resourceKey, ...params); @@ -18,10 +16,6 @@ export function createView(componentClass: any, properties: {}, view: ViewMetada - {React.createElement(componentClass, { ref: e => view.modules.set(componentName, e), ...properties })} @@ -32,17 +26,3 @@ export function createView(componentClass: any, properties: {}, view: ViewMetada export function renderMainView(children: React.ReactElement, container: Element) { return new Promise(resolve => ReactDOM.hydrate(children, container, resolve)); } - -function onChildViewAdded(childView: ViewMetadata) { - addView(childView.name, childView); - notifyViewInitialized(childView.name); -} - -function onChildViewRemoved(childView: ViewMetadata) { - deleteView(childView.name); - notifyViewDestroyed(childView.name); -} - -function onChildViewErrorRaised(childView: ViewMetadata, error: Error) { - handleError(error, childView); -} diff --git a/ReactViewResources/Loader/Internal/ViewPortal.tsx b/ReactViewResources/Loader/Internal/ViewPortal.tsx index 239e7728..49df0d47 100644 --- a/ReactViewResources/Loader/Internal/ViewPortal.tsx +++ b/ReactViewResources/Loader/Internal/ViewPortal.tsx @@ -3,21 +3,37 @@ import { webViewRootId } from "../Internal/Environment"; import { getStylesheets } from "./Common"; import { ViewMetadata } from "./ViewMetadata"; import { ViewSharedContext } from "../Public/ViewSharedContext"; +import {addView, deleteView} from "./ViewsCollection"; +import {notifyViewDestroyed, notifyViewInitialized} from "./NativeAPI"; +import {handleError} from "./ErrorHandler"; export type ViewLifecycleEventHandler = (view: ViewMetadata) => void; export type ViewErrorHandler = (view: ViewMetadata, error: Error) => void; export interface IViewPortalProps { view: ViewMetadata - viewMounted: ViewLifecycleEventHandler; - viewUnmounted: ViewLifecycleEventHandler; - viewErrorRaised: ViewErrorHandler; + shadowRoot: Element; } interface IViewPortalState { component: React.ReactElement; } + +function onChildViewAdded(childView: ViewMetadata) { + addView(childView.name, childView); + notifyViewInitialized(childView.name); +} + +function onChildViewRemoved(childView: ViewMetadata) { + deleteView(childView.name); + notifyViewDestroyed(childView.name); +} + +function onChildViewErrorRaised(childView: ViewMetadata, error: Error) { + handleError(error, childView); +} + /** * A ViewPortal is were a view is rendered. The view DOM is then moved into the appropriate placeholder. * This way we avoid a view being recreated (and losing state) when its ViewFrame is moved in the tree. @@ -27,21 +43,19 @@ interface IViewPortalState { * A view portal is persisted until its View Frame counterpart disappears. * */ export class ViewPortal extends React.Component { - - private head: Element; - private shadowRoot: HTMLElement; + private head: HTMLElement; constructor(props: IViewPortalProps, context: any) { super(props, context); this.state = { component: null! }; - this.shadowRoot = props.view.placeholder.attachShadow({ mode: "open" }).getRootNode() as HTMLElement; - props.view.renderHandler = component => this.renderPortal(component); } private renderPortal(component: React.ReactElement) { + console.log("ViewPortal :: renderPortal", this.props.view.name); + const wrappedComponent = ( {component} @@ -56,6 +70,7 @@ export class ViewPortal extends React.Component s.dataset.sticky === "true"); stylesheets.forEach(s => this.head.appendChild(document.importNode(s, true))); - this.props.viewMounted(this.props.view); + onChildViewAdded(this.props.view); } public componentWillUnmount() { - this.props.viewUnmounted(this.props.view); + console.log("ViewPortal :: componentWillUnmount", this.props.view.name); + onChildViewRemoved(this.props.view); } public componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { // execute error handling inside promise, to avoid the error handler to rethrow exception inside componentDidCatch - Promise.resolve(null).then(() => this.props.viewErrorRaised(this.props.view, error)); + Promise.resolve(null).then(() => onChildViewErrorRaised(this.props.view, error)); } public render(): React.ReactNode { + console.log("Render ViewPortal view:", this.props.view); + console.log("Render ViewPortal viewName:", this.props.view.name); return ReactDOM.createPortal( <> this.head = e!}> @@ -90,6 +108,6 @@ export class ViewPortal extends React.Component , - this.shadowRoot); + this.props.shadowRoot); } } \ No newline at end of file diff --git a/ReactViewResources/Loader/Internal/ViewPortalsCollection.tsx b/ReactViewResources/Loader/Internal/ViewPortalsCollection.tsx deleted file mode 100644 index 435a67ea..00000000 --- a/ReactViewResources/Loader/Internal/ViewPortalsCollection.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import * as React from "react"; -import { ObservableListCollection } from "./ObservableCollection"; -import { ViewMetadata } from "./ViewMetadata"; -import { ViewPortal, ViewLifecycleEventHandler, ViewErrorHandler } from "./ViewPortal"; -export { ViewLifecycleEventHandler, ViewErrorHandler } from "./ViewPortal"; - -interface IViewPortalsCollectionProps { - views: ObservableListCollection; - viewAdded: ViewLifecycleEventHandler; - viewRemoved: ViewLifecycleEventHandler; - viewErrorRaised: ViewErrorHandler; -} - -/** - * Handles notifications from the views collection. Whenever a view is added or removed - * the corresponding ViewPortal is added or removed - * */ -export class ViewPortalsCollection extends React.Component { - - constructor(props: IViewPortalsCollectionProps, context: any) { - super(props, context); - props.views.addChangedListener(() => this.forceUpdate()); - } - - public shouldComponentUpdate() { - return false; - } - - private renderViewPortal(view: ViewMetadata) { - return ( - - ); - } - - public render(): React.ReactNode { - return this.props.views.items.sort((a, b) => a.name.localeCompare(b.name)).map(view => this.renderViewPortal(view)); - } -} \ No newline at end of file diff --git a/ReactViewResources/Loader/Public/ViewFrame.tsx b/ReactViewResources/Loader/Public/ViewFrame.tsx index a2ed29be..f5d6f40e 100644 --- a/ReactViewResources/Loader/Public/ViewFrame.tsx +++ b/ReactViewResources/Loader/Public/ViewFrame.tsx @@ -3,27 +3,23 @@ import { IViewFrameProps } from "ViewFrame"; import { newView, ViewMetadata } from "../Internal/ViewMetadata"; import { ViewMetadataContext } from "../Internal/ViewMetadataContext"; import { ViewSharedContext } from "./ViewSharedContext"; +import {ViewPortal} from "../Internal/ViewPortal"; interface IInternalViewFrameProps extends IViewFrameProps { viewMetadata: ViewMetadata; - context: any; + context?: T; } /** * Placeholder were a child view is mounted. * */ -export class ViewFrame extends React.Component, {}, ViewMetadata> { - - constructor(props: IViewFrameProps, context: any) { - super(props, context); - } - +export class ViewFrame extends React.Component, {}> { public render(): JSX.Element { return ( {viewMetadata => - {viewcontext => } + {viewContext => } } @@ -32,12 +28,12 @@ export class ViewFrame extends React.Component, {}, ViewMe } class InternalViewFrame extends React.Component, {}, ViewMetadata> { - private static generation = 0; - private generation: number; + private readonly generation: number; private placeholder: Element; private replacement: Element; + private shadowRoot: Element | null = null; constructor(props: IInternalViewFrameProps, context: any) { super(props, context); @@ -53,10 +49,21 @@ class InternalViewFrame extends React.Component, { // keep track of this frame generation, so that we can keep tracking the most recent frame instance this.generation = ++InternalViewFrame.generation; - const view = this.getView(); - if (view) { - // update the existing view generation - view.generation = this.generation; + // const view = this.getView(); + // if (view) { + // // update the existing view generation + // view.generation = this.generation; + // } + } + + private setPlaceholder = (element: HTMLDivElement) => { + console.log("ViewFrame :: Setting placeholder REF"); + this.placeholder = element; + if (this.placeholder && !this.shadowRoot) { + console.log("ViewFrame :: Attach Shadow Root"); + // create an open shadow-dom, so that bubbled events expose the inner element + this.shadowRoot = this.placeholder.attachShadow({ mode: "open" }).getRootNode() as Element; + this.forceUpdate(); } } @@ -76,19 +83,30 @@ class InternalViewFrame extends React.Component, { } private getView(): ViewMetadata | undefined { + console.log("ViewFrame : getView ", this.fullName); + console.log("ViewFrame : getView ", this.parentView.childViews); const fullName = this.fullName; return this.parentView.childViews.items.find(c => c.name === fullName); } public componentDidMount() { + console.log("ViewFrame :: componentDidMount: ", this.fullName); + debugger; const existingView = this.getView(); if (existingView) { // there's a view already rendered, insert in current frame's placeholder + console.log("ViewFrame :: Existing View: ", existingView); + console.log("ViewFrame :: Existing ViewName: ", existingView.name); + console.log("ViewFrame :: Existing ViewchildViews: ", existingView.childViews); + console.log("ViewFrame :: Placeholder: ", this.placeholder); + console.log("ViewFrame :: Placeholder Parent: ", this.placeholder.parentElement); this.replacement = existingView.placeholder; this.placeholder.parentElement!.replaceChild(this.replacement, this.placeholder); return; } + console.log("ViewFrame :: componentDidMount: CreateNewView", this.fullName); + const id = this.generation; // for this purpose we can use generation (we just need a unique number) const childView = newView(id, this.fullName, false, this.placeholder); childView.generation = this.generation; @@ -113,11 +131,18 @@ class InternalViewFrame extends React.Component, { if (existingView && this.generation === existingView.generation) { // this is the most recent frame - meaning it was not replaced by another one - so the view should be removed this.parentView.childViews.remove(existingView); + console.log("unmount internal view frame", this.fullName); } + + + this.shadowRoot = null; } public render() { - return
this.placeholder = e!} className={this.props.className} />; + console.log("Render ViewFrame: ", this.fullName); + return
+ {this.shadowRoot && } +
; } } From 2a10ed23dcf52ab3657a11900585a0398732826b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lder=20Gon=C3=A7alves?= Date: Wed, 3 Dec 2025 11:02:34 +0000 Subject: [PATCH 06/28] Remove console log --- .../Loader/Internal/ViewPortal.tsx | 6 --- .../Loader/Public/ViewFrame.tsx | 39 ++++--------------- 2 files changed, 8 insertions(+), 37 deletions(-) diff --git a/ReactViewResources/Loader/Internal/ViewPortal.tsx b/ReactViewResources/Loader/Internal/ViewPortal.tsx index 49df0d47..d8f2955b 100644 --- a/ReactViewResources/Loader/Internal/ViewPortal.tsx +++ b/ReactViewResources/Loader/Internal/ViewPortal.tsx @@ -54,8 +54,6 @@ export class ViewPortal extends React.Component {component} @@ -70,7 +68,6 @@ export class ViewPortal extends React.Component this.head = e!}> diff --git a/ReactViewResources/Loader/Public/ViewFrame.tsx b/ReactViewResources/Loader/Public/ViewFrame.tsx index f5d6f40e..12e5525e 100644 --- a/ReactViewResources/Loader/Public/ViewFrame.tsx +++ b/ReactViewResources/Loader/Public/ViewFrame.tsx @@ -13,18 +13,11 @@ interface IInternalViewFrameProps extends IViewFrameProps { /** * Placeholder were a child view is mounted. * */ -export class ViewFrame extends React.Component, {}> { - public render(): JSX.Element { - return ( - - {viewMetadata => - - {viewContext => } - - } - - ); - } +export function ViewFrame(props: IViewFrameProps): JSX.Element { + const viewMetadata = React.useContext(ViewMetadataContext); + const viewContext = React.useContext(ViewSharedContext); + + return ; } class InternalViewFrame extends React.Component, {}, ViewMetadata> { @@ -48,12 +41,6 @@ class InternalViewFrame extends React.Component, { // keep track of this frame generation, so that we can keep tracking the most recent frame instance this.generation = ++InternalViewFrame.generation; - - // const view = this.getView(); - // if (view) { - // // update the existing view generation - // view.generation = this.generation; - // } } private setPlaceholder = (element: HTMLDivElement) => { @@ -83,30 +70,22 @@ class InternalViewFrame extends React.Component, { } private getView(): ViewMetadata | undefined { - console.log("ViewFrame : getView ", this.fullName); - console.log("ViewFrame : getView ", this.parentView.childViews); const fullName = this.fullName; return this.parentView.childViews.items.find(c => c.name === fullName); } public componentDidMount() { - console.log("ViewFrame :: componentDidMount: ", this.fullName); - debugger; const existingView = this.getView(); if (existingView) { + // update the existing view generation + existingView.generation = this.generation; + // there's a view already rendered, insert in current frame's placeholder - console.log("ViewFrame :: Existing View: ", existingView); - console.log("ViewFrame :: Existing ViewName: ", existingView.name); - console.log("ViewFrame :: Existing ViewchildViews: ", existingView.childViews); - console.log("ViewFrame :: Placeholder: ", this.placeholder); - console.log("ViewFrame :: Placeholder Parent: ", this.placeholder.parentElement); this.replacement = existingView.placeholder; this.placeholder.parentElement!.replaceChild(this.replacement, this.placeholder); return; } - console.log("ViewFrame :: componentDidMount: CreateNewView", this.fullName); - const id = this.generation; // for this purpose we can use generation (we just need a unique number) const childView = newView(id, this.fullName, false, this.placeholder); childView.generation = this.generation; @@ -131,7 +110,6 @@ class InternalViewFrame extends React.Component, { if (existingView && this.generation === existingView.generation) { // this is the most recent frame - meaning it was not replaced by another one - so the view should be removed this.parentView.childViews.remove(existingView); - console.log("unmount internal view frame", this.fullName); } @@ -139,7 +117,6 @@ class InternalViewFrame extends React.Component, { } public render() { - console.log("Render ViewFrame: ", this.fullName); return
{this.shadowRoot && }
; From 9a4c4f7606f292bc1591fa03ae681268ef823c87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lder=20Gon=C3=A7alves?= Date: Wed, 3 Dec 2025 12:38:38 +0000 Subject: [PATCH 07/28] Send FT to View --- ReactViewControl/ReactView.cs | 2 +- ReactViewControl/ReactViewFactory.cs | 2 ++ ReactViewControl/ReactViewRender.LoaderModule.cs | 3 ++- ReactViewControl/ReactViewRender.cs | 10 ++++++---- ReactViewResources/Loader/Internal/Loader.View.tsx | 7 +++---- .../Loader/Internal/ViewMetadataContext.ts | 12 +++++++++++- ReactViewResources/Loader/Loader.ts | 9 ++++++++- ReactViewResources/Loader/Public/ViewFrame.tsx | 8 ++++---- .../Loader/Public/ViewSharedContext.tsx | 2 +- 9 files changed, 38 insertions(+), 17 deletions(-) diff --git a/ReactViewControl/ReactView.cs b/ReactViewControl/ReactView.cs index fa769c16..4c31c226 100644 --- a/ReactViewControl/ReactView.cs +++ b/ReactViewControl/ReactView.cs @@ -19,7 +19,7 @@ public abstract partial class ReactView : IDisposable { private static ReactViewRender CreateReactViewInstance(ReactViewFactory factory) { ReactViewRender InnerCreateView() { - var view = new ReactViewRender(factory.DefaultStyleSheet, () => factory.InitializePlugins(), factory.EnableViewPreload, factory.EnableDebugMode); + var view = new ReactViewRender(factory.DefaultStyleSheet, () => factory.InitializePlugins(), factory.EnableViewPreload, factory.EnableDebugMode, factory.EnsureInnerViewsAreDisposed); if (factory.ShowDeveloperTools) { view.ShowDeveloperTools(); } diff --git a/ReactViewControl/ReactViewFactory.cs b/ReactViewControl/ReactViewFactory.cs index 156f2057..43f7f7b6 100644 --- a/ReactViewControl/ReactViewFactory.cs +++ b/ReactViewControl/ReactViewFactory.cs @@ -30,5 +30,7 @@ public class ReactViewFactory { /// The view is cached and preloaded. First render occurs earlier. /// public virtual bool EnableViewPreload => true; + + public virtual bool EnsureInnerViewsAreDisposed => false; } } \ No newline at end of file diff --git a/ReactViewControl/ReactViewRender.LoaderModule.cs b/ReactViewControl/ReactViewRender.LoaderModule.cs index 1e58a2f2..cbcef81b 100644 --- a/ReactViewControl/ReactViewRender.LoaderModule.cs +++ b/ReactViewControl/ReactViewRender.LoaderModule.cs @@ -22,7 +22,7 @@ public LoaderModule(ReactViewRender viewRender) { /// /// Loads the specified react component into the specified frame /// - public void LoadComponent(IViewModule component, string frameName, bool hasStyleSheet, bool hasPlugins) { + public void LoadComponent(IViewModule component, string frameName, bool hasStyleSheet, bool hasPlugins, bool ensureDisposeInnerViews) { var mainSource = ViewRender.ToFullUrl(NormalizeUrl(component.MainJsSource)); var dependencySources = component.DependencyJsSources.Select(s => ViewRender.ToFullUrl(NormalizeUrl(s))).ToArray(); var cssSources = component.CssSources.Select(s => ViewRender.ToFullUrl(NormalizeUrl(s))).ToArray(); @@ -56,6 +56,7 @@ public void LoadComponent(IViewModule component, string frameName, bool hasStyle componentSerialization, JavascriptSerializer.Serialize(frameName), JavascriptSerializer.Serialize(componentHash), + JavascriptSerializer.Serialize(ensureDisposeInnerViews), }; ExecuteLoaderFunction("loadComponent", loadArgs); diff --git a/ReactViewControl/ReactViewRender.cs b/ReactViewControl/ReactViewRender.cs index 1151e0ca..38f5a8e8 100644 --- a/ReactViewControl/ReactViewRender.cs +++ b/ReactViewControl/ReactViewRender.cs @@ -35,8 +35,10 @@ internal partial class ReactViewRender : IChildViewHost, IDisposable { private bool enableDebugMode; private ResourceUrl defaultStyleSheet; private bool isInputDisabled; // used primarly to control the intention to disable input (before the browser is ready) + private readonly bool ensureDisposeInnerViews; - public ReactViewRender(ResourceUrl defaultStyleSheet, Func initializePlugins, bool preloadWebView, bool enableDebugMode) { + public ReactViewRender(ResourceUrl defaultStyleSheet, Func initializePlugins, bool preloadWebView, bool enableDebugMode, bool ensureInnerViewsAreDisposed) { + this.ensureDisposeInnerViews = ensureInnerViewsAreDisposed; UserCallingAssembly = GetUserCallingMethod().ReflectedType.Assembly; // must useSharedDomain for the local storage to be shared @@ -68,12 +70,12 @@ public ReactViewRender(ResourceUrl defaultStyleSheet, Func initia ExtraInitialize(); - var urlParams = new string[] { + var urlParams = new[] { new ResourceUrl(ResourcesAssembly).ToString(), enableDebugMode ? "true" : "false", ExecutionEngine.ModulesObjectName, NativeAPI.NativeObjectName, - ResourceUrl.CustomScheme + Uri.SchemeDelimiter + CustomResourceBaseUrl + ResourceUrl.CustomScheme + Uri.SchemeDelimiter + CustomResourceBaseUrl, }; WebView.LoadResource(new ResourceUrl(ResourcesAssembly, ReactViewResources.Resources.DefaultUrl + "?" + string.Join("&", urlParams))); @@ -270,7 +272,7 @@ private void TryLoadComponent(FrameInfo frame) { RegisterNativeObject(frame.Component, frame); - Loader.LoadComponent(frame.Component, frame.Name, DefaultStyleSheet != null, frame.Plugins.Length > 0); + Loader.LoadComponent(frame.Component, frame.Name, DefaultStyleSheet != null, frame.Plugins.Length > 0, ensureDisposeInnerViews); if (isInputDisabled && frame.IsMain) { Loader.DisableMouseInteractions(); } diff --git a/ReactViewResources/Loader/Internal/Loader.View.tsx b/ReactViewResources/Loader/Internal/Loader.View.tsx index 13187fca..d6a7a0d3 100644 --- a/ReactViewResources/Loader/Internal/Loader.View.tsx +++ b/ReactViewResources/Loader/Internal/Loader.View.tsx @@ -1,16 +1,15 @@ import * as React from "react"; import * as ReactDOM from "react-dom"; -import { ViewMetadataContext } from "../Internal/ViewMetadataContext"; +import { getEnsureDisposeInnerViewsFlag, ViewMetadataContext } from "../Internal/ViewMetadataContext"; import { PluginsContext, PluginsContextHolder } from "../Public/PluginsContext"; import { formatUrl, ResourceLoader } from "../Public/ResourceLoader"; import { ViewMetadata } from "./ViewMetadata"; export function createView(componentClass: any, properties: {}, view: ViewMetadata, componentName: string) { componentClass.contextType = PluginsContext; - - console.log("Loader.View: createView", view.name, componentName); - const makeResourceUrl = (resourceKey: string, ...params: string[]) => formatUrl(view.name, resourceKey, ...params); + + console.log("Creating view:", view.name, getEnsureDisposeInnerViewsFlag()); return ( diff --git a/ReactViewResources/Loader/Internal/ViewMetadataContext.ts b/ReactViewResources/Loader/Internal/ViewMetadataContext.ts index 8a7f21b9..3ba01d30 100644 --- a/ReactViewResources/Loader/Internal/ViewMetadataContext.ts +++ b/ReactViewResources/Loader/Internal/ViewMetadataContext.ts @@ -1,4 +1,14 @@ import * as React from "react"; import { ViewMetadata } from "./ViewMetadata"; -export const ViewMetadataContext = React.createContext(null!); \ No newline at end of file +const EnsureDisposeInnerViewsFlagKey = "ENSURE_DISPOSE_INNER_VIEWS"; + +export const ViewMetadataContext = React.createContext(null!); + +export function getEnsureDisposeInnerViewsFlag(): boolean { + return !!window[EnsureDisposeInnerViewsFlagKey]; +} + +export function setEnsureDisposeInnerViewsFlag(ensureDisposeInnerViews: boolean): void { + window[EnsureDisposeInnerViewsFlagKey] = ensureDisposeInnerViews; +} \ No newline at end of file diff --git a/ReactViewResources/Loader/Loader.ts b/ReactViewResources/Loader/Loader.ts index 0e4ff86d..ee9aa781 100644 --- a/ReactViewResources/Loader/Loader.ts +++ b/ReactViewResources/Loader/Loader.ts @@ -11,6 +11,7 @@ import { Task } from "./Internal/Task"; import { ViewMetadata } from "./Internal/ViewMetadata"; import { createPropertiesProxy } from "./Internal/ViewPropertiesProxy"; import { addView, getView, tryGetView } from "./Internal/ViewsCollection"; +import { setEnsureDisposeInnerViewsFlag } from "./Internal/ViewMetadataContext"; export { disableMouseInteractions, enableMouseInteractions } from "./Internal/InputManager"; export { showErrorMessage } from "./Internal/MessagesProvider"; @@ -126,7 +127,8 @@ export function loadComponent( hasPlugins: boolean, componentNativeObject: any, frameName: string, - componentHash: string): void { + componentHash: string, + ensureDisposeInnerViews: boolean): void { async function innerLoad() { let view: ViewMetadata; @@ -135,6 +137,11 @@ export function loadComponent( // wait for the stylesheet to load before first render await defaultStylesheetLoadTask.promise; } + + if (frameName === mainFrameName) { + console.log(`Set ensureDisposeInnerViewsFlag to ${ensureDisposeInnerViews} for main frame`); + setEnsureDisposeInnerViewsFlag(ensureDisposeInnerViews); + } view = tryGetView(frameName)!; if (!view) { diff --git a/ReactViewResources/Loader/Public/ViewFrame.tsx b/ReactViewResources/Loader/Public/ViewFrame.tsx index 12e5525e..f14e7b8b 100644 --- a/ReactViewResources/Loader/Public/ViewFrame.tsx +++ b/ReactViewResources/Loader/Public/ViewFrame.tsx @@ -1,8 +1,8 @@ import * as React from "react"; import { IViewFrameProps } from "ViewFrame"; import { newView, ViewMetadata } from "../Internal/ViewMetadata"; -import { ViewMetadataContext } from "../Internal/ViewMetadataContext"; -import { ViewSharedContext } from "./ViewSharedContext"; +import { getEnsureDisposeInnerViewsFlag, ViewMetadataContext } from "../Internal/ViewMetadataContext"; +import { ViewSharedContext} from "./ViewSharedContext"; import {ViewPortal} from "../Internal/ViewPortal"; interface IInternalViewFrameProps extends IViewFrameProps { @@ -17,6 +17,8 @@ export function ViewFrame(props: IViewFrameProps): JSX.Element { const viewMetadata = React.useContext(ViewMetadataContext); const viewContext = React.useContext(ViewSharedContext); + console.log("Creating ViewFrame:", props.name, getEnsureDisposeInnerViewsFlag()); + return ; } @@ -44,10 +46,8 @@ class InternalViewFrame extends React.Component, { } private setPlaceholder = (element: HTMLDivElement) => { - console.log("ViewFrame :: Setting placeholder REF"); this.placeholder = element; if (this.placeholder && !this.shadowRoot) { - console.log("ViewFrame :: Attach Shadow Root"); // create an open shadow-dom, so that bubbled events expose the inner element this.shadowRoot = this.placeholder.attachShadow({ mode: "open" }).getRootNode() as Element; this.forceUpdate(); diff --git a/ReactViewResources/Loader/Public/ViewSharedContext.tsx b/ReactViewResources/Loader/Public/ViewSharedContext.tsx index 8dd4d90f..bed82474 100644 --- a/ReactViewResources/Loader/Public/ViewSharedContext.tsx +++ b/ReactViewResources/Loader/Public/ViewSharedContext.tsx @@ -1,3 +1,3 @@ import * as React from "react"; -export const ViewSharedContext = React.createContext({}); \ No newline at end of file +export const ViewSharedContext = React.createContext({}); From 5dad99bd278aaad133012395064fff3f0a19af84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lder=20Gon=C3=A7alves?= Date: Wed, 3 Dec 2025 12:40:51 +0000 Subject: [PATCH 08/28] Update ReactViewFactory.cs --- ReactViewControl/ReactViewFactory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ReactViewControl/ReactViewFactory.cs b/ReactViewControl/ReactViewFactory.cs index 43f7f7b6..075aeb9c 100644 --- a/ReactViewControl/ReactViewFactory.cs +++ b/ReactViewControl/ReactViewFactory.cs @@ -33,4 +33,4 @@ public class ReactViewFactory { public virtual bool EnsureInnerViewsAreDisposed => false; } -} \ No newline at end of file +} From a4aebcfb9c53c845ed596bba71d3fb23eb22299a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lder=20Gon=C3=A7alves?= Date: Wed, 3 Dec 2025 15:01:08 +0000 Subject: [PATCH 09/28] Add legacy files again --- .../Loader/Internal/ViewPortalLegacy.tsx | 95 +++++++++++++++++++ .../Internal/ViewPortalsCollections.tsx | 42 ++++++++ .../Loader/Public/ViewFrame.tsx | 9 +- .../Loader/Public/ViewFrameLegacy.tsx | 92 ++++++++++++++++++ 4 files changed, 235 insertions(+), 3 deletions(-) create mode 100644 ReactViewResources/Loader/Internal/ViewPortalLegacy.tsx create mode 100644 ReactViewResources/Loader/Internal/ViewPortalsCollections.tsx create mode 100644 ReactViewResources/Loader/Public/ViewFrameLegacy.tsx diff --git a/ReactViewResources/Loader/Internal/ViewPortalLegacy.tsx b/ReactViewResources/Loader/Internal/ViewPortalLegacy.tsx new file mode 100644 index 00000000..9fb4c134 --- /dev/null +++ b/ReactViewResources/Loader/Internal/ViewPortalLegacy.tsx @@ -0,0 +1,95 @@ +import * as React from "react"; +import { webViewRootId } from "../Internal/Environment"; +import { getStylesheets } from "./Common"; +import { ViewMetadata } from "./ViewMetadata"; +import { ViewSharedContext } from "../Public/ViewSharedContext"; + +export type ViewLifecycleEventHandler = (view: ViewMetadata) => void; +export type ViewErrorHandler = (view: ViewMetadata, error: Error) => void; + +export interface IViewPortalProps { + view: ViewMetadata + viewMounted: ViewLifecycleEventHandler; + viewUnmounted: ViewLifecycleEventHandler; + viewErrorRaised: ViewErrorHandler; +} + +interface IViewPortalState { + component: React.ReactElement; +} + +/** + * A ViewPortal is were a view is rendered. The view DOM is then moved into the appropriate placeholder. + * This way we avoid a view being recreated (and losing state) when its ViewFrame is moved in the tree. + * + * A View Frame notifies its sibling view collection when a new instance is mounted. + * Upon mount, a View Portal is created and it will be responsible for rendering its view component in the shadow dom. + * A view portal is persisted until its View Frame counterpart disappears. + * */ +export class ViewPortalLegacy extends React.Component { + + private head: Element; + private shadowRoot: HTMLElement; + + constructor(props: IViewPortalProps, context: any) { + super(props, context); + + this.state = { component: null! }; + + this.shadowRoot = props.view.placeholder.attachShadow({ mode: "open" }).getRootNode() as HTMLElement; + + props.view.renderHandler = component => this.renderPortal(component); + } + + private renderPortal(component: React.ReactElement) { + const wrappedComponent = ( + + {component} + + ); + return new Promise(resolve => this.setState({ component: wrappedComponent }, resolve)); + } + + public shouldComponentUpdate(nextProps: IViewPortalProps, nextState: IViewPortalState) { + // only update if the component was set (once) + return this.state.component === null && nextState.component !== this.state.component; + } + + public componentDidMount() { + this.props.view.head = this.head; + + const styleResets = document.createElement("style"); + styleResets.innerHTML = ":host { all: initial; display: block; }"; + + this.head.appendChild(styleResets); + + // get sticky stylesheets + const stylesheets = getStylesheets(document.head).filter(s => s.dataset.sticky === "true"); + stylesheets.forEach(s => this.head.appendChild(document.importNode(s, true))); + + this.props.viewMounted(this.props.view); + } + + public componentWillUnmount() { + this.props.viewUnmounted(this.props.view); + } + + public componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { + // execute error handling inside promise, to avoid the error handler to rethrow exception inside componentDidCatch + Promise.resolve(null).then(() => this.props.viewErrorRaised(this.props.view, error)); + } + + public render(): React.ReactNode { + return ReactDOM.createPortal( + <> + this.head = e!}> + + +
this.props.view.root = e!}> + {this.state.component ? this.state.component : null} +
+ + , + this.shadowRoot); + } +} \ No newline at end of file diff --git a/ReactViewResources/Loader/Internal/ViewPortalsCollections.tsx b/ReactViewResources/Loader/Internal/ViewPortalsCollections.tsx new file mode 100644 index 00000000..47375a37 --- /dev/null +++ b/ReactViewResources/Loader/Internal/ViewPortalsCollections.tsx @@ -0,0 +1,42 @@ +import * as React from "react"; +import { ObservableListCollection } from "./ObservableCollection"; +import { ViewMetadata } from "./ViewMetadata"; +import { ViewPortalLegacy, ViewLifecycleEventHandler, ViewErrorHandler } from "./ViewPortalLegacy"; +export { ViewLifecycleEventHandler, ViewErrorHandler } from "./ViewPortal"; + +interface IViewPortalsCollectionProps { + views: ObservableListCollection; + viewAdded: ViewLifecycleEventHandler; + viewRemoved: ViewLifecycleEventHandler; + viewErrorRaised: ViewErrorHandler; +} + +/** + * Handles notifications from the views collection. Whenever a view is added or removed + * the corresponding ViewPortal is added or removed + * */ +export class ViewPortalsCollection extends React.Component { + + constructor(props: IViewPortalsCollectionProps, context: any) { + super(props, context); + props.views.addChangedListener(() => this.forceUpdate()); + } + + public shouldComponentUpdate() { + return false; + } + + private renderViewPortal(view: ViewMetadata) { + return ( + + ); + } + + public render(): React.ReactNode { + return this.props.views.items.sort((a, b) => a.name.localeCompare(b.name)).map(view => this.renderViewPortal(view)); + } +} \ No newline at end of file diff --git a/ReactViewResources/Loader/Public/ViewFrame.tsx b/ReactViewResources/Loader/Public/ViewFrame.tsx index f14e7b8b..52425885 100644 --- a/ReactViewResources/Loader/Public/ViewFrame.tsx +++ b/ReactViewResources/Loader/Public/ViewFrame.tsx @@ -4,8 +4,9 @@ import { newView, ViewMetadata } from "../Internal/ViewMetadata"; import { getEnsureDisposeInnerViewsFlag, ViewMetadataContext } from "../Internal/ViewMetadataContext"; import { ViewSharedContext} from "./ViewSharedContext"; import {ViewPortal} from "../Internal/ViewPortal"; +import InternalViewFrameLegacy from "./ViewFrameLegacy"; -interface IInternalViewFrameProps extends IViewFrameProps { +export interface IInternalViewFrameProps extends IViewFrameProps { viewMetadata: ViewMetadata; context?: T; } @@ -16,8 +17,10 @@ interface IInternalViewFrameProps extends IViewFrameProps { export function ViewFrame(props: IViewFrameProps): JSX.Element { const viewMetadata = React.useContext(ViewMetadataContext); const viewContext = React.useContext(ViewSharedContext); - - console.log("Creating ViewFrame:", props.name, getEnsureDisposeInnerViewsFlag()); + + if(!getEnsureDisposeInnerViewsFlag()) { + return ; + } return ; } diff --git a/ReactViewResources/Loader/Public/ViewFrameLegacy.tsx b/ReactViewResources/Loader/Public/ViewFrameLegacy.tsx new file mode 100644 index 00000000..415119ec --- /dev/null +++ b/ReactViewResources/Loader/Public/ViewFrameLegacy.tsx @@ -0,0 +1,92 @@ +import * as React from "react"; +import { newView, ViewMetadata } from "../Internal/ViewMetadata"; +import {IInternalViewFrameProps} from "./ViewFrame"; + +export default class InternalViewFrameLegacy extends React.Component, {}, ViewMetadata> { + private static generation = 0; + + private generation: number; + private placeholder: Element; + private replacement: Element; + + constructor(props: IInternalViewFrameProps, context: any) { + super(props, context); + if (props.name === "") { + throw new Error("View Frame name must be specified (not empty)"); + } + + if (!/^[A-Za-z_][A-Za-z0-9_]*/.test(props.name as string)) { + // must be a valid js symbol name + throw new Error("View Frame name can only contain letters, numbers or _"); + } + + // keep track of this frame generation, so that we can keep tracking the most recent frame instance + this.generation = ++InternalViewFrameLegacy.generation; + + const view = this.getView(); + if (view) { + // update the existing view generation + view.generation = this.generation; + } + } + + private get fullName() { + const parentName = this.parentView.name; + // @ts-ignore + return (parentName ? (parentName + ".") : "") + this.props.name; + } + + public shouldComponentUpdate(): boolean { + // prevent component updates + return false; + } + + private get parentView(): ViewMetadata { + return this.props.viewMetadata; + } + + private getView(): ViewMetadata | undefined { + const fullName = this.fullName; + return this.parentView.childViews.items.find(c => c.name === fullName); + } + + public componentDidMount() { + const existingView = this.getView(); + if (existingView) { + // there's a view already rendered, insert in current frame's placeholder + this.replacement = existingView.placeholder; + this.placeholder.parentElement!.replaceChild(this.replacement, this.placeholder); + return; + } + + const id = this.generation; // for this purpose we can use generation (we just need a unique number) + const childView = newView(id, this.fullName, false, this.placeholder); + childView.generation = this.generation; + childView.parentView = this.parentView; + childView.context = this.props.context; + + const loadedHandler = this.props.loaded; + if (loadedHandler) { + childView.viewLoadTask.promise.then(() => loadedHandler()); + } + + this.parentView.childViews.add(childView); + } + + public componentWillUnmount() { + if (this.replacement) { + // put back the original container, otherwise react will complain + this.replacement.parentElement!.replaceChild(this.placeholder, this.replacement); + } + + const existingView = this.getView(); + if (existingView && this.generation === existingView.generation) { + // this is the most recent frame - meaning it was not replaced by another one - so the view should be removed + this.parentView.childViews.remove(existingView); + } + } + + public render() { + return
this.placeholder = e!} className={this.props.className} />; + } +} From c1086a05b070ece0587eb5f12f72029ca6647a45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lder=20Gon=C3=A7alves?= Date: Wed, 3 Dec 2025 15:01:40 +0000 Subject: [PATCH 10/28] Update ViewPortalsCollections.tsx --- .../Loader/Internal/ViewPortalsCollections.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ReactViewResources/Loader/Internal/ViewPortalsCollections.tsx b/ReactViewResources/Loader/Internal/ViewPortalsCollections.tsx index 47375a37..03b93c1b 100644 --- a/ReactViewResources/Loader/Internal/ViewPortalsCollections.tsx +++ b/ReactViewResources/Loader/Internal/ViewPortalsCollections.tsx @@ -29,10 +29,10 @@ export class ViewPortalsCollection extends React.Component + view={view} + viewMounted={this.props.viewAdded} + viewUnmounted={this.props.viewRemoved} + viewErrorRaised={this.props.viewErrorRaised} /> ); } From d1dbbb0e958a1c2dea6d617f51db7cb3788e0195 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lder=20Gon=C3=A7alves?= Date: Wed, 3 Dec 2025 15:02:29 +0000 Subject: [PATCH 11/28] Update ViewPortalsCollectionsLegacy.tsx --- ...wPortalsCollections.tsx => ViewPortalsCollectionsLegacy.tsx} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename ReactViewResources/Loader/Internal/{ViewPortalsCollections.tsx => ViewPortalsCollectionsLegacy.tsx} (93%) diff --git a/ReactViewResources/Loader/Internal/ViewPortalsCollections.tsx b/ReactViewResources/Loader/Internal/ViewPortalsCollectionsLegacy.tsx similarity index 93% rename from ReactViewResources/Loader/Internal/ViewPortalsCollections.tsx rename to ReactViewResources/Loader/Internal/ViewPortalsCollectionsLegacy.tsx index 03b93c1b..f79c71c3 100644 --- a/ReactViewResources/Loader/Internal/ViewPortalsCollections.tsx +++ b/ReactViewResources/Loader/Internal/ViewPortalsCollectionsLegacy.tsx @@ -15,7 +15,7 @@ interface IViewPortalsCollectionProps { * Handles notifications from the views collection. Whenever a view is added or removed * the corresponding ViewPortal is added or removed * */ -export class ViewPortalsCollection extends React.Component { +export class ViewPortalsCollectionLegacy extends React.Component { constructor(props: IViewPortalsCollectionProps, context: any) { super(props, context); From ac39aad4148281242c1be3fc39a164e01d0f2443 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lder=20Gon=C3=A7alves?= Date: Wed, 3 Dec 2025 15:03:09 +0000 Subject: [PATCH 12/28] Update ViewPortalLegacy.tsx --- ReactViewResources/Loader/Internal/ViewPortalLegacy.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ReactViewResources/Loader/Internal/ViewPortalLegacy.tsx b/ReactViewResources/Loader/Internal/ViewPortalLegacy.tsx index 9fb4c134..ae4b0bdd 100644 --- a/ReactViewResources/Loader/Internal/ViewPortalLegacy.tsx +++ b/ReactViewResources/Loader/Internal/ViewPortalLegacy.tsx @@ -92,4 +92,4 @@ export class ViewPortalLegacy extends React.Component, this.shadowRoot); } -} \ No newline at end of file +} From 4e0bc1b7841e517f116cb57a22ea9d092e44e78c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lder=20Gon=C3=A7alves?= Date: Wed, 3 Dec 2025 15:06:33 +0000 Subject: [PATCH 13/28] Update Loader.View.tsx --- .../Loader/Internal/Loader.View.tsx | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/ReactViewResources/Loader/Internal/Loader.View.tsx b/ReactViewResources/Loader/Internal/Loader.View.tsx index d6a7a0d3..296c5c5d 100644 --- a/ReactViewResources/Loader/Internal/Loader.View.tsx +++ b/ReactViewResources/Loader/Internal/Loader.View.tsx @@ -4,12 +4,30 @@ import { getEnsureDisposeInnerViewsFlag, ViewMetadataContext } from "../Internal import { PluginsContext, PluginsContextHolder } from "../Public/PluginsContext"; import { formatUrl, ResourceLoader } from "../Public/ResourceLoader"; import { ViewMetadata } from "./ViewMetadata"; +import {addView, deleteView} from "./ViewsCollection"; +import {notifyViewDestroyed, notifyViewInitialized} from "./NativeAPI"; +import {handleError} from "./ErrorHandler"; +import {ViewPortalsCollectionLegacy} from "./ViewPortalsCollectionsLegacy"; export function createView(componentClass: any, properties: {}, view: ViewMetadata, componentName: string) { componentClass.contextType = PluginsContext; const makeResourceUrl = (resourceKey: string, ...params: string[]) => formatUrl(view.name, resourceKey, ...params); console.log("Creating view:", view.name, getEnsureDisposeInnerViewsFlag()); + + if(!getEnsureDisposeInnerViewsFlag()) { + return + + + + {React.createElement(componentClass, { ref: e => view.modules.set(componentName, e), ...properties })} + + + ; + } return ( @@ -25,3 +43,17 @@ export function createView(componentClass: any, properties: {}, view: ViewMetada export function renderMainView(children: React.ReactElement, container: Element) { return new Promise(resolve => ReactDOM.hydrate(children, container, resolve)); } + +function onChildViewAdded(childView: ViewMetadata) { + addView(childView.name, childView); + notifyViewInitialized(childView.name); +} + +function onChildViewRemoved(childView: ViewMetadata) { + deleteView(childView.name); + notifyViewDestroyed(childView.name); +} + +function onChildViewErrorRaised(childView: ViewMetadata, error: Error) { + handleError(error, childView); +} \ No newline at end of file From bc75cd2d1da968949e932b81f722af2c094665a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lder=20Gon=C3=A7alves?= Date: Wed, 3 Dec 2025 15:07:33 +0000 Subject: [PATCH 14/28] Update Loader.View.tsx --- ReactViewResources/Loader/Internal/Loader.View.tsx | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/ReactViewResources/Loader/Internal/Loader.View.tsx b/ReactViewResources/Loader/Internal/Loader.View.tsx index 296c5c5d..9a9ffb4b 100644 --- a/ReactViewResources/Loader/Internal/Loader.View.tsx +++ b/ReactViewResources/Loader/Internal/Loader.View.tsx @@ -4,17 +4,15 @@ import { getEnsureDisposeInnerViewsFlag, ViewMetadataContext } from "../Internal import { PluginsContext, PluginsContextHolder } from "../Public/PluginsContext"; import { formatUrl, ResourceLoader } from "../Public/ResourceLoader"; import { ViewMetadata } from "./ViewMetadata"; -import {addView, deleteView} from "./ViewsCollection"; -import {notifyViewDestroyed, notifyViewInitialized} from "./NativeAPI"; -import {handleError} from "./ErrorHandler"; -import {ViewPortalsCollectionLegacy} from "./ViewPortalsCollectionsLegacy"; +import { ViewPortalsCollectionLegacy } from "./ViewPortalsCollectionsLegacy"; +import { addView, deleteView } from "./ViewsCollection"; +import { notifyViewDestroyed, notifyViewInitialized } from "./NativeAPI"; +import { handleError } from "./ErrorHandler"; export function createView(componentClass: any, properties: {}, view: ViewMetadata, componentName: string) { componentClass.contextType = PluginsContext; const makeResourceUrl = (resourceKey: string, ...params: string[]) => formatUrl(view.name, resourceKey, ...params); - - console.log("Creating view:", view.name, getEnsureDisposeInnerViewsFlag()); - + if(!getEnsureDisposeInnerViewsFlag()) { return From 49066cfdd7ea44d0b22868ca4329e9b6097993c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lder=20Gon=C3=A7alves?= Date: Wed, 3 Dec 2025 15:08:15 +0000 Subject: [PATCH 15/28] Update Loader.View.tsx --- ReactViewResources/Loader/Internal/Loader.View.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ReactViewResources/Loader/Internal/Loader.View.tsx b/ReactViewResources/Loader/Internal/Loader.View.tsx index 9a9ffb4b..4775a9fa 100644 --- a/ReactViewResources/Loader/Internal/Loader.View.tsx +++ b/ReactViewResources/Loader/Internal/Loader.View.tsx @@ -3,11 +3,11 @@ import * as ReactDOM from "react-dom"; import { getEnsureDisposeInnerViewsFlag, ViewMetadataContext } from "../Internal/ViewMetadataContext"; import { PluginsContext, PluginsContextHolder } from "../Public/PluginsContext"; import { formatUrl, ResourceLoader } from "../Public/ResourceLoader"; +import { handleError } from "./ErrorHandler"; +import { notifyViewDestroyed, notifyViewInitialized } from "./NativeAPI"; import { ViewMetadata } from "./ViewMetadata"; import { ViewPortalsCollectionLegacy } from "./ViewPortalsCollectionsLegacy"; import { addView, deleteView } from "./ViewsCollection"; -import { notifyViewDestroyed, notifyViewInitialized } from "./NativeAPI"; -import { handleError } from "./ErrorHandler"; export function createView(componentClass: any, properties: {}, view: ViewMetadata, componentName: string) { componentClass.contextType = PluginsContext; From 95e7c5621a88be64f57a576b142fdfe146450909 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lder=20Gon=C3=A7alves?= Date: Wed, 3 Dec 2025 15:08:45 +0000 Subject: [PATCH 16/28] Update Loader.View.tsx --- ReactViewResources/Loader/Internal/Loader.View.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ReactViewResources/Loader/Internal/Loader.View.tsx b/ReactViewResources/Loader/Internal/Loader.View.tsx index 4775a9fa..f642296d 100644 --- a/ReactViewResources/Loader/Internal/Loader.View.tsx +++ b/ReactViewResources/Loader/Internal/Loader.View.tsx @@ -17,7 +17,7 @@ export function createView(componentClass: any, properties: {}, view: ViewMetada return - From 2cc5e8391b4f127208b681aa455c4d4e47b25f9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lder=20Gon=C3=A7alves?= Date: Wed, 3 Dec 2025 15:08:57 +0000 Subject: [PATCH 17/28] Update Loader.View.tsx --- ReactViewResources/Loader/Internal/Loader.View.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ReactViewResources/Loader/Internal/Loader.View.tsx b/ReactViewResources/Loader/Internal/Loader.View.tsx index f642296d..57142012 100644 --- a/ReactViewResources/Loader/Internal/Loader.View.tsx +++ b/ReactViewResources/Loader/Internal/Loader.View.tsx @@ -54,4 +54,4 @@ function onChildViewRemoved(childView: ViewMetadata) { function onChildViewErrorRaised(childView: ViewMetadata, error: Error) { handleError(error, childView); -} \ No newline at end of file +} From 62afa926a658d573e921046d9c7678e557f4e6c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lder=20Gon=C3=A7alves?= Date: Wed, 3 Dec 2025 15:09:47 +0000 Subject: [PATCH 18/28] Update ViewMetadataContext.ts --- ReactViewResources/Loader/Internal/ViewMetadataContext.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ReactViewResources/Loader/Internal/ViewMetadataContext.ts b/ReactViewResources/Loader/Internal/ViewMetadataContext.ts index 3ba01d30..b9982b90 100644 --- a/ReactViewResources/Loader/Internal/ViewMetadataContext.ts +++ b/ReactViewResources/Loader/Internal/ViewMetadataContext.ts @@ -11,4 +11,4 @@ export function getEnsureDisposeInnerViewsFlag(): boolean { export function setEnsureDisposeInnerViewsFlag(ensureDisposeInnerViews: boolean): void { window[EnsureDisposeInnerViewsFlagKey] = ensureDisposeInnerViews; -} \ No newline at end of file +} From 14f9b98118cf93700377b64b6a4ece959084b960 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lder=20Gon=C3=A7alves?= Date: Wed, 3 Dec 2025 15:11:19 +0000 Subject: [PATCH 19/28] d --- ReactViewResources/Loader/Internal/ViewPortal.tsx | 2 +- ReactViewResources/Loader/Loader.ts | 1 - Sample.Avalonia/ViewPlugin/ViewPlugin.ts | 2 -- 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/ReactViewResources/Loader/Internal/ViewPortal.tsx b/ReactViewResources/Loader/Internal/ViewPortal.tsx index d8f2955b..de394583 100644 --- a/ReactViewResources/Loader/Internal/ViewPortal.tsx +++ b/ReactViewResources/Loader/Internal/ViewPortal.tsx @@ -104,4 +104,4 @@ export class ViewPortal extends React.Component, this.props.shadowRoot); } -} \ No newline at end of file +} diff --git a/ReactViewResources/Loader/Loader.ts b/ReactViewResources/Loader/Loader.ts index ee9aa781..81c8c3cf 100644 --- a/ReactViewResources/Loader/Loader.ts +++ b/ReactViewResources/Loader/Loader.ts @@ -139,7 +139,6 @@ export function loadComponent( } if (frameName === mainFrameName) { - console.log(`Set ensureDisposeInnerViewsFlag to ${ensureDisposeInnerViews} for main frame`); setEnsureDisposeInnerViewsFlag(ensureDisposeInnerViews); } diff --git a/Sample.Avalonia/ViewPlugin/ViewPlugin.ts b/Sample.Avalonia/ViewPlugin/ViewPlugin.ts index 6631b7d2..72ed56d3 100644 --- a/Sample.Avalonia/ViewPlugin/ViewPlugin.ts +++ b/Sample.Avalonia/ViewPlugin/ViewPlugin.ts @@ -2,8 +2,6 @@ notifyViewLoaded(viewName: string): void; } -console.log("Plugin loaded"); - export default class ViewPlugin { constructor(private nativeObject: IViewPluginProperties) { From 73aba24fc3a3fc6c8f74c7182f95d9250760f3eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lder=20Gon=C3=A7alves?= Date: Wed, 3 Dec 2025 15:13:11 +0000 Subject: [PATCH 20/28] Update ViewPortal.tsx --- ReactViewResources/Loader/Internal/ViewPortal.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/ReactViewResources/Loader/Internal/ViewPortal.tsx b/ReactViewResources/Loader/Internal/ViewPortal.tsx index de394583..2018496e 100644 --- a/ReactViewResources/Loader/Internal/ViewPortal.tsx +++ b/ReactViewResources/Loader/Internal/ViewPortal.tsx @@ -37,11 +37,7 @@ function onChildViewErrorRaised(childView: ViewMetadata, error: Error) { /** * A ViewPortal is were a view is rendered. The view DOM is then moved into the appropriate placeholder. * This way we avoid a view being recreated (and losing state) when its ViewFrame is moved in the tree. - * - * A View Frame notifies its sibling view collection when a new instance is mounted. - * Upon mount, a View Portal is created and it will be responsible for rendering its view component in the shadow dom. - * A view portal is persisted until its View Frame counterpart disappears. - * */ + */ export class ViewPortal extends React.Component { private head: HTMLElement; From e3d093cf8112fdf9c8634860fe5fd1d77832d07c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lder=20Gon=C3=A7alves?= Date: Wed, 3 Dec 2025 15:49:32 +0000 Subject: [PATCH 21/28] Update ReactViewFactory.cs --- ReactViewControl/ReactViewFactory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ReactViewControl/ReactViewFactory.cs b/ReactViewControl/ReactViewFactory.cs index 075aeb9c..ba695754 100644 --- a/ReactViewControl/ReactViewFactory.cs +++ b/ReactViewControl/ReactViewFactory.cs @@ -31,6 +31,6 @@ public class ReactViewFactory { /// public virtual bool EnableViewPreload => true; - public virtual bool EnsureInnerViewsAreDisposed => false; + public virtual bool EnsureInnerViewsAreDisposed => true; } } From 5e7e9ae5755ca48a449d8cc176aea90efd935b2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lder=20Gon=C3=A7alves?= Date: Wed, 3 Dec 2025 15:49:44 +0000 Subject: [PATCH 22/28] r --- ReactViewResources/Loader/Loader.ts | 1 + Sample.Avalonia/ExtendedReactViewFactory.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ReactViewResources/Loader/Loader.ts b/ReactViewResources/Loader/Loader.ts index 81c8c3cf..ee9aa781 100644 --- a/ReactViewResources/Loader/Loader.ts +++ b/ReactViewResources/Loader/Loader.ts @@ -139,6 +139,7 @@ export function loadComponent( } if (frameName === mainFrameName) { + console.log(`Set ensureDisposeInnerViewsFlag to ${ensureDisposeInnerViews} for main frame`); setEnsureDisposeInnerViewsFlag(ensureDisposeInnerViews); } diff --git a/Sample.Avalonia/ExtendedReactViewFactory.cs b/Sample.Avalonia/ExtendedReactViewFactory.cs index 79324c0e..6a31a5f7 100644 --- a/Sample.Avalonia/ExtendedReactViewFactory.cs +++ b/Sample.Avalonia/ExtendedReactViewFactory.cs @@ -16,7 +16,7 @@ public override IViewModule[] InitializePlugins() { return new IViewModule[] { viewPlugin }; } - public override bool ShowDeveloperTools => false; + public override bool ShowDeveloperTools => true; public override bool EnableViewPreload => true; From 9c4f96d83205c855f1bcc915a3e7bbc2a9a115a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lder=20Gon=C3=A7alves?= Date: Fri, 12 Dec 2025 16:22:40 +0000 Subject: [PATCH 23/28] fix duplicated code --- .../Loader/Internal/Loader.View.tsx | 18 +----------------- .../Loader/Internal/ViewPortal.tsx | 7 +++---- 2 files changed, 4 insertions(+), 21 deletions(-) diff --git a/ReactViewResources/Loader/Internal/Loader.View.tsx b/ReactViewResources/Loader/Internal/Loader.View.tsx index 57142012..fcc691c7 100644 --- a/ReactViewResources/Loader/Internal/Loader.View.tsx +++ b/ReactViewResources/Loader/Internal/Loader.View.tsx @@ -3,11 +3,9 @@ import * as ReactDOM from "react-dom"; import { getEnsureDisposeInnerViewsFlag, ViewMetadataContext } from "../Internal/ViewMetadataContext"; import { PluginsContext, PluginsContextHolder } from "../Public/PluginsContext"; import { formatUrl, ResourceLoader } from "../Public/ResourceLoader"; -import { handleError } from "./ErrorHandler"; -import { notifyViewDestroyed, notifyViewInitialized } from "./NativeAPI"; import { ViewMetadata } from "./ViewMetadata"; +import { onChildViewAdded, onChildViewRemoved, onChildViewErrorRaised } from "./ViewPortal"; import { ViewPortalsCollectionLegacy } from "./ViewPortalsCollectionsLegacy"; -import { addView, deleteView } from "./ViewsCollection"; export function createView(componentClass: any, properties: {}, view: ViewMetadata, componentName: string) { componentClass.contextType = PluginsContext; @@ -41,17 +39,3 @@ export function createView(componentClass: any, properties: {}, view: ViewMetada export function renderMainView(children: React.ReactElement, container: Element) { return new Promise(resolve => ReactDOM.hydrate(children, container, resolve)); } - -function onChildViewAdded(childView: ViewMetadata) { - addView(childView.name, childView); - notifyViewInitialized(childView.name); -} - -function onChildViewRemoved(childView: ViewMetadata) { - deleteView(childView.name); - notifyViewDestroyed(childView.name); -} - -function onChildViewErrorRaised(childView: ViewMetadata, error: Error) { - handleError(error, childView); -} diff --git a/ReactViewResources/Loader/Internal/ViewPortal.tsx b/ReactViewResources/Loader/Internal/ViewPortal.tsx index 2018496e..c6fe683b 100644 --- a/ReactViewResources/Loader/Internal/ViewPortal.tsx +++ b/ReactViewResources/Loader/Internal/ViewPortal.tsx @@ -19,18 +19,17 @@ interface IViewPortalState { component: React.ReactElement; } - -function onChildViewAdded(childView: ViewMetadata) { +export function onChildViewAdded(childView: ViewMetadata) { addView(childView.name, childView); notifyViewInitialized(childView.name); } -function onChildViewRemoved(childView: ViewMetadata) { +export function onChildViewRemoved(childView: ViewMetadata) { deleteView(childView.name); notifyViewDestroyed(childView.name); } -function onChildViewErrorRaised(childView: ViewMetadata, error: Error) { +export function onChildViewErrorRaised(childView: ViewMetadata, error: Error) { handleError(error, childView); } From 8ac6c2272a127e0e856b94af21531f867057acad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lder=20Gon=C3=A7alves?= Date: Fri, 12 Dec 2025 16:26:18 +0000 Subject: [PATCH 24/28] Update ViewPortal.tsx --- ReactViewResources/Loader/Internal/ViewPortal.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ReactViewResources/Loader/Internal/ViewPortal.tsx b/ReactViewResources/Loader/Internal/ViewPortal.tsx index c6fe683b..4b26b428 100644 --- a/ReactViewResources/Loader/Internal/ViewPortal.tsx +++ b/ReactViewResources/Loader/Internal/ViewPortal.tsx @@ -3,9 +3,9 @@ import { webViewRootId } from "../Internal/Environment"; import { getStylesheets } from "./Common"; import { ViewMetadata } from "./ViewMetadata"; import { ViewSharedContext } from "../Public/ViewSharedContext"; -import {addView, deleteView} from "./ViewsCollection"; -import {notifyViewDestroyed, notifyViewInitialized} from "./NativeAPI"; -import {handleError} from "./ErrorHandler"; +import { addView, deleteView } from "./ViewsCollection"; +import { notifyViewDestroyed, notifyViewInitialized } from "./NativeAPI"; +import { handleError } from "./ErrorHandler"; export type ViewLifecycleEventHandler = (view: ViewMetadata) => void; export type ViewErrorHandler = (view: ViewMetadata, error: Error) => void; From 54c552922b16b4bc6b56f9f57b2e90bb85e15d04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lder=20Gon=C3=A7alves?= Date: Fri, 12 Dec 2025 16:54:53 +0000 Subject: [PATCH 25/28] Update Loader.ts --- ReactViewResources/Loader/Loader.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/ReactViewResources/Loader/Loader.ts b/ReactViewResources/Loader/Loader.ts index ee9aa781..81c8c3cf 100644 --- a/ReactViewResources/Loader/Loader.ts +++ b/ReactViewResources/Loader/Loader.ts @@ -139,7 +139,6 @@ export function loadComponent( } if (frameName === mainFrameName) { - console.log(`Set ensureDisposeInnerViewsFlag to ${ensureDisposeInnerViews} for main frame`); setEnsureDisposeInnerViewsFlag(ensureDisposeInnerViews); } From e9eb1086fdec021e2986256b4ea264050047e7c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lder=20Gon=C3=A7alves?= Date: Fri, 12 Dec 2025 16:56:56 +0000 Subject: [PATCH 26/28] Update Directory.Build.props --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index e73a76e6..48d8a8ed 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -5,7 +5,7 @@ 2.0.0.0 2.0.0.0 - 5.120.1 + 5.120.2 OutSystems ReactView Copyright © OutSystems 2023 From d9574c3fc69e3cf2374ad71b7a03e5e721430005 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lder=20Gon=C3=A7alves?= Date: Fri, 12 Dec 2025 17:02:47 +0000 Subject: [PATCH 27/28] more small fixes --- ReactViewResources/Loader/Public/ViewFrame.tsx | 1 - Sample.Avalonia/ExtendedReactViewFactory.cs | 2 +- Sample.Avalonia/MainView/MainView.tsx | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/ReactViewResources/Loader/Public/ViewFrame.tsx b/ReactViewResources/Loader/Public/ViewFrame.tsx index 52425885..1d75accd 100644 --- a/ReactViewResources/Loader/Public/ViewFrame.tsx +++ b/ReactViewResources/Loader/Public/ViewFrame.tsx @@ -115,7 +115,6 @@ class InternalViewFrame extends React.Component, { this.parentView.childViews.remove(existingView); } - this.shadowRoot = null; } diff --git a/Sample.Avalonia/ExtendedReactViewFactory.cs b/Sample.Avalonia/ExtendedReactViewFactory.cs index 6a31a5f7..79324c0e 100644 --- a/Sample.Avalonia/ExtendedReactViewFactory.cs +++ b/Sample.Avalonia/ExtendedReactViewFactory.cs @@ -16,7 +16,7 @@ public override IViewModule[] InitializePlugins() { return new IViewModule[] { viewPlugin }; } - public override bool ShowDeveloperTools => true; + public override bool ShowDeveloperTools => false; public override bool EnableViewPreload => true; diff --git a/Sample.Avalonia/MainView/MainView.tsx b/Sample.Avalonia/MainView/MainView.tsx index 3f7cf51a..42ef6865 100644 --- a/Sample.Avalonia/MainView/MainView.tsx +++ b/Sample.Avalonia/MainView/MainView.tsx @@ -35,7 +35,7 @@ export interface IMainViewBehaviors { } export interface IChildViews { - ListView: TaskListView + ListView: TaskListView; } enum TaskListShowStatus { From 19ea5756aba23fe5efd30c54b170fa48fb5fc5c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lder=20Gon=C3=A7alves?= Date: Fri, 12 Dec 2025 17:06:08 +0000 Subject: [PATCH 28/28] Update ViewFrame.tsx --- ReactViewResources/Loader/Public/ViewFrame.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/ReactViewResources/Loader/Public/ViewFrame.tsx b/ReactViewResources/Loader/Public/ViewFrame.tsx index 1d75accd..20f0f206 100644 --- a/ReactViewResources/Loader/Public/ViewFrame.tsx +++ b/ReactViewResources/Loader/Public/ViewFrame.tsx @@ -114,7 +114,6 @@ class InternalViewFrame extends React.Component, { // this is the most recent frame - meaning it was not replaced by another one - so the view should be removed this.parentView.childViews.remove(existingView); } - this.shadowRoot = null; }