import React, {useState, useEffect, useRef, forwardRef} from 'react'; import sum from 'lodash/sum'; import type {SplitPaneProps} from '../../typings/hyper'; const SplitPane = forwardRef((props, ref) => { const dragPanePosition = useRef(0); const dragTarget = useRef(null); const paneIndex = useRef(0); const d1 = props.direction === 'horizontal' ? 'height' : 'width'; const d2 = props.direction === 'horizontal' ? 'top' : 'left'; const d3 = props.direction === 'horizontal' ? 'clientY' : 'clientX'; const panesSize = useRef(null); const [dragging, setDragging] = useState(false); const handleAutoResize = (ev: React.MouseEvent, index: number) => { ev.preventDefault(); paneIndex.current = index; const sizes_ = getSizes(); sizes_[paneIndex.current] = 0; sizes_[paneIndex.current + 1] = 0; const availableWidth = 1 - sum(sizes_); sizes_[paneIndex.current] = availableWidth / 2; sizes_[paneIndex.current + 1] = availableWidth / 2; props.onResize(sizes_); }; const handleDragStart = (ev: React.MouseEvent, index: number) => { ev.preventDefault(); setDragging(true); window.addEventListener('mousemove', onDrag); window.addEventListener('mouseup', onDragEnd); const target = ev.target as HTMLDivElement; dragTarget.current = target; dragPanePosition.current = dragTarget.current.getBoundingClientRect()[d2]; panesSize.current = target.parentElement!.getBoundingClientRect()[d1]; paneIndex.current = index; }; const getSizes = () => { const {sizes} = props; let sizes_: number[]; if (sizes) { sizes_ = [...sizes.asMutable()]; } else { const total = props.children.length; const count = new Array(total).fill(1 / total); sizes_ = count; } return sizes_; }; const onDrag = (ev: MouseEvent) => { const sizes_ = getSizes(); const i = paneIndex.current; const pos = ev[d3]; const d = Math.abs(dragPanePosition.current - pos) / panesSize.current!; if (pos > dragPanePosition.current) { sizes_[i] += d; sizes_[i + 1] -= d; } else { sizes_[i] -= d; sizes_[i + 1] += d; } props.onResize(sizes_); }; const onDragEnd = () => { window.removeEventListener('mousemove', onDrag); window.removeEventListener('mouseup', onDragEnd); setDragging(false); }; useEffect(() => { return () => { onDragEnd(); }; }, []); const {children, direction, borderColor} = props; const sizeProperty = direction === 'horizontal' ? 'height' : 'width'; // workaround for the fact that if we don't specify // sizes, sometimes flex fails to calculate the // right height for the horizontal panes const sizes = props.sizes || new Array(children.length).fill(1 / children.length); return (
{children.map((child, i) => { const style = { // flexBasis doesn't work for the first horizontal pane, height need to be specified [sizeProperty]: `${sizes[i] * 100}%`, flexBasis: `${sizes[i] * 100}%`, flexGrow: 0 }; return (
{child}
{i < children.length - 1 ? (
handleDragStart(e, i)} onDoubleClick={(e) => handleAutoResize(e, i)} style={{backgroundColor: borderColor}} className={`splitpane_divider splitpane_divider_${direction}`} /> ) : null} ); })}
); }); SplitPane.displayName = 'SplitPane'; export default SplitPane;