132 lines
3.7 KiB
TypeScript
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 };
|
|
}
|