Files
kandev/apps/web/hooks/use-editor-view-zone-comments.ts
T
2026-02-17 19:37:53 +00:00

69 lines
2.3 KiB
TypeScript

import { useEffect, useRef, type DependencyList } from 'react';
import type { editor as monacoEditor } from 'monaco-editor';
import { createRoot, type Root } from 'react-dom/client';
import type { ReactNode } from 'react';
type ViewZoneEntry = { id: string; root: Root };
type AddZoneFn = (afterLine: number, heightPx: number, content: ReactNode) => void;
/**
* Manages Monaco ViewZones that render React components inline between editor lines.
* Extracted from the diff viewer's inline-comment pattern.
*
* @param editor The Monaco editor instance (null while mounting)
* @param deps Dependency list — zones are rebuilt whenever these change
* @param buildZones Callback that receives an `addZone` helper to create zones
*/
export function useEditorViewZoneComments(
editor: monacoEditor.ICodeEditor | null,
deps: DependencyList,
buildZones: (addZone: AddZoneFn) => void,
): void {
const zonesRef = useRef<ViewZoneEntry[]>([]);
useEffect(() => {
if (!editor) return;
// Tear down old zones
const oldZones = zonesRef.current;
if (oldZones.length > 0) {
editor.changeViewZones((accessor) => {
for (const z of oldZones) accessor.removeZone(z.id);
});
const staleRoots = oldZones.map((z) => z.root);
queueMicrotask(() => staleRoots.forEach((r) => r.unmount()));
zonesRef.current = [];
}
// Build new zones
const newZones: ViewZoneEntry[] = [];
const addZone: AddZoneFn = (afterLine, heightPx, content) => {
const domNode = document.createElement('div');
domNode.style.zIndex = '10';
const root = createRoot(domNode);
root.render(content);
let zoneId = '';
editor.changeViewZones((accessor) => {
zoneId = accessor.addZone({ afterLineNumber: afterLine, heightInPx: heightPx, domNode });
});
newZones.push({ id: zoneId, root });
};
buildZones(addZone);
zonesRef.current = newZones;
return () => {
try {
editor.changeViewZones((accessor) => {
for (const z of newZones) accessor.removeZone(z.id);
});
} catch { /* editor may be disposed */ }
const roots = newZones.map((z) => z.root);
queueMicrotask(() => roots.forEach((r) => r.unmount()));
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [editor, ...deps]);
}