@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.
469 lines (465 loc) • 16 kB
JavaScript
'use client';
'use strict';
var core = require('@mantine/core');
var React = require('react');
var useResponsiveValue = require('../hooks/use-responsive-value.cjs');
var useSplitResizerOrientation = require('../hooks/use-split-resizer-orientation.cjs');
var Split_context = require('../Split.context.cjs');
var snap = require('./snap.cjs');
var SplitResizer_module = require('./SplitResizer.module.css.cjs');
const varsResolver = core.createVarsResolver(
(theme, {
size,
opacity,
radius,
color,
hoverColor,
knobSize,
knobOpacity,
knobRadius,
knobColor,
knobHoverColor,
withKnob,
knobAlwaysOn,
spacing,
variant,
cursorVertical,
cursorHorizontal,
gradient,
hoverGradient
}) => {
const knobVariant = variant === "dotted" || variant === "dashed";
const forceKnobOpacityValue = withKnob && knobAlwaysOn && !knobVariant ? knobOpacity : "0";
const colors = variantColorResolver({
color,
hover: hoverColor,
knob: knobColor,
hoverKnob: knobHoverColor,
theme,
gradient,
hoverGradient,
variant: variant || "filled"
});
return {
root: {
"--split-resizer-size": core.getSize(size, "split-resizer-size"),
"--split-resizer-color": colors.color,
"--split-resizer-hover-color": colors.hover,
"--split-resizer-radius": core.getRadius(radius),
"--split-resizer-opacity": opacity !== void 0 ? opacity : "1",
"--split-resizer-knob-size": core.getSize(knobSize, "split-resizer-knob-size"),
"--split-resizer-knob-opacity": forceKnobOpacityValue,
"--split-resizer-knob-hover-opacity": withKnob || knobVariant ? "1" : "0",
"--split-resizer-knob-radius": core.getRadius(knobRadius),
"--split-resizer-knob-color": colors.knob,
"--split-resizer-knob-hover-color": colors.hoverKnob,
"--split-resizer-spacing": core.getSize(spacing, "split-resizer-spacing"),
"--split-resizer-cursor-vertical": cursorVertical || "col-resize",
"--split-resizer-cursor-horizontal": cursorHorizontal || "row-resize"
}
};
}
);
const variantColorResolver = ({
color,
hover,
knob,
hoverKnob,
theme,
variant,
gradient,
hoverGradient
}) => {
const parsedColor = color ? core.parseThemeColor({ color, theme }).value : void 0;
const parsedHover = hover ? core.parseThemeColor({ color: hover, theme }).value : void 0;
const parsedKnob = knob ? core.parseThemeColor({ color: knob, theme }).value : void 0;
const parsedHoverKnob = hoverKnob ? core.parseThemeColor({ color: hoverKnob, theme }).value : void 0;
const colors = {
color: parsedColor,
hover: parsedHover,
knob: parsedKnob,
hoverKnob: parsedHoverKnob
};
if (variant === "gradient") {
colors.color = core.getGradient(gradient, theme);
colors.hover = core.getGradient(hoverGradient || gradient, theme);
}
return colors;
};
const defaultProps = {
orientation: "vertical",
opacity: 0.8,
size: "sm",
radius: "xs",
withKnob: false,
knobAlwaysOn: true,
knobSize: "sm",
knobOpacity: 0.5,
knobRadius: "sm",
knobColor: "white",
knobHoverColor: "white",
spacing: "xs",
step: 8,
shiftStep: 64,
snapPoints: [],
snapTolerance: snap.DEFAULT_SNAP_TOLERANCE,
snapFrom: "before",
cursorVertical: "col-resize",
cursorHorizontal: "row-resize"
};
const SplitResizer = core.factory((_props) => {
const ctx = Split_context.useSplitContext();
const { containerSize: _containerSize, ...ctxResizerProps } = ctx || {};
const props = core.useProps("SplitResizer", { ...defaultProps, ...ctxResizerProps }, _props);
const {
orientation: propOrientation,
opacity,
size,
radius,
withKnob,
knobAlwaysOn,
knobSize,
knobOpacity,
knobRadius,
knobColor,
knobHoverColor,
spacing,
step,
shiftStep,
snapPoints,
snapTolerance,
snapFrom,
cursorVertical,
cursorHorizontal,
color,
hoverColor,
variant,
gradient,
hoverGradient,
onResizeStart,
onResizing,
onResizeEnd,
onSnap,
onDoubleClick,
__beforeRef: beforeRef,
__afterRef: afterRef,
className,
style,
classNames,
styles,
unstyled,
vars,
mod,
...rest
} = props;
const orientation = useSplitResizerOrientation.useSplitResizerOrientation(propOrientation ?? "vertical");
const resolvedSize = useResponsiveValue.useResponsiveValue(size) ?? defaultProps.size;
const resolvedSpacing = useResponsiveValue.useResponsiveValue(spacing) ?? defaultProps.spacing;
const resolvedKnobSize = useResponsiveValue.useResponsiveValue(knobSize) ?? defaultProps.knobSize;
const resolvedSnapPoints = useResponsiveValue.useResponsiveValue(snapPoints, []);
const normalizedSnap = React.useMemo(
() => snap.normalizeSnapPoints({ snapPoints: resolvedSnapPoints, snapTolerance }),
[resolvedSnapPoints, snapTolerance]
);
const lastSnappedPointRef = React.useRef(null);
const isSnappingRef = React.useRef(false);
const [isSnapping, setIsSnapping] = React.useState(false);
const reportSnap = (snappedPoint) => {
if (snappedPoint !== lastSnappedPointRef.current) {
lastSnappedPointRef.current = snappedPoint;
onSnap?.(snappedPoint);
}
const nextSnapping = snappedPoint !== null;
if (nextSnapping !== isSnappingRef.current) {
isSnappingRef.current = nextSnapping;
setIsSnapping(nextSnapping);
}
};
const resolvedProps = {
...props,
size: resolvedSize,
spacing: resolvedSpacing,
knobSize: resolvedKnobSize
};
const getStyles = core.useStyles({
name: "SplitResizer",
classes: SplitResizer_module,
props: resolvedProps,
className,
style,
classNames,
styles,
unstyled,
vars,
varsResolver
});
const containerRef = React.useRef(null);
const processVerticalSize = (deltaX = 0) => {
if (!beforeRef?.current || !afterRef?.current) {
return;
}
const minBeforeWidth = beforeRef.current.getMinWidth?.();
const maxBeforeWidth = beforeRef.current.getMaxWidth?.();
const minAfterWidth = afterRef.current.getMinWidth?.();
const maxAfterWidth = afterRef.current.getMaxWidth?.();
const beforePane = beforeRef.current.splitPane;
const afterPane = afterRef.current.splitPane;
if (!beforePane || !afterPane) {
return;
}
const beforeWidth = beforePane.getBoundingClientRect().width;
const afterWidth = afterPane.getBoundingClientRect().width;
const nextSizes = snap.calculateSnappedPaneSizes({
beforeSize: beforeWidth,
afterSize: afterWidth,
delta: deltaX,
minBeforeSize: minBeforeWidth,
maxBeforeSize: maxBeforeWidth,
minAfterSize: minAfterWidth,
maxAfterSize: maxAfterWidth,
snapPoints: normalizedSnap.snapPoints,
snapTolerance: normalizedSnap.snapTolerance,
snapFrom
});
reportSnap(nextSizes.snappedPoint);
function setVerticalSize() {
const beforeWidthString = `${nextSizes.beforeSize}px`;
const afterWidthString = `${nextSizes.afterSize}px`;
const beforePaneSizes = {
width: nextSizes.beforeSize,
height: beforePane.getBoundingClientRect().height
};
const afterPaneSizes = {
width: nextSizes.afterSize,
height: afterPane.getBoundingClientRect().height
};
beforeRef?.current?.onResizing?.(beforePaneSizes);
afterRef?.current?.onResizing?.(afterPaneSizes);
onResizing?.({
beforePane: beforePaneSizes,
afterPane: afterPaneSizes
});
beforePane.style.width = beforeWidthString;
afterPane.style.width = afterWidthString;
}
setVerticalSize();
};
const processHorizontalSize = (deltaY = 0) => {
if (!beforeRef?.current || !afterRef?.current) {
return;
}
const minBeforeHeight = beforeRef.current.getMinHeight?.();
const maxBeforeHeight = beforeRef.current.getMaxHeight?.();
const minAfterHeight = afterRef.current.getMinHeight?.();
const maxAfterHeight = afterRef.current.getMaxHeight?.();
const beforePane = beforeRef.current.splitPane;
const afterPane = afterRef.current.splitPane;
if (!beforePane || !afterPane) {
return;
}
const beforeHeight = beforePane.getBoundingClientRect().height;
const afterHeight = afterPane.getBoundingClientRect().height;
const nextSizes = snap.calculateSnappedPaneSizes({
beforeSize: beforeHeight,
afterSize: afterHeight,
delta: deltaY,
minBeforeSize: minBeforeHeight,
maxBeforeSize: maxBeforeHeight,
minAfterSize: minAfterHeight,
maxAfterSize: maxAfterHeight,
snapPoints: normalizedSnap.snapPoints,
snapTolerance: normalizedSnap.snapTolerance,
snapFrom
});
reportSnap(nextSizes.snappedPoint);
function setHorizontalSize() {
const beforeHeightString = `${nextSizes.beforeSize}px`;
const afterHeightString = `${nextSizes.afterSize}px`;
const beforePaneSizes = {
width: beforePane.getBoundingClientRect().width,
height: nextSizes.beforeSize
};
const afterPaneSizes = {
width: afterPane.getBoundingClientRect().width,
height: nextSizes.afterSize
};
onResizing?.({
beforePane: beforePaneSizes,
afterPane: afterPaneSizes
});
beforeRef?.current?.onResizing?.(beforePaneSizes);
afterRef?.current?.onResizing?.(afterPaneSizes);
beforePane.style.height = beforeHeightString;
afterPane.style.height = afterHeightString;
}
setHorizontalSize();
};
const handleStart = (event) => {
event.preventDefault();
event.stopPropagation();
document.body.style.userSelect = "none";
document.body.style.webkitUserSelect = "none";
if (event.type === "mousedown") {
document.addEventListener("mousemove", handleMove);
document.addEventListener("mouseup", handleMouseUp);
}
if (event.type === "touchstart") {
document.addEventListener("touchmove", handleMove);
document.addEventListener("touchend", handleTouchEnd);
}
onResizeStart?.();
beforeRef?.current?.onResizeStart?.();
afterRef?.current?.onResizeStart?.();
document.body.style.cursor = (orientation === "vertical" ? cursorVertical : cursorHorizontal) ?? "";
};
const handleMove = (event) => {
if (!beforeRef?.current || !afterRef?.current) {
return;
}
event.preventDefault();
event.stopPropagation();
const computedStyle = window.getComputedStyle(containerRef.current);
if (orientation === "vertical") {
const size2 = parseFloat(computedStyle.getPropertyValue("width"));
const clientX = "clientX" in event ? event.clientX : event.touches[0].clientX;
const deltaX = clientX - containerRef.current.getBoundingClientRect().left - size2 / 2;
return processVerticalSize(deltaX);
}
if (orientation === "horizontal") {
const size2 = parseFloat(computedStyle.getPropertyValue("height"));
const clientY = "clientY" in event ? event.clientY : event.touches[0].clientY;
const deltaY = clientY - containerRef.current.getBoundingClientRect().top - size2 / 2;
return processHorizontalSize(deltaY);
}
};
const handleMouseUp = () => {
if (!beforeRef?.current || !afterRef?.current) {
return;
}
document.body.style.userSelect = "initial";
document.body.style.webkitUserSelect = "initial";
document.removeEventListener("mousemove", handleMove);
document.removeEventListener("mouseup", handleMouseUp);
document.body.style.cursor = "initial";
const beforePane = beforeRef.current.splitPane;
const afterPane = afterRef.current.splitPane;
const beforePaneSizes = {
width: beforePane.getBoundingClientRect().width,
height: beforePane.getBoundingClientRect().height
};
const afterPaneSizes = {
width: afterPane.getBoundingClientRect().width,
height: afterPane.getBoundingClientRect().height
};
onResizeEnd?.({
beforePane: beforePaneSizes,
afterPane: afterPaneSizes
});
beforeRef?.current?.onResizeEnd?.(beforePaneSizes);
afterRef?.current?.onResizeEnd?.(afterPaneSizes);
};
const handleTouchEnd = () => {
if (!beforeRef?.current || !afterRef?.current) {
return;
}
document.removeEventListener("touchmove", handleMove);
document.removeEventListener("touchend", handleTouchEnd);
document.body.style.cursor = "initial";
const beforePane = beforeRef.current.splitPane;
const afterPane = afterRef.current.splitPane;
const beforePaneSizes = {
width: beforePane.getBoundingClientRect().width,
height: beforePane.getBoundingClientRect().height
};
const afterPaneSizes = {
width: afterPane.getBoundingClientRect().width,
height: afterPane.getBoundingClientRect().height
};
onResizeEnd?.({
beforePane: beforePaneSizes,
afterPane: afterPaneSizes
});
beforeRef?.current?.onResizeEnd?.(beforePaneSizes);
afterRef?.current?.onResizeEnd?.(afterPaneSizes);
};
const handleKeyUp = (event) => {
if (containerRef.current !== document.activeElement) {
return;
}
const code = event.nativeEvent.code;
const arrowLeftRight = code === "ArrowRight" || code === "ArrowLeft";
const arrowUpDown = code === "ArrowUp" || code === "ArrowDown";
const delta = (event.shiftKey ? shiftStep : step) ?? 8;
if (orientation === "vertical" && arrowLeftRight) {
event.preventDefault();
event.stopPropagation();
const deltaSign = code === "ArrowRight" ? 1 : -1;
const deltaX = delta * deltaSign;
return processVerticalSize(deltaX);
}
if (orientation === "horizontal" && arrowUpDown) {
event.preventDefault();
event.stopPropagation();
const deltaSign = code === "ArrowDown" ? 1 : -1;
const deltaY = delta * deltaSign;
return processHorizontalSize(deltaY);
}
if (code === "Escape") {
event.preventDefault();
event.stopPropagation();
containerRef.current?.blur();
}
};
const handleDoubleClick = (e) => {
e.preventDefault();
e.stopPropagation();
beforeRef?.current?.resetInitialSize?.(e);
afterRef?.current?.resetInitialSize?.(e);
if (beforeRef?.current && afterRef?.current) {
const beforePane = beforeRef.current.splitPane;
const afterPane = afterRef.current.splitPane;
if (beforePane && afterPane) {
const beforePaneSizes = {
width: beforePane.getBoundingClientRect().width,
height: beforePane.getBoundingClientRect().height
};
const afterPaneSizes = {
width: afterPane.getBoundingClientRect().width,
height: afterPane.getBoundingClientRect().height
};
onResizing?.({
beforePane: beforePaneSizes,
afterPane: afterPaneSizes
});
onResizeEnd?.({
beforePane: beforePaneSizes,
afterPane: afterPaneSizes
});
beforeRef.current.notifyResizing?.(beforePaneSizes);
afterRef.current.notifyResizing?.(afterPaneSizes);
beforeRef.current.notifyResizeEnd?.(beforePaneSizes);
afterRef.current.notifyResizeEnd?.(afterPaneSizes);
}
}
onDoubleClick?.(e);
};
return /* @__PURE__ */ React.createElement(
core.UnstyledButton,
{
ref: containerRef,
mod: { orientation, snapping: isSnapping },
onMouseDown: handleStart,
onKeyDown: handleKeyUp,
onTouchStart: handleStart,
onDoubleClick: handleDoubleClick,
"aria-label": "Resize",
...getStyles("root", { variant: variant || "default" }),
...rest
}
);
});
SplitResizer.classes = SplitResizer_module;
SplitResizer.displayName = "SplitResizer";
exports.SplitResizer = SplitResizer;
exports.defaultProps = defaultProps;
//# sourceMappingURL=SplitResizer.cjs.map