import memoizeOne from "@essentials/memoize-one";
import * as React from "react";
import { useSyncExternalStore } from "use-sync-external-store/shim";
import type { Subject } from "./tree/subject";
import { mergeProps as mergeProps_ } from "./utils";
/**
* A hook that observes to plugins and retrieves props that should be applied
* to a given node. An example of a plugin wouuld be the `useTraits()` hook.
*
* @param nodeId - The node ID used to retrieve props from a plugin
* @param plugins - A list of file tree plugins
* @example
* ```ts
* const traits = useTraits(fileTree)
* const props = useNodePlugins(fileTree.visibleNodes[0], [traits])
* return
...
* ```
*/
export function useNodePlugins(
nodeId: number,
plugins: NodePlugin[] = []
): React.HTMLAttributes {
const subject = createSubject(plugins);
const mergeProps = React.useRef(
memoizeOne(mergeProps_, (a, b) => shallowEqualArray(a[0], b[0]))
).current;
const storedPlugins = React.useRef(plugins);
React.useEffect(() => {
storedPlugins.current = plugins;
});
const getSnapshot = React.useCallback(() => {
const plugins = storedPlugins.current;
const length = plugins.length;
const props: React.HTMLAttributes[] = new Array(length);
for (let i = 0; i < length; i++) props[i] = plugins[i].getProps(nodeId);
return mergeProps(props);
}, [mergeProps, nodeId]);
return useSyncExternalStore(subject, getSnapshot, getSnapshot);
}
const createSubject = memoizeOne((plugins: NodePlugin[]) => {
return function subject(onStoreChange: () => void) {
const numPlugins = plugins.length;
const unsubs: (() => void)[] = new Array(numPlugins);
let i = 0;
for (; i < numPlugins; i++)
unsubs[i] = plugins[i].didChange.observe(onStoreChange);
return () => {
for (i = 0; i < unsubs.length; i++) unsubs[i]();
};
};
}, shallowEqualArray);
function shallowEqualArray(a: any[], b: any[]) {
if (a === b) return true;
if (a.length !== b.length) return false;
for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
return true;
}
export type NodePlugin = {
/**
* A subject that the `useNodePlugins()` hook will observe to.
*/
didChange: Subject;
/**
* A function that returns React props based on a node ID.
*
* @param nodeId - The ID of a node in the file tree.
*/
getProps(nodeId: number): React.HTMLAttributes;
};