import type * as React from "react"; import trieMemoize from "trie-memoize"; import type { FileTree } from "./file-tree"; import type { Subject } from "./tree/subject"; import { subject } from "./tree/subject"; /** * A plugin hook for adding roving focus to file tree nodes. * * @param fileTree - A file tree */ export function useRovingFocus( fileTree: FileTree ): UseRovingFocusPlugin { const focusedNodeId = createFocusedNodeId(fileTree); return { didChange: focusedNodeId, getProps(nodeId) { return createProps( focusedNodeId, nodeId, nodeId === focusedNodeId.getState() ); }, }; } const createProps = trieMemoize( [WeakMap, Map, Map], ( focusedNodeId: Subject, nodeId: number, focused: boolean ): RovingFocusProps => { return { tabIndex: focused ? 0 : -1, onFocus() { focusedNodeId.setState(nodeId); }, onBlur() { if (focused) { focusedNodeId.setState(-1); } }, }; } ); const createFocusedNodeId = trieMemoize( [WeakMap], (fileTree: FileTree) => subject(-1) ); export interface RovingFocusProps { tabIndex: number; onFocus(e: React.FocusEvent): void; onBlur(e: React.FocusEvent): void; } export interface UseRovingFocusPlugin { /** * A subject that you can use to observe to changes to the focused node. */ didChange: Subject; /** * Get the React props for a given node ID. * * @param nodeId - A node ID */ getProps: (nodeId: number) => RovingFocusProps; }