@ui5/webcomponents-react
Version:
React Wrapper for UI5 Web Components and additional components
102 lines (101 loc) • 3.76 kB
JavaScript
'use client';
import { debounce, useStylesheet, useSyncRef } from '@ui5/webcomponents-react-base';
import { clsx } from 'clsx';
import { forwardRef, useEffect, useRef, useState } from 'react';
import { SplitterLayoutContext } from '../../internal/SplitterLayoutContext.js';
import { classNames, styleData } from './SplitterLayout.module.css.js';
import { useConcatSplitterElements } from './useConcatSplitterElements.js';
import { jsx as _jsx } from "react/jsx-runtime";
/**
* A layout that contains several content areas. The content that is added to the `SplitterLayout` should be wrapped
* into 0-n `SplitterElement`s which define the size and size constraints of the content area.
* The orientation of the `SplitterLayout` can be set to horizontal (default) or vertical. All content areas of the
* layout will be arranged in that way. In order to split vertically and horizontally at the same time, splitters need
* to be nested.
* By adding or changing `SplitterElement`s to the `SplitterLayout` that make up the content areas, the size can be changed
* programmatically. Additionally, the content areas can be made non-resizable individually and a minimal size (in px)
* can be set.
* The splitter bars are focusable to enable resizing of the content areas via keyboard. The size of the content areas
* can be manipulated when the splitter bar is focused and Left/Down/Right/Up are pressed.
*/
const SplitterLayout = /*#__PURE__*/forwardRef((props, ref) => {
const {
vertical,
children,
title,
style,
className,
options,
...rest
} = props;
const [componentRef, sLRef] = useSyncRef(ref);
const [reset, setReset] = useState(undefined);
const prevSize = useRef({
width: undefined,
height: undefined
});
const initialChildren = useRef(true);
const initialCustomDep = useRef(true);
const layoutElements = useConcatSplitterElements({
children: children ?? [],
width: style?.width,
height: style?.height,
vertical
});
useStylesheet(styleData, SplitterLayout.displayName);
useEffect(() => {
if (!initialChildren.current && options?.resetOnChildrenChange) {
setReset(true);
}
initialChildren.current = false;
}, [children, options?.resetOnChildrenChange]);
useEffect(() => {
if (!initialCustomDep.current) {
setReset(true);
}
initialCustomDep.current = false;
}, options?.resetOnCustomDepsChange ?? []);
useEffect(() => {
if (options?.resetOnSizeChange) {
const layoutObserver = new ResizeObserver(debounce(([container]) => {
const containerRect = container.target.getBoundingClientRect();
if (!vertical && containerRect.width !== prevSize.current.width) {
prevSize.current.width = containerRect.width;
setReset(true);
} else if (vertical && containerRect.height !== prevSize.current.height) {
prevSize.current.height = containerRect.height;
setReset(true);
}
}, 60));
layoutObserver.observe(sLRef.current);
return () => {
layoutObserver.disconnect();
};
}
}, [vertical, options?.resetOnSizeChange]);
useEffect(() => {
if (reset) {
setReset(false);
}
}, [reset]);
return /*#__PURE__*/_jsx(SplitterLayoutContext.Provider, {
value: {
vertical,
reset
},
children: /*#__PURE__*/_jsx("div", {
style: {
flexDirection: vertical ? 'column' : 'row',
...style
},
title: title,
...rest,
className: clsx(classNames.splitterLayout, className),
ref: componentRef,
"data-splitter-vertical": vertical,
children: layoutElements
})
});
});
SplitterLayout.displayName = 'SplitterLayout';
export { SplitterLayout };