import { useCallback, useEffect, useRef } from 'react'; import { adjustAdjacentGridFr, FLOATING_WIDGET_CELL_MIN_H, FLOATING_WIDGET_CELL_MIN_W, FLOATING_WIDGET_GRID_GAP, } from '../types/floatingWidget'; import { isPrimaryPointerButton } from '../utils/pointerDrag'; type SplitAxis = 'col' | 'row'; interface Options { onColFrChange: (next: number[]) => void; onRowFrChange: (next: number[]) => void; getColFr: () => number[]; getRowFr: () => number[]; getBodySize: () => { width: number; height: number }; onSplitEnd?: () => void; } export function useFloatingWidgetGridSplit({ onColFrChange, onRowFrChange, getColFr, getRowFr, getBodySize, onSplitEnd, }: Options) { const activeDragRef = useRef<(() => void) | null>(null); useEffect(() => () => { activeDragRef.current?.(); document.body.style.cursor = ''; document.body.style.userSelect = ''; }, []); const startSplit = useCallback(( e: React.PointerEvent, axis: SplitAxis, index: number, ) => { if (!isPrimaryPointerButton(e)) return; e.preventDefault(); e.stopPropagation(); const handle = e.currentTarget as HTMLElement; try { handle.setPointerCapture(e.pointerId); } catch { /* ignore */ } handle.classList.add('fw-grid-splitter--active'); const startX = e.clientX; const startY = e.clientY; const startColFr = [...getColFr()]; const startRowFr = [...getRowFr()]; document.body.style.cursor = axis === 'col' ? 'col-resize' : 'row-resize'; document.body.style.userSelect = 'none'; activeDragRef.current?.(); const onMove = (ev: PointerEvent) => { const size = getBodySize(); if (axis === 'col') { if (size.width <= 0) return; const delta = ev.clientX - startX; onColFrChange(adjustAdjacentGridFr( startColFr, index, delta, size.width, FLOATING_WIDGET_GRID_GAP, FLOATING_WIDGET_CELL_MIN_W, )); return; } if (size.height <= 0) return; const delta = ev.clientY - startY; onRowFrChange(adjustAdjacentGridFr( startRowFr, index, delta, size.height, FLOATING_WIDGET_GRID_GAP, FLOATING_WIDGET_CELL_MIN_H, )); }; const onUp = (ev: PointerEvent) => { handle.classList.remove('fw-grid-splitter--active'); document.body.style.cursor = ''; document.body.style.userSelect = ''; try { if (handle.hasPointerCapture(ev.pointerId)) { handle.releasePointerCapture(ev.pointerId); } } catch { /* ignore */ } document.removeEventListener('pointermove', onMove, true); document.removeEventListener('pointerup', onUp, true); document.removeEventListener('pointercancel', onUp, true); activeDragRef.current = null; onSplitEnd?.(); }; document.addEventListener('pointermove', onMove, { capture: true }); document.addEventListener('pointerup', onUp, { capture: true }); document.addEventListener('pointercancel', onUp, { capture: true }); activeDragRef.current = () => { document.removeEventListener('pointermove', onMove, true); document.removeEventListener('pointerup', onUp, true); document.removeEventListener('pointercancel', onUp, true); }; }, [getBodySize, getColFr, getRowFr, onColFrChange, onRowFrChange, onSplitEnd]); const onColSplitPointerDown = useCallback( (index: number) => (e: React.PointerEvent) => startSplit(e, 'col', index), [startSplit], ); const onRowSplitPointerDown = useCallback( (index: number) => (e: React.PointerEvent) => startSplit(e, 'row', index), [startSplit], ); return { onColSplitPointerDown, onRowSplitPointerDown }; }