'use client' import { useState, useCallback, useRef, useEffect } from 'react' interface ColumnWidths { [columnId: string]: number } interface UseColumnResizeOptions { enabled?: boolean initialWidths?: ColumnWidths | null minWidth?: number onWidthChange?: (widths: ColumnWidths) => void } interface UseColumnResizeReturn { columnWidths: ColumnWidths getColumnWidth: (columnId: string, defaultWidth?: number) => number | undefined startResize: (columnId: string, startX: number, startWidth: number) => void resetWidths: () => void isResizing: boolean resizingColumn: string | null } export function useColumnResize({ enabled = true, initialWidths = null, minWidth = 50, onWidthChange, }: UseColumnResizeOptions = {}): UseColumnResizeReturn { const [columnWidths, setColumnWidths] = useState(() => initialWidths ?? {}) const [resizingColumn, setResizingColumn] = useState(null) const resizeRef = useRef<{ columnId: string startX: number startWidth: number } | null>(null) // Store dependencies in refs to avoid useCallback recreation const minWidthRef = useRef(minWidth) const onWidthChangeRef = useRef(onWidthChange) useEffect(() => { minWidthRef.current = minWidth onWidthChangeRef.current = onWidthChange }, [minWidth, onWidthChange]) // Get column width (undefined allows CSS default) const getColumnWidth = useCallback((columnId: string, defaultWidth?: number): number | undefined => { if (!enabled) return defaultWidth return columnWidths[columnId] ?? defaultWidth }, [enabled, columnWidths]) // Start resizing a column const startResize = useCallback((columnId: string, startX: number, startWidth: number) => { if (!enabled) return resizeRef.current = { columnId, startX, startWidth } setResizingColumn(columnId) }, [enabled]) // Handle pointer/mouse move during resize - uses refs to avoid recreating const handlePointerMove = useCallback((e: PointerEvent | MouseEvent) => { if (!resizeRef.current) return const { columnId, startX, startWidth } = resizeRef.current const diff = e.clientX - startX const newWidth = Math.max(minWidthRef.current, startWidth + diff) setColumnWidths(prev => ({ ...prev, [columnId]: newWidth })) }, []) // Handle pointer/mouse up to end resize - uses refs to avoid recreating const handlePointerUp = useCallback(() => { if (!resizeRef.current) return resizeRef.current = null setResizingColumn(null) // Notify parent of the final width changes setColumnWidths(current => { onWidthChangeRef.current?.(current) return current }) }, []) // Add/remove global pointer/mouse listeners during resize useEffect(() => { if (resizingColumn) { // Add listeners for both pointer and mouse events for cross-browser support document.addEventListener('pointermove', handlePointerMove) document.addEventListener('pointerup', handlePointerUp) document.addEventListener('mousemove', handlePointerMove) document.addEventListener('mouseup', handlePointerUp) // Prevent text selection during resize document.body.style.cursor = 'col-resize' document.body.style.userSelect = 'none' return () => { // Remove listeners document.removeEventListener('pointermove', handlePointerMove) document.removeEventListener('pointerup', handlePointerUp) document.removeEventListener('mousemove', handlePointerMove) document.removeEventListener('mouseup', handlePointerUp) document.body.style.cursor = '' document.body.style.userSelect = '' } } }, [resizingColumn, handlePointerMove, handlePointerUp]) // Reset all widths const resetWidths = useCallback(() => { setColumnWidths({}) onWidthChange?.({}) }, [onWidthChange]) return { columnWidths, getColumnWidth, startResize, resetWidths, isResizing: resizingColumn !== null, resizingColumn, } }