71 lines
3.1 KiB
TypeScript
71 lines
3.1 KiB
TypeScript
"use client";
|
|
|
|
import { useCallback, useSyncExternalStore } from "react";
|
|
import type { DockviewPanelApi } from "dockview-react";
|
|
import { panelPortalManager } from "@/lib/layout/panel-portal-manager";
|
|
|
|
/**
|
|
* Tracks whether the dockview panel identified by panelId is the active tab
|
|
* in its dockview group. Portal-hosted panel content (see
|
|
* `lib/layout/panel-portal-manager.ts`) stays mounted while its tab is
|
|
* inactive — e.g. the Chat tab keeps running behind an active Files/Changes
|
|
* tab — so components that must distinguish "mounted" from "actually on
|
|
* screen" (like the unread-divider read tracker) need this signal rather
|
|
* than assuming mount implies visibility.
|
|
*
|
|
* Backed by `panelPortalManager` since portal-hosted content only receives
|
|
* its `panelId`, not the dockview api directly; `usePortalSlot`'s mount
|
|
* effect acquires the portal entry (with its api) before any child content
|
|
* renders, so the entry is virtually always present by the time this runs.
|
|
* Subscribes to both the manager's own add/remove notifications (a panel
|
|
* acquired after this hook's first mount, or released while it stays
|
|
* mounted) and the panel's own `onDidActiveChange` (tab switches within an
|
|
* existing group).
|
|
*
|
|
* Only for dockview-hosted panels — pass the panel's real id. Defaults to
|
|
* `false` until a portal entry/api is actually registered for it, rather
|
|
* than assuming visibility during the brief acquire/mount race or after a
|
|
* panel has been detached: a false negative just delays marking the session
|
|
* read by a render, while a false positive could mark it read too early.
|
|
* Non-dockview hosts (mobile, quick chat, kanban preview) never acquire a
|
|
* portal entry — they should pass a literal `true`/`false` instead of
|
|
* calling this hook.
|
|
*/
|
|
export function usePanelActive(panelId: string): boolean {
|
|
const subscribe = useCallback(
|
|
(onStoreChange: () => void) => {
|
|
// Tracks which api our onDidActiveChange listener is currently bound
|
|
// to, so a manager notification (panel acquired/released/remounted
|
|
// anywhere) can detect when *this* panelId's api was replaced —
|
|
// Dockview does this during a layout restore/switch — and rebind
|
|
// rather than keep listening on the now-stale api.
|
|
let boundApi: DockviewPanelApi | null = null;
|
|
let disposable: { dispose: () => void } | null = null;
|
|
|
|
const rebindIfApiChanged = () => {
|
|
const api = panelPortalManager.get(panelId)?.api ?? null;
|
|
if (api === boundApi) return;
|
|
disposable?.dispose();
|
|
boundApi = api;
|
|
disposable = api?.onDidActiveChange(onStoreChange) ?? null;
|
|
};
|
|
|
|
rebindIfApiChanged();
|
|
const unsubscribeManager = panelPortalManager.subscribe(() => {
|
|
rebindIfApiChanged();
|
|
onStoreChange();
|
|
});
|
|
return () => {
|
|
unsubscribeManager();
|
|
disposable?.dispose();
|
|
};
|
|
},
|
|
[panelId],
|
|
);
|
|
const getSnapshot = useCallback(
|
|
() => panelPortalManager.get(panelId)?.api?.isActive ?? false,
|
|
[panelId],
|
|
);
|
|
return useSyncExternalStore(subscribe, getSnapshot, () => false);
|
|
}
|