refactor(palette-ops): flatten Handle wrapper into ref-based registry

This commit is contained in:
simosmik
2026-04-30 08:17:48 +00:00
parent 99495ff9ce
commit 67ffaa7eff

View File

@@ -1,4 +1,4 @@
import { createContext, useCallback, useContext, useEffect, useMemo, useRef } from 'react';
import { createContext, useContext, useEffect, useMemo, useRef } from 'react';
import type { ReactNode } from 'react';
export type PaletteOps = {
@@ -7,64 +7,47 @@ export type PaletteOps = {
refreshProjects: () => Promise<void> | void;
};
type Handle = {
handlersRef: React.MutableRefObject<Partial<PaletteOps>>;
call: PaletteOps;
type Registry = React.MutableRefObject<Partial<PaletteOps>>;
const PaletteOpsContext = createContext<Registry | null>(null);
const defaultOps: PaletteOps = {
openFile: () => undefined,
openSettings: () => undefined,
refreshProjects: () => undefined,
};
const PaletteOpsContext = createContext<Handle | null>(null);
const noop = () => undefined;
export function PaletteOpsProvider({ children }: { children: ReactNode }) {
const handlersRef = useRef<Partial<PaletteOps>>({});
const call = useMemo<PaletteOps>(
() => ({
openFile: (path) => handlersRef.current.openFile?.(path),
openSettings: (tab) => handlersRef.current.openSettings?.(tab),
refreshProjects: () => handlersRef.current.refreshProjects?.() ?? undefined,
}),
[],
);
const value = useMemo<Handle>(() => ({ handlersRef, call }), [call]);
return <PaletteOpsContext.Provider value={value}>{children}</PaletteOpsContext.Provider>;
const ref = useRef<Partial<PaletteOps>>({});
return <PaletteOpsContext.Provider value={ref}>{children}</PaletteOpsContext.Provider>;
}
export function usePaletteOps(): PaletteOps {
const handle = useContext(PaletteOpsContext);
if (!handle) {
return { openFile: noop, openSettings: noop, refreshProjects: noop };
}
return handle.call;
const ref = useContext(PaletteOpsContext);
return useMemo<PaletteOps>(
() => ({
openFile: (path) => (ref?.current.openFile ?? defaultOps.openFile)(path),
openSettings: (tab) => (ref?.current.openSettings ?? defaultOps.openSettings)(tab),
refreshProjects: () => (ref?.current.refreshProjects ?? defaultOps.refreshProjects)(),
}),
[ref],
);
}
export function usePaletteOpsRegister(partial: Partial<PaletteOps>) {
const handle = useContext(PaletteOpsContext);
const openFile = partial.openFile;
const openSettings = partial.openSettings;
const refreshProjects = partial.refreshProjects;
const ref = useContext(PaletteOpsContext);
const { openFile, openSettings, refreshProjects } = partial;
const installer = useCallback(() => {
if (!handle) return undefined;
const prev = { ...handle.handlersRef.current };
if (openFile) handle.handlersRef.current.openFile = openFile;
if (openSettings) handle.handlersRef.current.openSettings = openSettings;
if (refreshProjects) handle.handlersRef.current.refreshProjects = refreshProjects;
useEffect(() => {
if (!ref) return undefined;
const prev = { ...ref.current };
if (openFile) ref.current.openFile = openFile;
if (openSettings) ref.current.openSettings = openSettings;
if (refreshProjects) ref.current.refreshProjects = refreshProjects;
return () => {
if (openFile && handle.handlersRef.current.openFile === openFile) {
handle.handlersRef.current.openFile = prev.openFile;
}
if (openSettings && handle.handlersRef.current.openSettings === openSettings) {
handle.handlersRef.current.openSettings = prev.openSettings;
}
if (refreshProjects && handle.handlersRef.current.refreshProjects === refreshProjects) {
handle.handlersRef.current.refreshProjects = prev.refreshProjects;
}
if (openFile && ref.current.openFile === openFile) ref.current.openFile = prev.openFile;
if (openSettings && ref.current.openSettings === openSettings) ref.current.openSettings = prev.openSettings;
if (refreshProjects && ref.current.refreshProjects === refreshProjects) ref.current.refreshProjects = prev.refreshProjects;
};
}, [handle, openFile, openSettings, refreshProjects]);
useEffect(() => installer(), [installer]);
}, [ref, openFile, openSettings, refreshProjects]);
}