"use strict"; const React = require("react"); const useMeasure = require("react-use-measure"); const syncpipe = require("syncpipe"); const defaultValue = require("default-value"); const clamp = require("clamp"); const useTheme = require("../../util/themeable"); const defaultStyle = require("./style.css"); const childrenWithProps = require("../../util/children-with-props"); const generateGridTemplateString = require("../../util/generate-grid-template-string"); const useIndexedState = require("../../util/use-indexed-state"); const sum = require("../../util/sum"); const MINIMUM_PANE_SIZE = 16; // FIXME: Logic to constrain resizing when increasing a pane's size further would make the paneset exceed its size boundaries // FIXME: Disable selection globally during dragging function selectSmallestSize(sizes) { console.log("sizes", sizes); if (sizes.some((size) => size != null && isNaN(size))) { throw new Error(`NaN encountered!`); } return syncpipe(sizes, [ (_) => _.filter((size) => size != null), (_) => (_.length > 0) ? Math.min(... _) : undefined ]); } module.exports = function PaneSet({ vertical, horizontal, children }) { let { withTheme } = useTheme({ control: "paneSet", defaultStyle }); let [ boundsRef, bounds ] = useMeasure({ debounce: 10 }); let [ newSizes, setNewSize, resetNewSizes ] = useIndexedState(); // FIXME: How to deal with new children? Reset all newSizes? let direction = (vertical === true) ? "vertical" : "horizontal"; let ownSize = (direction === "vertical") ? bounds.height : bounds.width; let childrenArray = React.Children.toArray(children); let minimumSizes = React.useMemo(() => { return childrenArray.map((child) => { return selectSmallestSize([ child.props.minimumSize, MINIMUM_PANE_SIZE ]); }); }); // FIXME: Detect when this fails let autoPaneMinimumSize = syncpipe(childrenArray, [ (_) => _.findIndex((child) => child.resizable !== true), (_) => minimumSizes[_] ]); let maximumSizes = React.useMemo(() => { let paneSizes = childrenArray.map((child, i) => { // FIXME: Require initialSize when resizable return defaultValue(newSizes[i], child.props.initialSize); }); return childrenArray.map((_, ownIndex) => { let otherPaneSizes = syncpipe(paneSizes, [ (_) => _.filter((size, i) => size != null && i !== ownIndex), (_) => sum(_) ]); return clamp(ownSize - otherPaneSizes - autoPaneMinimumSize, 0, Infinity); }); }, [ childrenArray, newSizes, ownSize ]); let autoSizeSeen = 0; // NOTE: Not using React.Children.map because it will ignore null return values let handleSides = childrenArray.map((child) => { if (child.props.resizable === true) { if (autoSizeSeen > 0) { return (direction === "vertical") ? "n" // north, top : "w"; // west, left } else { return (direction === "vertical") ? "s" // south, bottom : "e"; // east, right } } else { autoSizeSeen += 1; return null; } }); let gridTemplateProperty = (direction === "vertical") ? "gridTemplateRows" : "gridTemplateColumns"; let gridTemplateString = syncpipe(handleSides, [ (_) => _.map((handle) => handle != null ? "auto" : 1), (_) => generateGridTemplateString(_) ]); let style = { [gridTemplateProperty]: gridTemplateString }; if (autoSizeSeen !== 1) { // We don't allow *multiple* auto-sized panes, to avoid weird behaviour where the auto-layouted panes may not end up exactly where the resize handle is throw new Error(`Exactly one (center) pane must be non-resizable!`); } return (
{childrenWithProps(children, (child, i) => { let handleSide = handleSides[i]; return { onResized: (newSize) => { setNewSize(i, newSize); }, maximumSize: selectSmallestSize([ child.props.maximumSize, maximumSizes[i] ]), // Note that we shouldn't override an explicitly user-configured handleSide ... (handleSide == null) ? {} : { handleSide: defaultValue(child.props.handleSide, handleSide), direction: direction } }; })}
); };