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.

469 lines (465 loc) 16 kB
'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