|
|
|
@ -1,6 +1,6 @@ |
|
|
|
|
import { css } from '@emotion/css'; |
|
|
|
|
import { clamp, throttle } from 'lodash'; |
|
|
|
|
import { useCallback, useId, useLayoutEffect, useRef } from 'react'; |
|
|
|
|
import { clamp } from 'lodash'; |
|
|
|
|
import { useCallback, useId, useRef } from 'react'; |
|
|
|
|
import * as React from 'react'; |
|
|
|
|
|
|
|
|
|
import { GrafanaTheme2 } from '@grafana/data'; |
|
|
|
@ -12,16 +12,18 @@ import { DragHandlePosition, getDragStyles } from '../DragHandle/DragHandle'; |
|
|
|
|
export interface UseSplitterOptions { |
|
|
|
|
/** |
|
|
|
|
* The initial size of the primary pane between 0-1, defaults to 0.5 |
|
|
|
|
* If `usePixels` is true, this is the initial size in pixels of the second pane. |
|
|
|
|
*/ |
|
|
|
|
initialSize?: number; |
|
|
|
|
direction: 'row' | 'column'; |
|
|
|
|
dragPosition?: DragHandlePosition; |
|
|
|
|
usePixels?: boolean; |
|
|
|
|
/** |
|
|
|
|
* Called when ever the size of the primary pane changes |
|
|
|
|
* @param flexSize (float from 0-1) |
|
|
|
|
*/ |
|
|
|
|
onSizeChanged?: (flexSize: number, pixelSize: number) => void; |
|
|
|
|
onResizing?: (flexSize: number, pixelSize: number) => void; |
|
|
|
|
onSizeChanged?: (flexSize: number, firstPanePixels: number, secondPanePixels: number) => void; |
|
|
|
|
onResizing?: (flexSize: number, firstPanePixels: number, secondPanePixels: number) => void; |
|
|
|
|
|
|
|
|
|
// 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.
|
|
|
|
@ -48,7 +50,14 @@ const propsForDirection = { |
|
|
|
|
} as const; |
|
|
|
|
|
|
|
|
|
export function useSplitter(options: UseSplitterOptions) { |
|
|
|
|
const { direction, initialSize = 0.5, dragPosition = 'middle', onResizing, onSizeChanged } = options; |
|
|
|
|
const { |
|
|
|
|
direction, |
|
|
|
|
initialSize = options.usePixels ? 300 : 0.5, |
|
|
|
|
dragPosition = 'middle', |
|
|
|
|
onResizing, |
|
|
|
|
onSizeChanged, |
|
|
|
|
usePixels, |
|
|
|
|
} = options; |
|
|
|
|
|
|
|
|
|
const handleSize = getPixelSize(options.handleSize); |
|
|
|
|
const splitterRef = useRef<HTMLDivElement | null>(null); |
|
|
|
@ -56,43 +65,19 @@ export function useSplitter(options: UseSplitterOptions) { |
|
|
|
|
const secondPaneRef = useRef<HTMLDivElement | null>(null); |
|
|
|
|
const containerRef = useRef<HTMLDivElement | null>(null); |
|
|
|
|
const containerSize = useRef<number | null>(null); |
|
|
|
|
const primarySizeRef = useRef<'1fr' | number>('1fr'); |
|
|
|
|
const firstPaneMeasurements = useRef<MeasureResult | undefined>(undefined); |
|
|
|
|
const primarySizeRef = useRef<number | null>(null); |
|
|
|
|
const referencePaneSize = useRef<MeasureResult | undefined>(undefined); |
|
|
|
|
const savedPos = useRef<string | undefined>(undefined); |
|
|
|
|
|
|
|
|
|
const measurementProp = propsForDirection[direction].dim; |
|
|
|
|
const clientAxis = propsForDirection[direction].axis; |
|
|
|
|
const minDimProp = propsForDirection[direction].min; |
|
|
|
|
const maxDimProp = propsForDirection[direction].max; |
|
|
|
|
|
|
|
|
|
// Using a resize observer here, as with content or screen based width/height the ratio between panes might
|
|
|
|
|
// change after a window resize, so ariaValueNow needs to be updated accordingly
|
|
|
|
|
useResizeObserver( |
|
|
|
|
containerRef.current!, |
|
|
|
|
(entries) => { |
|
|
|
|
for (const entry of entries) { |
|
|
|
|
if (!entry.target.isSameNode(containerRef.current)) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (!firstPaneRef.current) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const curSize = firstPaneRef.current!.getBoundingClientRect()[measurementProp]; |
|
|
|
|
const newDims = measureElement(firstPaneRef.current); |
|
|
|
|
|
|
|
|
|
splitterRef.current!.ariaValueNow = ariaValue(curSize, newDims[minDimProp], newDims[maxDimProp]); |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
500, |
|
|
|
|
[maxDimProp, minDimProp, direction, measurementProp] |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
const dragStart = useRef<number | null>(null); |
|
|
|
|
|
|
|
|
|
const onPointerDown = useCallback( |
|
|
|
|
(e: React.PointerEvent<HTMLDivElement>) => { |
|
|
|
|
if (!firstPaneRef.current) { |
|
|
|
|
if (!firstPaneRef.current || !secondPaneRef.current) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -100,32 +85,54 @@ export function useSplitter(options: UseSplitterOptions) { |
|
|
|
|
primarySizeRef.current = firstPaneRef.current!.getBoundingClientRect()[measurementProp]; |
|
|
|
|
containerSize.current = containerRef.current!.getBoundingClientRect()[measurementProp]; |
|
|
|
|
|
|
|
|
|
// set position at start of drag
|
|
|
|
|
dragStart.current = e[clientAxis]; |
|
|
|
|
splitterRef.current!.setPointerCapture(e.pointerId); |
|
|
|
|
firstPaneMeasurements.current = measureElement(firstPaneRef.current); |
|
|
|
|
|
|
|
|
|
if (usePixels) { |
|
|
|
|
referencePaneSize.current = measureElement(secondPaneRef.current, usePixels); |
|
|
|
|
} else { |
|
|
|
|
referencePaneSize.current = measureElement(firstPaneRef.current); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
savedPos.current = undefined; |
|
|
|
|
}, |
|
|
|
|
[measurementProp, clientAxis] |
|
|
|
|
[measurementProp, clientAxis, usePixels] |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
const onPointerMove = useCallback( |
|
|
|
|
(e: React.PointerEvent<HTMLDivElement>) => { |
|
|
|
|
if (dragStart.current !== null && primarySizeRef.current !== '1fr') { |
|
|
|
|
const diff = e[clientAxis] - dragStart.current; |
|
|
|
|
const dims = firstPaneMeasurements.current!; |
|
|
|
|
const onUpdateSize = useCallback( |
|
|
|
|
(diff: number) => { |
|
|
|
|
if (!containerSize.current || !primarySizeRef.current || !secondPaneRef.current) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const firstPanePixels = primarySizeRef.current; |
|
|
|
|
const secondPanePixels = containerSize.current - firstPanePixels - handleSize; |
|
|
|
|
const dims = referencePaneSize.current!; |
|
|
|
|
|
|
|
|
|
if (usePixels) { |
|
|
|
|
const newSize = clamp(secondPanePixels - diff, dims[minDimProp], dims[maxDimProp]); |
|
|
|
|
secondPaneRef.current!.style.flexBasis = `${newSize}px`; |
|
|
|
|
splitterRef.current!.ariaValueNow = `${newSize}`; |
|
|
|
|
onResizing?.(newSize, firstPanePixels + diff, newSize); |
|
|
|
|
} else { |
|
|
|
|
const newSize = clamp(primarySizeRef.current + diff, dims[minDimProp], dims[maxDimProp]); |
|
|
|
|
const newFlex = newSize / (containerSize.current! - handleSize); |
|
|
|
|
|
|
|
|
|
firstPaneRef.current!.style.flexGrow = `${newFlex}`; |
|
|
|
|
secondPaneRef.current!.style.flexGrow = `${1 - newFlex}`; |
|
|
|
|
splitterRef.current!.ariaValueNow = ariaValue(newSize, dims[minDimProp], dims[maxDimProp]); |
|
|
|
|
onResizing?.(newFlex, newSize, secondPanePixels - diff); |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
[onResizing, handleSize, usePixels, minDimProp, maxDimProp] |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
onResizing?.(newFlex, newSize); |
|
|
|
|
const onPointerMove = useCallback( |
|
|
|
|
(e: React.PointerEvent<HTMLDivElement>) => { |
|
|
|
|
if (dragStart.current !== null) { |
|
|
|
|
onUpdateSize(e[clientAxis] - dragStart.current); |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
[handleSize, clientAxis, minDimProp, maxDimProp, onResizing] |
|
|
|
|
[onUpdateSize, clientAxis] |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
const onPointerUp = useCallback( |
|
|
|
@ -133,14 +140,16 @@ export function useSplitter(options: UseSplitterOptions) { |
|
|
|
|
e.preventDefault(); |
|
|
|
|
e.stopPropagation(); |
|
|
|
|
|
|
|
|
|
splitterRef.current!.releasePointerCapture(e.pointerId); |
|
|
|
|
dragStart.current = null; |
|
|
|
|
|
|
|
|
|
if (typeof primarySizeRef.current === 'number') { |
|
|
|
|
onSizeChanged?.(parseFloat(firstPaneRef.current!.style.flexGrow), primarySizeRef.current); |
|
|
|
|
} |
|
|
|
|
splitterRef.current!.releasePointerCapture(e.pointerId); |
|
|
|
|
|
|
|
|
|
const firstPaneSize = firstPaneRef.current!.getBoundingClientRect()[measurementProp]; |
|
|
|
|
const secondPanePixels = containerSize.current! - firstPaneSize - handleSize; |
|
|
|
|
|
|
|
|
|
onSizeChanged?.(parseFloat(firstPaneRef.current!.style.flexGrow), firstPaneSize, secondPanePixels); |
|
|
|
|
}, |
|
|
|
|
[onSizeChanged] |
|
|
|
|
[onSizeChanged, handleSize, measurementProp] |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
const pressedKeys = useRef(new Set<string>()); |
|
|
|
@ -151,7 +160,7 @@ export function useSplitter(options: UseSplitterOptions) { |
|
|
|
|
if (nothingPressed) { |
|
|
|
|
keysLastHandledAt.current = null; |
|
|
|
|
return; |
|
|
|
|
} else if (primarySizeRef.current === '1fr') { |
|
|
|
|
} else if (primarySizeRef.current === null) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -175,21 +184,17 @@ export function useSplitter(options: UseSplitterOptions) { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const firstPaneDims = firstPaneMeasurements.current!; |
|
|
|
|
const curSize = firstPaneRef.current!.getBoundingClientRect()[measurementProp]; |
|
|
|
|
const newSize = clamp(curSize + sizeChange, firstPaneDims[minDimProp], firstPaneDims[maxDimProp]); |
|
|
|
|
const newFlex = newSize / (containerSize.current! - handleSize); |
|
|
|
|
|
|
|
|
|
firstPaneRef.current!.style.flexGrow = `${newFlex}`; |
|
|
|
|
secondPaneRef.current!.style.flexGrow = `${1 - newFlex}`; |
|
|
|
|
splitterRef.current!.ariaValueNow = ariaValue(newSize, firstPaneDims[minDimProp], firstPaneDims[maxDimProp]); |
|
|
|
|
// measure primary and container
|
|
|
|
|
primarySizeRef.current = firstPaneRef.current!.getBoundingClientRect()[measurementProp]; |
|
|
|
|
containerSize.current = containerRef.current!.getBoundingClientRect()[measurementProp]; |
|
|
|
|
|
|
|
|
|
onResizing?.(newFlex, newSize); |
|
|
|
|
onUpdateSize(sizeChange); |
|
|
|
|
|
|
|
|
|
keysLastHandledAt.current = time; |
|
|
|
|
|
|
|
|
|
window.requestAnimationFrame(handlePressedKeys); |
|
|
|
|
}, |
|
|
|
|
[direction, handleSize, minDimProp, maxDimProp, measurementProp, onResizing] |
|
|
|
|
[direction, measurementProp, onUpdateSize] |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
const onKeyDown = useCallback( |
|
|
|
@ -198,35 +203,6 @@ export function useSplitter(options: UseSplitterOptions) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (e.key === 'Enter') { |
|
|
|
|
if (savedPos.current === undefined) { |
|
|
|
|
savedPos.current = firstPaneRef.current!.style.flexGrow; |
|
|
|
|
firstPaneRef.current!.style.flexGrow = '0'; |
|
|
|
|
secondPaneRef.current!.style.flexGrow = '1'; |
|
|
|
|
} else { |
|
|
|
|
firstPaneRef.current!.style.flexGrow = savedPos.current; |
|
|
|
|
secondPaneRef.current!.style.flexGrow = `${1 - parseFloat(savedPos.current)}`; |
|
|
|
|
savedPos.current = undefined; |
|
|
|
|
} |
|
|
|
|
return; |
|
|
|
|
} else if (e.key === 'Home') { |
|
|
|
|
firstPaneMeasurements.current = measureElement(firstPaneRef.current); |
|
|
|
|
containerSize.current = containerRef.current!.getBoundingClientRect()[measurementProp]; |
|
|
|
|
const newFlex = firstPaneMeasurements.current[minDimProp] / (containerSize.current - handleSize); |
|
|
|
|
firstPaneRef.current.style.flexGrow = `${newFlex}`; |
|
|
|
|
secondPaneRef.current.style.flexGrow = `${1 - newFlex}`; |
|
|
|
|
splitterRef.current.ariaValueNow = '0'; |
|
|
|
|
return; |
|
|
|
|
} else if (e.key === 'End') { |
|
|
|
|
firstPaneMeasurements.current = measureElement(firstPaneRef.current); |
|
|
|
|
containerSize.current = containerRef.current!.getBoundingClientRect()[measurementProp]; |
|
|
|
|
const newFlex = firstPaneMeasurements.current[maxDimProp] / (containerSize.current - handleSize); |
|
|
|
|
firstPaneRef.current!.style.flexGrow = `${newFlex}`; |
|
|
|
|
secondPaneRef.current!.style.flexGrow = `${1 - newFlex}`; |
|
|
|
|
splitterRef.current!.ariaValueNow = '100'; |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if ( |
|
|
|
|
!( |
|
|
|
|
(direction === 'column' && VERTICAL_KEYS.has(e.key)) || |
|
|
|
@ -240,9 +216,16 @@ export function useSplitter(options: UseSplitterOptions) { |
|
|
|
|
savedPos.current = undefined; |
|
|
|
|
e.preventDefault(); |
|
|
|
|
e.stopPropagation(); |
|
|
|
|
|
|
|
|
|
primarySizeRef.current = firstPaneRef.current.getBoundingClientRect()[measurementProp]; |
|
|
|
|
containerSize.current = containerRef.current!.getBoundingClientRect()[measurementProp]; |
|
|
|
|
firstPaneMeasurements.current = measureElement(firstPaneRef.current); |
|
|
|
|
|
|
|
|
|
if (usePixels) { |
|
|
|
|
referencePaneSize.current = measureElement(secondPaneRef.current!); |
|
|
|
|
} else { |
|
|
|
|
referencePaneSize.current = measureElement(firstPaneRef.current!); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const newKey = !pressedKeys.current.has(e.key); |
|
|
|
|
|
|
|
|
|
if (newKey) { |
|
|
|
@ -254,7 +237,7 @@ export function useSplitter(options: UseSplitterOptions) { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
[direction, handlePressedKeys, handleSize, maxDimProp, measurementProp, minDimProp] |
|
|
|
|
[direction, handlePressedKeys, , measurementProp, usePixels] |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
const onKeyUp = useCallback( |
|
|
|
@ -268,11 +251,12 @@ export function useSplitter(options: UseSplitterOptions) { |
|
|
|
|
|
|
|
|
|
pressedKeys.current.delete(e.key); |
|
|
|
|
|
|
|
|
|
if (typeof primarySizeRef.current === 'number') { |
|
|
|
|
onSizeChanged?.(parseFloat(firstPaneRef.current!.style.flexGrow), primarySizeRef.current); |
|
|
|
|
if (primarySizeRef.current !== null) { |
|
|
|
|
const secondPanePixels = containerSize.current! - primarySizeRef.current - handleSize; |
|
|
|
|
onSizeChanged?.(parseFloat(firstPaneRef.current!.style.flexGrow), primarySizeRef.current, secondPanePixels); |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
[direction, onSizeChanged] |
|
|
|
|
[direction, onSizeChanged, handleSize] |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
const onDoubleClick = useCallback(() => { |
|
|
|
@ -280,13 +264,15 @@ export function useSplitter(options: UseSplitterOptions) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
firstPaneRef.current.style.flexGrow = '0.5'; |
|
|
|
|
secondPaneRef.current.style.flexGrow = '0.5'; |
|
|
|
|
const dim = measureElement(firstPaneRef.current); |
|
|
|
|
firstPaneMeasurements.current = dim; |
|
|
|
|
primarySizeRef.current = firstPaneRef.current!.getBoundingClientRect()[measurementProp]; |
|
|
|
|
splitterRef.current!.ariaValueNow = `${ariaValue(primarySizeRef.current, dim[minDimProp], dim[maxDimProp])}`; |
|
|
|
|
}, [maxDimProp, measurementProp, minDimProp]); |
|
|
|
|
if (usePixels) { |
|
|
|
|
secondPaneRef.current.style.flexBasis = `${initialSize}px`; |
|
|
|
|
} else { |
|
|
|
|
firstPaneRef.current.style.flexGrow = '0.5'; |
|
|
|
|
secondPaneRef.current.style.flexGrow = '0.5'; |
|
|
|
|
primarySizeRef.current = firstPaneRef.current!.getBoundingClientRect()[measurementProp]; |
|
|
|
|
splitterRef.current!.ariaValueNow = `50`; |
|
|
|
|
} |
|
|
|
|
}, [measurementProp, usePixels, initialSize]); |
|
|
|
|
|
|
|
|
|
const onBlur = useCallback(() => { |
|
|
|
|
// If focus is lost while keys are held, stop changing panel sizes
|
|
|
|
@ -295,10 +281,11 @@ export function useSplitter(options: UseSplitterOptions) { |
|
|
|
|
dragStart.current = null; |
|
|
|
|
|
|
|
|
|
if (typeof primarySizeRef.current === 'number') { |
|
|
|
|
onSizeChanged?.(parseFloat(firstPaneRef.current!.style.flexGrow), primarySizeRef.current); |
|
|
|
|
const secondPanePixels = containerSize.current! - primarySizeRef.current - handleSize; |
|
|
|
|
onSizeChanged?.(parseFloat(firstPaneRef.current!.style.flexGrow), primarySizeRef.current, secondPanePixels); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}, [onSizeChanged]); |
|
|
|
|
}, [onSizeChanged, handleSize]); |
|
|
|
|
|
|
|
|
|
const styles = useStyles2(getStyles, direction); |
|
|
|
|
const dragStyles = useStyles2(getDragStyles, dragPosition); |
|
|
|
@ -306,7 +293,7 @@ export function useSplitter(options: UseSplitterOptions) { |
|
|
|
|
const id = useId(); |
|
|
|
|
|
|
|
|
|
const primaryStyles: React.CSSProperties = { |
|
|
|
|
flexGrow: clamp(initialSize ?? 0.5, 0, 1), |
|
|
|
|
flexGrow: clamp(initialSize, 0, 1), |
|
|
|
|
[minDimProp]: 'min-content', |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
@ -315,6 +302,12 @@ export function useSplitter(options: UseSplitterOptions) { |
|
|
|
|
[minDimProp]: 'min-content', |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
if (usePixels) { |
|
|
|
|
primaryStyles.flexGrow = 1; |
|
|
|
|
secondaryStyles.flexGrow = 'unset'; |
|
|
|
|
secondaryStyles.flexBasis = `${initialSize}px`; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return { |
|
|
|
|
containerProps: { |
|
|
|
|
ref: containerRef, |
|
|
|
@ -363,49 +356,34 @@ interface MeasureResult { |
|
|
|
|
maxHeight: number; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function measureElement<T extends HTMLElement>(ref: T): MeasureResult { |
|
|
|
|
function measureElement<T extends HTMLElement>(ref: T, usePixels?: boolean): MeasureResult { |
|
|
|
|
const savedBodyOverflow = document.body.style.overflow; |
|
|
|
|
const savedWidth = ref.style.width; |
|
|
|
|
const savedHeight = ref.style.height; |
|
|
|
|
const savedFlex = ref.style.flexGrow; |
|
|
|
|
const savedFlexBasis = ref.style.flexBasis; |
|
|
|
|
|
|
|
|
|
document.body.style.overflow = 'hidden'; |
|
|
|
|
|
|
|
|
|
ref.style.flexGrow = '0'; |
|
|
|
|
ref.style.flexBasis = '0'; |
|
|
|
|
|
|
|
|
|
const { width: minWidth, height: minHeight } = ref.getBoundingClientRect(); |
|
|
|
|
|
|
|
|
|
ref.style.flexGrow = '100'; |
|
|
|
|
|
|
|
|
|
const { width: maxWidth, height: maxHeight } = ref.getBoundingClientRect(); |
|
|
|
|
|
|
|
|
|
document.body.style.overflow = savedBodyOverflow; |
|
|
|
|
|
|
|
|
|
ref.style.width = savedWidth; |
|
|
|
|
ref.style.height = savedHeight; |
|
|
|
|
ref.style.flexGrow = savedFlex; |
|
|
|
|
ref.style.flexBasis = savedFlexBasis; |
|
|
|
|
|
|
|
|
|
return { minWidth, maxWidth, minHeight, maxHeight }; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function useResizeObserver( |
|
|
|
|
target: Element, |
|
|
|
|
cb: (entries: ResizeObserverEntry[]) => void, |
|
|
|
|
throttleWait = 0, |
|
|
|
|
deps?: React.DependencyList |
|
|
|
|
) { |
|
|
|
|
const throttledCallback = throttle(cb, throttleWait); |
|
|
|
|
|
|
|
|
|
useLayoutEffect(() => { |
|
|
|
|
if (!target) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const resizeObserver = new ResizeObserver(throttledCallback); |
|
|
|
|
|
|
|
|
|
resizeObserver.observe(target, { box: 'device-pixel-content-box' }); |
|
|
|
|
return () => resizeObserver.disconnect(); |
|
|
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
|
|
|
}, deps); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function getStyles(theme: GrafanaTheme2, direction: UseSplitterOptions['direction']) { |
|
|
|
|
return { |
|
|
|
|
container: css({ |
|
|
|
|