UNPKG

@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
'use client'; 'use strict'; 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