import { FocusEvent, MouseEvent, useCallback } from "react" import { FieldValues, UseFormSetValue } from "react-hook-form" import { DataGridMatrix, DataGridUpdateCommand } from "../models" import { DataGridCoordinates } from "../types" type UseDataGridCellHandlersOptions = { matrix: DataGridMatrix anchor: DataGridCoordinates | null rangeEnd: DataGridCoordinates | null setRangeEnd: (coords: DataGridCoordinates | null) => void isSelecting: boolean setIsSelecting: (isSelecting: boolean) => void isDragging: boolean setIsDragging: (isDragging: boolean) => void setSingleRange: (coords: DataGridCoordinates) => void dragEnd: DataGridCoordinates | null setDragEnd: (coords: DataGridCoordinates | null) => void setValue: UseFormSetValue execute: (command: DataGridUpdateCommand) => void multiColumnSelection?: boolean } export const useDataGridCellHandlers = < TData, TFieldValues extends FieldValues >({ matrix, anchor, rangeEnd, setRangeEnd, isDragging, setIsDragging, isSelecting, setIsSelecting, setSingleRange, dragEnd, setDragEnd, setValue, execute, multiColumnSelection, }: UseDataGridCellHandlersOptions) => { const getWrapperFocusHandler = useCallback( (coords: DataGridCoordinates) => { return (_e: FocusEvent) => { setSingleRange(coords) } }, [setSingleRange] ) const getOverlayMouseDownHandler = useCallback( (coords: DataGridCoordinates) => { return (e: MouseEvent) => { e.stopPropagation() e.preventDefault() if (e.shiftKey) { setRangeEnd(coords) return } setIsSelecting(true) setSingleRange(coords) } }, [setIsSelecting, setRangeEnd, setSingleRange] ) const getWrapperMouseOverHandler = useCallback( (coords: DataGridCoordinates) => { if (!isDragging && !isSelecting) { return } return (_e: MouseEvent) => { /** * If the column is not the same as the anchor col, * we don't want to select the cell. Unless multiColumnSelection is true. */ if (anchor?.col !== coords.col && !multiColumnSelection) { return } if (isSelecting) { setRangeEnd(coords) } else { setDragEnd(coords) } } }, [ anchor?.col, isDragging, isSelecting, setDragEnd, setRangeEnd, multiColumnSelection, ] ) const getInputChangeHandler = useCallback( // Using `any` here as the generic type of Path will // not be inferred correctly. (field: any) => { return (next: any, prev: any) => { const command = new DataGridUpdateCommand({ next, prev, setter: (value) => { setValue(field, value, { shouldDirty: true, shouldTouch: true, }) }, }) execute(command) } }, [setValue, execute] ) const onDragToFillStart = useCallback( (_e: MouseEvent) => { setIsDragging(true) }, [setIsDragging] ) const getIsCellSelected = useCallback( (cell: DataGridCoordinates | null) => { if (!cell || !anchor || !rangeEnd) { return false } return matrix.getIsCellSelected(cell, anchor, rangeEnd) }, [anchor, rangeEnd, matrix] ) const getIsCellDragSelected = useCallback( (cell: DataGridCoordinates | null) => { if (!cell || !anchor || !dragEnd) { return false } return matrix.getIsCellSelected(cell, anchor, dragEnd) }, [anchor, dragEnd, matrix] ) return { getWrapperFocusHandler, getOverlayMouseDownHandler, getWrapperMouseOverHandler, getInputChangeHandler, getIsCellSelected, getIsCellDragSelected, onDragToFillStart, } }