/* * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) * * SPDX-License-Identifier: AGPL-3.0-only */ import React, { ReactElement, useCallback, useEffect, useRef, useState } from 'react' import { ShowIf } from '../../common/show-if/show-if' import { SplitDivider } from './split-divider/split-divider' import './splitter.scss' import { useAdjustedRelativeSplitValue } from './hooks/use-adjusted-relative-split-value' export interface SplitterProps { left: ReactElement right: ReactElement additionalContainerClassName?: string showLeft: boolean showRight: boolean } /** * Checks if the given {@link Event} is a {@link MouseEvent} * @param event the event to check * @return {@code true} if the given event is a {@link MouseEvent} */ const isMouseEvent = (event: Event): event is MouseEvent => { return (event as MouseEvent).buttons !== undefined } const isLeftMouseButtonClicked = (mouseEvent: MouseEvent): boolean => { return mouseEvent.buttons === 1 } /** * Extracts the absolute horizontal position of the mouse or touch point from the event. * If no position could be found or * * @param moveEvent */ const extractHorizontalPosition = (moveEvent: MouseEvent | TouchEvent): number => { if (isMouseEvent(moveEvent)) { return moveEvent.pageX } else { return moveEvent.touches[0]?.pageX } } /** * Creates a Left/Right splitter react component. * * @param additionalContainerClassName css classes that are added to the split container. * @param left the react component that should be shown on the left side. * @param right the react component that should be shown on the right side. * @param showLeft defines if the left component should be shown or hidden. Settings this prop will hide the component with css. * @param showRight defines if the right component should be shown or hidden. Settings this prop will hide the component with css. * @return the created component */ export const Splitter: React.FC = ({ additionalContainerClassName, left, right, showLeft, showRight }) => { const [relativeSplitValue, setRelativeSplitValue] = useState(50) const adjustedRelativeSplitValue = useAdjustedRelativeSplitValue(showLeft, showRight, relativeSplitValue) const resizingInProgress = useRef(false) const splitContainer = useRef(null) /** * Starts the splitter resizing */ const onStartResizing = useCallback(() => { resizingInProgress.current = true }, []) /** * Stops the splitter resizing */ const onStopResizing = useCallback(() => { if (resizingInProgress.current) { resizingInProgress.current = false } }, []) /** * Recalculates the panel split based on the absolute mouse/touch position. * * @param moveEvent is a {@link MouseEvent} or {@link TouchEvent} that got triggered. */ const onMove = useCallback((moveEvent: MouseEvent | TouchEvent) => { if (!resizingInProgress.current || !splitContainer.current) { return } if (isMouseEvent(moveEvent) && !isLeftMouseButtonClicked(moveEvent)) { resizingInProgress.current = false moveEvent.preventDefault() return undefined } const horizontalPosition = extractHorizontalPosition(moveEvent) const horizontalPositionInSplitContainer = horizontalPosition - splitContainer.current.offsetLeft const newRelativeSize = horizontalPositionInSplitContainer / splitContainer.current.clientWidth setRelativeSplitValue(newRelativeSize * 100) moveEvent.preventDefault() }, []) /** * Registers and unregisters necessary event listeners on the body so you can use the split even if the mouse isn't moving over it. */ useEffect(() => { const moveHandler = onMove const stopResizeHandler = onStopResizing window.addEventListener('touchmove', moveHandler) window.addEventListener('mousemove', moveHandler) window.addEventListener('touchcancel', stopResizeHandler) window.addEventListener('touchend', stopResizeHandler) window.addEventListener('mouseup', stopResizeHandler) return () => { window.removeEventListener('touchmove', moveHandler) window.removeEventListener('mousemove', moveHandler) window.removeEventListener('touchcancel', stopResizeHandler) window.removeEventListener('touchend', stopResizeHandler) window.removeEventListener('mouseup', stopResizeHandler) } }, [resizingInProgress, onMove, onStopResizing]) return (
{left}
{right}
) }