2025-04-01 10:38:02 +09:00

115 lines
3.5 KiB
TypeScript

import { useState, useCallback } from 'react';
import { ComponentSize, DragHandlePosition, useSplitter } from '@grafana/ui';
export interface UseSnappingSplitterOptions {
/**
* The initial size of the primary pane between 0-1, defaults to 0.5
*/
initialSize?: number;
direction: 'row' | 'column';
dragPosition?: DragHandlePosition;
paneOptions: PaneOptions;
collapsed?: boolean;
// Size of the region left of the handle indicator that is responsive to dragging. At the same time acts as a margin
// pushing the left pane content left.
handleSize?: ComponentSize;
}
interface PaneOptions {
collapseBelowPixels: number;
snapOpenToPixels?: number;
}
interface PaneState {
collapsed: boolean;
snapSize?: number;
}
export function useSnappingSplitter(options: UseSnappingSplitterOptions) {
const { paneOptions } = options;
const [state, setState] = useState<PaneState>({
collapsed: options.collapsed ?? false,
snapSize: options.collapsed ? 0 : undefined,
});
const onResizing = useCallback(
(flexSize: number, pixelSize: number) => {
if (flexSize <= 0 && pixelSize <= 0) {
return;
}
const optionsPixelSize = (pixelSize / flexSize) * (1 - flexSize);
if (state.collapsed && optionsPixelSize > paneOptions.collapseBelowPixels) {
setState({ collapsed: false });
}
if (!state.collapsed && optionsPixelSize < paneOptions.collapseBelowPixels) {
setState({ collapsed: true });
}
},
[state, paneOptions.collapseBelowPixels]
);
const onSizeChanged = useCallback(
(flexSize: number, pixelSize: number) => {
if (flexSize <= 0 && pixelSize <= 0) {
return;
}
const newSecondPaneSize = 1 - flexSize;
const isSnappedClosed = state.snapSize === 0;
const sizeOfBothPanes = pixelSize / flexSize;
const snapOpenToPixels = paneOptions.snapOpenToPixels ?? sizeOfBothPanes / 2;
const snapSize = snapOpenToPixels / sizeOfBothPanes;
if (state.collapsed) {
if (isSnappedClosed) {
setState({ snapSize: Math.max(newSecondPaneSize, snapSize), collapsed: false });
} else {
setState({ snapSize: 0, collapsed: true });
}
} else if (isSnappedClosed) {
setState({ snapSize: newSecondPaneSize, collapsed: false });
}
},
[state, paneOptions.snapOpenToPixels]
);
const onToggleCollapse = useCallback(() => {
setState({ collapsed: !state.collapsed });
}, [state.collapsed]);
const { containerProps, primaryProps, secondaryProps, splitterProps } = useSplitter({
direction: options.direction,
dragPosition: options.dragPosition,
handleSize: options.handleSize,
initialSize: options.initialSize,
onResizing,
onSizeChanged,
});
// This is to allow resizing it beyond the content dimensions
secondaryProps.style.overflow = 'hidden';
secondaryProps.style.minWidth = 'unset';
secondaryProps.style.minHeight = 'unset';
if (state.snapSize) {
primaryProps.style = {
...primaryProps.style,
flexGrow: 1 - state.snapSize,
};
secondaryProps.style.flexGrow = state.snapSize;
} else if (state.snapSize === 0) {
primaryProps.style.flexGrow = 1;
secondaryProps.style.flexGrow = 0;
secondaryProps.style.minWidth = 'unset';
secondaryProps.style.minHeight = 'unset';
secondaryProps.style.overflow = 'unset';
}
return { containerProps, primaryProps, secondaryProps, splitterProps, splitterState: state, onToggleCollapse };
}