@gfazioli/mantine-split-pane
Version:
A Mantine 9 React component for resizable split pane layouts with 7 resizer variants, context-based prop inheritance, responsive orientation, and dynamic pane generation.
309 lines (305 loc) • 11 kB
JavaScript
'use client';
;
var core = require('@mantine/core');
var React = require('react');
var useResponsiveValue = require('../hooks/use-responsive-value.cjs');
var Split_context = require('../Split.context.cjs');
var SplitPane_module = require('./SplitPane.module.css.cjs');
const varsResolver = core.createVarsResolver((_, { grow }) => {
return {
root: {
"--split-pane-grow": grow ? 1 : "initial"
}
};
});
const defaultProps = {
grow: false
};
function isPercentageValue(size) {
return typeof size === "string" && size.includes("%");
}
function withPx(value) {
if (typeof value === "number") {
return `${value}px`;
}
if (typeof value === "string" && value.includes("px")) {
return value;
}
return value;
}
const SplitPane = core.factory((_props) => {
const { ref, ...restProps } = _props;
const props = core.useProps("Pane", defaultProps, restProps);
const ctx = Split_context.useSplitContext();
const {
children,
grow,
initialWidth: initialWidthProp,
initialHeight: initialHeightProp,
minWidth: minWidthProp,
minHeight: minHeightProp,
maxWidth: maxWidthProp,
maxHeight: maxHeightProp,
onResizeStart,
onResizing,
onResizeEnd,
onResetInitialSize,
className,
style,
classNames,
styles,
unstyled,
vars,
mod,
...others
} = props;
const initialWidth = useResponsiveValue.useResponsiveValue(initialWidthProp);
const initialHeight = useResponsiveValue.useResponsiveValue(initialHeightProp);
const minWidth = useResponsiveValue.useResponsiveValue(minWidthProp);
const minHeight = useResponsiveValue.useResponsiveValue(minHeightProp);
const maxWidth = useResponsiveValue.useResponsiveValue(maxWidthProp);
const maxHeight = useResponsiveValue.useResponsiveValue(maxHeightProp);
const localRef = React.useRef(null);
const hasBeenDraggedRef = React.useRef(false);
const dragRatioRef = React.useRef(null);
const prevContainerSizeRef = React.useRef(null);
React.useImperativeHandle(ref, () => ({
...localRef.current,
splitPane: localRef.current,
resetInitialSize,
getMinWidth: () => getSizeInPixel(minWidth),
getMinHeight: () => getSizeInPixel(minHeight),
getMaxWidth: () => getSizeInPixel(maxWidth),
getMaxHeight: () => getSizeInPixel(maxHeight),
getInitialWidth: () => initialWidth,
getInitialHeight: () => initialHeight,
onResizeStart: () => onResizeStart && onResizeStart(),
onResizing: (size) => onResizing && onResizing(size),
onResizeEnd: (size) => {
if (localRef.current && ctx.containerSize) {
const rect = localRef.current.getBoundingClientRect();
if (ctx.orientation === "vertical" && ctx.containerSize.width > 0) {
dragRatioRef.current = rect.width / ctx.containerSize.width;
hasBeenDraggedRef.current = true;
} else if (ctx.orientation === "horizontal" && ctx.containerSize.height > 0) {
dragRatioRef.current = rect.height / ctx.containerSize.height;
hasBeenDraggedRef.current = true;
}
}
onResizeEnd && onResizeEnd(size);
},
// Plain user-callback emitters used after a double-click reset: they
// forward to the consumer's `onResizing` / `onResizeEnd` props without
// touching `dragRatioRef` / `hasBeenDraggedRef`, so the reset that
// `resetInitialSize` just performed is not immediately undone.
notifyResizing: (size) => onResizing && onResizing(size),
notifyResizeEnd: (size) => onResizeEnd && onResizeEnd(size)
}));
const initialWidthRef = React.useRef(null);
const initialHeightRef = React.useRef(null);
const getSizeInPixel = React.useCallback(
(size) => {
if (size === void 0 || size === null) {
return void 0;
}
if (typeof size === "string" && size.includes("%")) {
const value = parseFloat(size);
const parentEl = localRef.current?.parentElement;
if (!parentEl) {
return void 0;
}
const dimension = ctx.orientation === "vertical" ? "width" : "height";
const parentSize = parentEl.getBoundingClientRect()[dimension];
return parentSize * value / 100;
}
const result = core.px(size);
return typeof result === "number" && !Number.isNaN(result) ? result : void 0;
},
[ctx.orientation]
);
const getInitialVerticalSize = React.useCallback(() => {
if (ctx.orientation === "vertical") {
const currentWidth = localRef.current?.getBoundingClientRect().width ?? 0;
if (!initialWidth && !minWidth) {
return currentWidth;
}
if (initialWidth && !minWidth) {
return getSizeInPixel(initialWidth);
}
if (!initialWidth && minWidth) {
return getSizeInPixel(minWidth);
}
if (initialWidth && minWidth) {
return Math.max(getSizeInPixel(initialWidth) ?? 0, getSizeInPixel(minWidth) ?? 0);
}
}
return "auto";
}, [ctx.orientation, initialWidth, minWidth, getSizeInPixel]);
const getInitialHorizontalSize = React.useCallback(() => {
if (ctx.orientation === "horizontal") {
const currentHeight = localRef.current?.getBoundingClientRect().height ?? 0;
if (!initialHeight && !minHeight) {
return currentHeight;
}
if (initialHeight && !minHeight) {
return getSizeInPixel(initialHeight);
}
if (!initialHeight && minHeight) {
return getSizeInPixel(minHeight);
}
if (initialHeight && minHeight) {
return Math.max(getSizeInPixel(initialHeight) ?? 0, getSizeInPixel(minHeight) ?? 0);
}
}
return "auto";
}, [ctx.orientation, initialHeight, minHeight, getSizeInPixel]);
React.useEffect(() => {
initialWidthRef.current = getInitialVerticalSize() ?? null;
initialHeightRef.current = getInitialHorizontalSize() ?? null;
if (localRef.current) {
localRef.current.style.width = withPx(getInitialVerticalSize() ?? "auto");
localRef.current.style.height = withPx(getInitialHorizontalSize() ?? "auto");
}
}, [getInitialVerticalSize, getInitialHorizontalSize]);
React.useEffect(() => {
hasBeenDraggedRef.current = false;
dragRatioRef.current = null;
const newWidth = getInitialVerticalSize();
const newHeight = getInitialHorizontalSize();
initialWidthRef.current = newWidth ?? null;
initialHeightRef.current = newHeight ?? null;
if (localRef.current) {
localRef.current.style.width = withPx(newWidth ?? "auto");
localRef.current.style.height = withPx(newHeight ?? "auto");
}
}, [getInitialVerticalSize, getInitialHorizontalSize]);
React.useEffect(() => {
if (!ctx.containerSize || !localRef.current) {
return;
}
const { width: containerWidth, height: containerHeight } = ctx.containerSize;
if (containerWidth === 0 && containerHeight === 0) {
return;
}
if (!prevContainerSizeRef.current) {
prevContainerSizeRef.current = { width: containerWidth, height: containerHeight };
return;
}
const prevSize = prevContainerSizeRef.current;
const widthChanged = Math.abs(containerWidth - prevSize.width) > 1;
const heightChanged = Math.abs(containerHeight - prevSize.height) > 1;
if (!widthChanged && !heightChanged) {
return;
}
prevContainerSizeRef.current = { width: containerWidth, height: containerHeight };
if (grow) {
return;
}
if (ctx.orientation === "vertical" && widthChanged) {
if (hasBeenDraggedRef.current && dragRatioRef.current !== null) {
const minPx = getSizeInPixel(minWidth);
const maxPx = getSizeInPixel(maxWidth);
let newWidth = dragRatioRef.current * containerWidth;
if (minPx !== void 0) {
newWidth = Math.max(newWidth, minPx);
}
if (maxPx !== void 0) {
newWidth = Math.min(newWidth, maxPx);
}
localRef.current.style.width = `${newWidth}px`;
} else if (isPercentageValue(initialWidth)) {
const newWidth = getSizeInPixel(initialWidth);
if (newWidth !== void 0) {
const minPx = getSizeInPixel(minWidth);
const maxPx = getSizeInPixel(maxWidth);
let finalWidth = newWidth;
if (minPx !== void 0) {
finalWidth = Math.max(finalWidth, minPx);
}
if (maxPx !== void 0) {
finalWidth = Math.min(finalWidth, maxPx);
}
localRef.current.style.width = `${finalWidth}px`;
}
}
}
if (ctx.orientation === "horizontal" && heightChanged) {
if (hasBeenDraggedRef.current && dragRatioRef.current !== null) {
const minPx = getSizeInPixel(minHeight);
const maxPx = getSizeInPixel(maxHeight);
let newHeight = dragRatioRef.current * containerHeight;
if (minPx !== void 0) {
newHeight = Math.max(newHeight, minPx);
}
if (maxPx !== void 0) {
newHeight = Math.min(newHeight, maxPx);
}
localRef.current.style.height = `${newHeight}px`;
} else if (isPercentageValue(initialHeight)) {
const newHeight = getSizeInPixel(initialHeight);
if (newHeight !== void 0) {
const minPx = getSizeInPixel(minHeight);
const maxPx = getSizeInPixel(maxHeight);
let finalHeight = newHeight;
if (minPx !== void 0) {
finalHeight = Math.max(finalHeight, minPx);
}
if (maxPx !== void 0) {
finalHeight = Math.min(finalHeight, maxPx);
}
localRef.current.style.height = `${finalHeight}px`;
}
}
}
}, [
ctx.containerSize,
ctx.orientation,
grow,
initialWidth,
initialHeight,
minWidth,
minHeight,
maxWidth,
maxHeight,
getSizeInPixel
]);
const getStyles = core.useStyles({
name: "SplitPane",
classes: SplitPane_module,
props,
className,
style,
classNames,
styles,
unstyled,
vars,
varsResolver
});
const resetInitialSize = (e) => {
e.preventDefault();
e.stopPropagation();
if (!localRef.current) {
return;
}
hasBeenDraggedRef.current = false;
dragRatioRef.current = null;
if (ctx.orientation === "vertical") {
const newSize = getInitialVerticalSize();
initialWidthRef.current = newSize ?? null;
localRef.current.style.width = withPx(newSize ?? "auto");
}
if (ctx.orientation === "horizontal") {
const newSize = getInitialHorizontalSize();
initialHeightRef.current = newSize ?? null;
localRef.current.style.height = withPx(newSize ?? "auto");
}
if (onResetInitialSize) {
onResetInitialSize(e);
}
};
return /* @__PURE__ */ React.createElement(core.Box, { ref: localRef, mod: { orientation: ctx.orientation }, ...others, ...getStyles("root") }, children);
});
SplitPane.classes = SplitPane_module;
SplitPane.displayName = "SplitPane";
exports.SplitPane = SplitPane;
//# sourceMappingURL=SplitPane.cjs.map