Files
goldenChart/frontend/src/hooks/useFloatingWidgetGridSplit.ts
T

132 lines
3.7 KiB
TypeScript

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 };
}