You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
139 lines
4.1 KiB
JavaScript
139 lines
4.1 KiB
JavaScript
"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 (
|
|
<div ref={boundsRef} className={withTheme("uilibComponent", "paneSet", `direction-${direction}`)} style={style}>
|
|
{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
|
|
}
|
|
};
|
|
})}
|
|
</div>
|
|
);
|
|
};
|