import React from 'react'; import styled, { css } from 'styled-components'; import { SplitType } from '.'; import { Nullable } from '../../types/utilities'; export const DEFAULT_MIN_SIZE = 50; export const getMinSize = (index: number, minSizes?: number | number[]): number => { if (typeof minSizes === 'number') { if (minSizes > 0) { return minSizes; } } else if (minSizes instanceof Array) { const value = minSizes[index]; if (value > 0) { return value; } } return DEFAULT_MIN_SIZE; }; export const getRefSize = ({ ref, split, }: { split: SplitType; ref: React.RefObject; }) => { const sizeAttr = split === 'vertical' ? 'width' : 'height'; return ref.current?.getBoundingClientRect()[sizeAttr] ?? 0; }; export type MoveDetails = { sizes: number[]; index: number; offset: number; minSizes: number[]; collapsedIndices: number[]; collapsedSize: number; }; /** * Mutates the original array in a recursive fashion, identifying the current sizes, whether they need to be changed, and whether they need to push the next or previous pane. */ export const moveSizes = ({ index, minSizes, offset, sizes, collapsedIndices, collapsedSize, }: MoveDetails): number => { //recursion break points if (!offset || index < 0 || index + 1 >= sizes.length) { return 0; } const isCollapsed = (idx: number) => collapsedIndices.includes(idx); const firstMinSize = isCollapsed(index) ? collapsedSize : getMinSize(index, minSizes); const secondMinSize = isCollapsed(index + 1) ? collapsedSize : getMinSize(index + 1, minSizes); const firstSize = sizes[index] + offset; const secondSize = sizes[index + 1] - offset; if (offset < 0 && firstSize < firstMinSize) { const missing = firstSize - firstMinSize; const pushedOffset = moveSizes({ sizes, index: index - 1, offset: missing, minSizes, collapsedIndices, collapsedSize, }); offset -= missing - pushedOffset; } else if (offset > 0 && secondSize < secondMinSize) { const missing = secondMinSize - secondSize; const pushedOffset = moveSizes({ sizes, index: index + 1, offset: missing, minSizes, collapsedIndices, collapsedSize, }); offset -= missing - pushedOffset; } sizes[index] += offset; sizes[index + 1] -= offset; return offset; }; interface MoveCollapsedDetails { offset: number; isReversed: boolean; index: number; sizes: number[]; collapsedIndices: number[]; minSizes: number[]; collapsedSize: number; } /** * This is only used when a collapse action is invoked. It's meant to move any collapsed siblings along with the move. */ export const moveCollapsedSiblings = ({ offset, isReversed, collapsedIndices, minSizes, sizes, index, collapsedSize, }: MoveCollapsedDetails) => { if (isReversed ? offset > 0 : offset < 0) { for ( let i = isReversed ? index : index + 1; isReversed ? i > 0 : i < sizes.length - 1; isReversed ? i-- : i++ ) { if (collapsedIndices.includes(i)) { moveSizes({ sizes, index: isReversed ? i - 1 : i, offset, minSizes, collapsedIndices, collapsedSize, }); } } } }; const verticalCss = css` left: 0; right: 0; flex-direction: row; `; const horizontalCss = css` bottom: 0; top: 0; flex-direction: column; min-height: 100%; width: 100%; `; export const Wrapper = styled.div<{ split: SplitType }>` display: flex; flex: 1; height: 100%; position: absolute; outline: none; overflow: hidden; ${props => (props.split === 'vertical' ? verticalCss : horizontalCss)} `; /** * Infers the indices of the collapsed panels from an array of nullable collapse sizes. If the index is null then it's not collapsed. */ export const convertCollapseSizesToIndices = (sizes?: Nullable[]) => sizes?.reduce((prev, cur, idx) => (cur !== null ? [...prev, idx] : [...prev]), [] as number[]) ?? []; export const addArray = (arr: number[]) => arr.reduce((prev, cur) => prev + cur, 0); /** * Returns a debounced version of a function. Similar to lodash's _.debounce * @param func the function to be debounced * @param waitFor the amount of time that must elapse before the debounce expires and the callback is called. */ export const debounce = any>(func: F, waitFor: number) => { let timeout: ReturnType | null = null; const debounced = (...args: Parameters) => { if (timeout !== null) { clearTimeout(timeout); timeout = null; } timeout = setTimeout(() => func(...args), waitFor); }; return debounced as (...args: Parameters) => ReturnType; };