/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
/**
* Renders the panels that have been popped out into OS / PiP windows (#1208).
*
* Each open window gets a `createPortal` of the *same* panel component into
* its document body — so the panel keeps running in this tab's React tree and
* shares the live Zustand store. We only sync the theme class across windows
* and make sure every child closes when the parent tab unloads.
*/
import { useEffect } from 'react';
import { createPortal } from 'react-dom';
import { useSyncExternalStore } from 'react';
import { PinOff, X, MonitorUp } from 'lucide-react';
import { useViewerStore } from '@/store';
import { getPanelDef } from '@/lib/panels/registry';
import { renderPanelBody } from '@/lib/panels/renderPanelBody';
import { PortalContainerProvider } from '@/components/ui/portal-container';
import {
subscribePanelWindows,
getPanelWindowsSnapshot,
closePanelWindow,
closeAllPanelWindows,
syncPanelWindowsTheme,
type PanelWindowEntry,
} from '@/services/panel-windows';
export function PanelWindowHost() {
const windows = useSyncExternalStore(
subscribePanelWindows,
getPanelWindowsSnapshot,
getPanelWindowsSnapshot,
);
const theme = useViewerStore((s) => s.theme);
// Mirror the theme class (dark / colorful) onto every open window so toggling
// the theme in the main tab updates the popped-out panels too.
useEffect(() => {
syncPanelWindowsTheme(document.documentElement.className);
}, [theme, windows]);
// Never orphan a child window: close them all when the parent tab unloads.
useEffect(() => {
const onUnload = () => closeAllPanelWindows();
window.addEventListener('beforeunload', onUnload);
return () => {
window.removeEventListener('beforeunload', onUnload);
onUnload();
};
}, []);
return (
<>
{windows.map((entry) => createPortal(, entry.win.document.body, entry.id))}
>
);
}
function PanelWindowChrome({ entry }: { entry: PanelWindowEntry }) {
const def = getPanelDef(entry.id);
const Icon = def?.Icon;
const dock = () => useViewerStore.getState().showWorkspacePanel(entry.id);
const close = () => closePanelWindow(entry.id);
return (