import * as React from "react"; import { FieldValues, Path, UseFormReturn, useFieldArray } from "react-hook-form"; interface UseItemsTableNavigationOptions { /** Nombre del array de líneas en el formulario (tipo-safe) */ name: Path; /** Creador de una línea vacía */ createEmpty: () => unknown; // ajusta el tipo del item si lo conoces /** Primer campo editable de la fila */ firstEditableField?: string; } export function useItemsTableNavigation( form: UseFormReturn, { name, createEmpty, firstEditableField = "description", }: UseItemsTableNavigationOptions ) { const { control, getValues, setFocus } = form; const fa = useFieldArray({ control, name }); // Desestructurar para evitar recreaciones const { append, insert, remove: faRemove, move } = fa; // Ref estable para getValues const getValuesRef = React.useRef(getValues); getValuesRef.current = getValues; const length = React.useCallback(() => { const arr = getValuesRef.current(name) as unknown[]; return Array.isArray(arr) ? arr.length : 0; }, [name]); const focusRowFirstField = React.useCallback( (rowIndex: number) => { queueMicrotask(() => { try { setFocus(`${name}.${rowIndex}.${firstEditableField}` as any, { shouldSelect: true, }); } catch { // el campo aún no está montado } }); }, [name, firstEditableField, setFocus] ); const addEmpty = React.useCallback( (atEnd = true, index?: number, initial?: Record) => { const row = { ...createEmpty(), ...(initial ?? {}) }; if (!atEnd && typeof index === "number") insert(index, row); else append(row); }, [append, insert, createEmpty] ); const duplicate = React.useCallback( (i: number) => { const curr = getValuesRef.current(`${name}.${i}`) as Record | undefined; if (!curr) return; const clone = typeof structuredClone === "function" ? structuredClone(curr) : JSON.parse(JSON.stringify(curr)); const { id: _id, ...sanitized } = clone; insert(i + 1, sanitized); }, [insert, name] ); const remove = React.useCallback( (i: number) => { if (i < 0 || i >= length()) return; faRemove(i); }, [faRemove, length] ); const moveUp = React.useCallback( (i: number) => { if (i <= 0) return; move(i, i - 1); }, [move] ); const moveDown = React.useCallback( (i: number) => { const len = length(); if (i < 0 || i >= len - 1) return; move(i, i + 1); }, [move, length] ); const onTabFromLastCell = React.useCallback( (rowIndex: number) => { const len = length(); if (rowIndex === len - 1) { addEmpty(true); focusRowFirstField(len); } else { focusRowFirstField(rowIndex + 1); } }, [length, addEmpty, focusRowFirstField] ); const onShiftTabFromFirstCell = React.useCallback( (rowIndex: number) => { if (rowIndex <= 0) return; focusRowFirstField(rowIndex - 1); }, [focusRowFirstField] ); return { fieldArray: fa, // { fields, append, remove, insert, move, ... } addEmpty, duplicate, remove, moveUp, moveDown, onTabFromLastCell, onShiftTabFromFirstCell, focusRowFirstField, }; }