import React, { useEffect, useRef, useState } from 'react'; import classNames from 'classnames'; import styles from '../../styles/rt-splitter.module.css'; import { RtSplitterBarProps } from './rt-splitter-bar.types'; import { RtSplitterBarDragger } from './rt-splitter-bar-dragger'; import { RtFrame } from '@dpa/react-frame-intro'; import { RtSplitBarSplitIcon } from './rt-split-bar-split-icon'; function getValidNumber(num: number | undefined): number { return typeof num === 'number' && !Number.isNaN(num) ? Math.round(num) : 0; } export const RtSplitterBar: React.FC = (props) => { const { vertical, index, areaNow, areaMin, areaMax, resizable, startCollapsible, endCollapsible, onOffsetStart, onOffsetUpdate, onOffsetEnd, onCollapse, containerSize, startSlot, endSlot, dragLinePaddings, } = props; const [startPos, setStartPos] = useState<[x: number, y: number] | null>(null); const rtSplitterBarRef = useRef(null); const [isContainerHovered, setIsContainerHovered] = useState(false); const [constrainedOffset, setConstrainedOffset] = useState(0); useEffect(() => { if (rtSplitterBarRef.current) { const parentSize = vertical ? rtSplitterBarRef.current.offsetHeight : rtSplitterBarRef.current.offsetWidth; setConstrainedOffset(parentSize / 2); } }, [vertical]); const onMouseDown: React.MouseEventHandler = (e) => { if (resizable && e.currentTarget) { setStartPos([e.pageX, e.pageY]); onOffsetStart(index); } }; const onTouchStart: React.TouchEventHandler = (e) => { if (resizable && e.touches.length === 1) { const touch = e.touches[0]; setStartPos([touch.pageX, touch.pageY]); onOffsetStart(index); } }; const cursorTrackingAnimationIdRef = useRef(null); // Анимация следования сплит-бара за курсором useEffect(() => { const onMouseMove = (e: MouseEvent) => { if (cursorTrackingAnimationIdRef.current) { cancelAnimationFrame(cursorTrackingAnimationIdRef.current); } cursorTrackingAnimationIdRef.current = requestAnimationFrame(() => { if (!e.target) { return; } const target = e.target as HTMLElement; const isValidHoverElement = target.classList.contains(styles['rt-splitter-bar-dragger-line']) || target.classList.contains(styles['rt-splitter-bar-dragger']) || target.classList.contains( styles['rt-splitter-bar-dragger-line-container'] ); if ( rtSplitterBarRef.current && isContainerHovered && isValidHoverElement ) { const parentRect = rtSplitterBarRef.current.getBoundingClientRect(); const elementSize = 30; const minOffset = elementSize / 2; const maxOffset = vertical ? parentRect.width - minOffset : parentRect.height - minOffset; const desiredOffset = vertical ? e.clientX - parentRect.left : e.clientY - parentRect.top; const newOffset = Math.max( minOffset, Math.min(desiredOffset, maxOffset) ); setConstrainedOffset(newOffset); } }); }; if (window) { window.addEventListener('mousemove', onMouseMove); } return () => { if (window) { window.removeEventListener('mousemove', onMouseMove); } if (cursorTrackingAnimationIdRef.current) { cancelAnimationFrame(cursorTrackingAnimationIdRef.current); } }; }, [isContainerHovered, vertical]); const mouseResizeAnimationIdRef = useRef(null); const touchResizeAnimationIdRef = useRef(null); // Анимация ресайза окна useEffect(() => { if (startPos) { const onMouseMove = (e: MouseEvent) => { if (mouseResizeAnimationIdRef.current) { cancelAnimationFrame(mouseResizeAnimationIdRef.current); } mouseResizeAnimationIdRef.current = requestAnimationFrame(() => { const { pageX, pageY } = e; const offsetX = pageX - startPos[0]; const offsetY = pageY - startPos[1]; onOffsetUpdate(index, offsetX, offsetY); }); }; const onMouseUp = () => { setStartPos(null); onOffsetEnd(); }; const handleTouchMove = (e: TouchEvent) => { if (touchResizeAnimationIdRef.current) { cancelAnimationFrame(touchResizeAnimationIdRef.current); } touchResizeAnimationIdRef.current = requestAnimationFrame(() => { if (e.touches.length === 1) { const touch = e.touches[0]; const offsetX = touch.pageX - startPos[0]; const offsetY = touch.pageY - startPos[1]; onOffsetUpdate(index, offsetX, offsetY); } }); }; const handleTouchEnd = () => { setStartPos(null); onOffsetEnd(); if (touchResizeAnimationIdRef.current) { cancelAnimationFrame(touchResizeAnimationIdRef.current); } if (mouseResizeAnimationIdRef.current) { cancelAnimationFrame(mouseResizeAnimationIdRef.current); } }; if (window) { window.addEventListener('touchmove', handleTouchMove); window.addEventListener('touchend', handleTouchEnd); window.addEventListener('mousemove', onMouseMove); window.addEventListener('mouseup', onMouseUp); } return () => { if (window) { window.removeEventListener('mousemove', onMouseMove); window.removeEventListener('mouseup', onMouseUp); window.removeEventListener('touchmove', handleTouchMove); window.removeEventListener('touchend', handleTouchEnd); } if (touchResizeAnimationIdRef.current) { cancelAnimationFrame(touchResizeAnimationIdRef.current); } if (mouseResizeAnimationIdRef.current) { cancelAnimationFrame(mouseResizeAnimationIdRef.current); } }; } }, [startPos, vertical, index, containerSize, areaNow, areaMin, areaMax]); return (
setIsContainerHovered(true)} onMouseLeave={() => setIsContainerHovered(false)} >
{startCollapsible && ( onCollapse(index, 'start')} /> )} {startSlot}
{endCollapsible && ( onCollapse(index, 'end')} /> )} {endSlot}
); };