@indevstudio/react-split-panel
Version:
React split-pane component for TypeScript and React 17+
590 lines (523 loc) • 19.3 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
var React = require('react');
var React__default = _interopDefault(React);
var EventListener = require('@indevstudio/react-event-listener');
var EventListener__default = _interopDefault(EventListener);
var styled = _interopDefault(require('@emotion/styled'));
var isEqual = _interopDefault(require('lodash/isEqual'));
var DEFAULT_PANE_SIZE = '1';
var DEFAULT_PANE_MIN_SIZE = '0';
var DEFAULT_PANE_MAX_SIZE = '100%';
function getUnit(size) {
if (size.endsWith('px')) {
return 'px';
}
if (size.endsWith('%')) {
return '%';
}
return 'ratio';
}
var convertSizeToCssValue = function convertSizeToCssValue(value, resizersSize) {
if (getUnit(value) !== '%') {
return value;
}
if (!resizersSize) {
return value;
}
var idx = value.search('%');
var percent = +value.slice(0, idx) / 100;
if (percent === 0) {
return value;
}
return "calc(" + value + " - " + resizersSize + "px*" + percent + ")";
};
var convertToUnit = function convertToUnit(size, unit, containerSize) {
switch (unit) {
case '%':
return (size / containerSize * 100).toFixed(2) + "%";
case 'px':
return size.toFixed(2) + "px";
case 'ratio':
return (size * 100).toFixed(0);
}
throw new Error('unit out of bound!');
};
var removeNullChildren = function removeNullChildren(children) {
return React__default.Children.toArray(children).filter(function (c) {
return c;
});
};
var getPanePropSize = function getPanePropSize(children) {
return removeNullChildren(children).map(function (child) {
var _child$props$size;
var value = (_child$props$size = child['props']['size']) != null ? _child$props$size : child.props['initialSize'];
return value != null ? value : DEFAULT_PANE_SIZE;
});
};
var getPanePropMinMaxSize = function getPanePropMinMaxSize(children, key) {
return removeNullChildren(children).map(function (child) {
var value = child.props[key];
if (value === undefined) {
return key === 'maxSize' ? DEFAULT_PANE_MAX_SIZE : DEFAULT_PANE_MIN_SIZE;
}
return value;
});
};
var getResizersSize = function getResizersSize(children, resizerSize) {
if (!children) {
return 0;
}
return (React__default.Children.count(children) - 1) * resizerSize;
};
var getPaneDimensions = function getPaneDimensions(paneElements) {
return paneElements.filter(function (el) {
return el;
}).map(function (el) {
return el.getBoundingClientRect();
});
};
var getDimensionsSnapshot = function getDimensionsSnapshot(children, resizerSize, split, paneElements, splitPane) {
var paneDimensions = getPaneDimensions(paneElements);
var splitPaneDimensions = splitPane.getBoundingClientRect();
var minSizes = getPanePropMinMaxSize(children, 'minSize');
var maxSizes = getPanePropMinMaxSize(children, 'maxSize');
var notNullChildren = removeNullChildren(children);
var resizersSize = getResizersSize(notNullChildren, resizerSize);
var splitPaneSizePx = split === 'vertical' ? splitPaneDimensions.width - resizersSize : splitPaneDimensions.height - resizersSize;
var minSizesPx = minSizes.map(function (s) {
return convert(s, splitPaneSizePx);
});
var maxSizesPx = maxSizes.map(function (s) {
return convert(s, splitPaneSizePx);
});
var sizesPx = paneDimensions.map(function (d) {
return split === 'vertical' ? d.width : d.height;
});
return {
resizersSize: resizersSize,
paneDimensions: paneDimensions,
splitPaneSizePx: splitPaneSizePx,
minSizesPx: minSizesPx,
maxSizesPx: maxSizesPx,
sizesPx: sizesPx
};
};
var toPx = function toPx(value, unit, size) {
switch (unit) {
case '%':
{
return +(size * value / 100).toFixed(2);
}
default:
{
return +value;
}
}
};
var convert = function convert(str, size) {
var tokens = str.match(/([0-9]+)([px|%]*)/);
if (!tokens) {
return 0;
}
var value = tokens[1];
var unit = tokens[2];
return toPx(+value, unit, size);
};
var calculateSizes = function calculateSizes(props) {
var split = props.split,
resizerIndex = props.resizerIndex,
dimensionsSnapshot = props.dimensionsSnapshot,
startClientX = props.startClientX,
startClientY = props.startClientY,
clientX = props.clientX,
clientY = props.clientY,
sizes = props.sizes;
var sizesPx = dimensionsSnapshot.sizesPx,
minSizesPx = dimensionsSnapshot.minSizesPx,
maxSizesPx = dimensionsSnapshot.maxSizesPx,
splitPaneSizePx = dimensionsSnapshot.splitPaneSizePx,
paneDimensions = dimensionsSnapshot.paneDimensions;
var sizeDim = split === 'vertical' ? 'width' : 'height';
var primary = paneDimensions[resizerIndex];
var secondary = paneDimensions[resizerIndex + 1];
var maxSize = primary[sizeDim] + secondary[sizeDim];
var primaryMinSizePx = minSizesPx[resizerIndex];
var secondaryMinSizePx = minSizesPx[resizerIndex + 1];
var primaryMaxSizePx = Math.min(maxSizesPx[resizerIndex], maxSize);
var secondaryMaxSizePx = Math.min(maxSizesPx[resizerIndex + 1], maxSize);
var moveOffset = split === 'vertical' ? startClientX - clientX : startClientY - clientY;
var primarySizePx = primary[sizeDim] - moveOffset;
var secondarySizePx = secondary[sizeDim] + moveOffset;
var primaryHasReachedLimit = false;
var secondaryHasReachedLimit = false;
if (primarySizePx < primaryMinSizePx) {
primarySizePx = primaryMinSizePx;
primaryHasReachedLimit = true;
} else if (primarySizePx > primaryMaxSizePx) {
primarySizePx = primaryMaxSizePx;
primaryHasReachedLimit = true;
}
if (secondarySizePx < secondaryMinSizePx) {
secondarySizePx = secondaryMinSizePx;
secondaryHasReachedLimit = true;
} else if (secondarySizePx > secondaryMaxSizePx) {
secondarySizePx = secondaryMaxSizePx;
secondaryHasReachedLimit = true;
}
if (primaryHasReachedLimit) {
secondarySizePx = primary[sizeDim] + secondary[sizeDim] - primarySizePx;
} else if (secondaryHasReachedLimit) {
primarySizePx = primary[sizeDim] + secondary[sizeDim] - secondarySizePx;
}
sizesPx[resizerIndex] = primarySizePx;
sizesPx[resizerIndex + 1] = secondarySizePx;
var newSizes = sizes.concat();
var updateRatio;
[primarySizePx, secondarySizePx].forEach(function (paneSize, idx) {
var unit = getUnit(sizes[resizerIndex + idx]);
if (unit !== 'ratio') {
newSizes[resizerIndex + idx] = convertToUnit(paneSize, unit, splitPaneSizePx);
} else {
updateRatio = true;
}
});
if (updateRatio) {
var ratioCount = 0;
var lastRatioIdx = 0;
newSizes = newSizes.map(function (size, idx) {
if (getUnit(size) === 'ratio') {
ratioCount++;
lastRatioIdx = idx;
return convertToUnit(sizesPx[idx], 'ratio', 0);
}
return size;
});
if (ratioCount === 1) {
newSizes[lastRatioIdx] = '1';
}
}
return newSizes;
};
var paneStyle = function paneStyle(props) {
var _ref;
var __internal = props.__internal,
initialSize = props.initialSize,
minSize = props.minSize,
maxSize = props.maxSize;
if (!__internal) {
throw new Error("__internal is null");
}
var split = __internal.split,
size = __internal.size,
resizersSize = __internal.resizersSize;
var value = (_ref = size != null ? size : initialSize) != null ? _ref : "1";
var vertical = split === 'vertical';
var styleProp = {
minSize: vertical ? 'minWidth' : 'minHeight',
maxSize: vertical ? 'maxWidth' : 'maxHeight',
size: vertical ? 'width' : 'height'
};
var style = {
display: 'flex',
outline: 'none'
};
style[styleProp.minSize] = minSize && convertSizeToCssValue(minSize, resizersSize);
style[styleProp.maxSize] = maxSize && convertSizeToCssValue(maxSize, resizersSize);
switch (getUnit(value)) {
case 'ratio':
style.flex = value;
break;
case '%':
case 'px':
style.flexGrow = 0;
style[styleProp.size] = convertSizeToCssValue(value, resizersSize);
break;
}
return style;
};
var Pane = /*#__PURE__*/React.forwardRef(function (props, ref) {
var children = props.children,
className = props.className;
var prefixedStyle = paneStyle(props);
return React__default.createElement("div", {
className: className,
style: prefixedStyle,
ref: ref
}, children);
});
function _extends() {
_extends = Object.assign || function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
return _extends.apply(this, arguments);
}
function _taggedTemplateLiteralLoose(strings, raw) {
if (!raw) {
raw = strings.slice(0);
}
strings.raw = raw;
return strings;
}
var _templateObject, _templateObject2, _templateObject3;
var Wrapper = /*#__PURE__*/styled.div(_templateObject || (_templateObject = /*#__PURE__*/_taggedTemplateLiteralLoose(["\n background: #000;\n opacity: 0.2;\n z-index: 1;\n box-sizing: border-box;\n background-clip: padding-box;\n\n :hover {\n transition: all 2s ease;\n }\n"])));
var HorizontalWrapper = /*#__PURE__*/styled(Wrapper)(_templateObject2 || (_templateObject2 = /*#__PURE__*/_taggedTemplateLiteralLoose(["\n height: 11px;\n margin: -5px 0;\n border-top: 5px solid rgba(255, 255, 255, 0);\n border-bottom: 5px solid rgba(255, 255, 255, 0);\n cursor: row-resize;\n width: 100%;\n\n :hover {\n border-top: 5px solid rgba(0, 0, 0, 0.5);\n border-bottom: 5px solid rgba(0, 0, 0, 0.5);\n }\n\n .disabled {\n cursor: not-allowed;\n }\n .disabled:hover {\n border-color: transparent;\n }\n"])));
var VerticalWrapper = /*#__PURE__*/styled(Wrapper)(_templateObject3 || (_templateObject3 = /*#__PURE__*/_taggedTemplateLiteralLoose(["\n width: 11px;\n margin: 0 -5px;\n border-left: 5px solid rgba(255, 255, 255, 0);\n border-right: 5px solid rgba(255, 255, 255, 0);\n cursor: col-resize;\n\n :hover {\n border-left: 5px solid rgba(0, 0, 0, 0.5);\n border-right: 5px solid rgba(0, 0, 0, 0.5);\n }\n .disabled {\n cursor: not-allowed;\n }\n .disabled:hover {\n border-color: transparent;\n }\n"])));
var Resizer = function Resizer(props) {
var index = props.index,
split = props.split,
onMouseDown = props.onMouseDown,
onTouchStart = props.onTouchStart,
style = props.style,
className = props.className;
var onMouseDownHandler = React.useCallback(function (event) {
if (!onMouseDown) {
return;
}
event.preventDefault();
onMouseDown(event, index);
}, []);
var onTouchEndHandler = React.useCallback(function (event) {
event.preventDefault();
}, []);
var onTouchStartHandler = React.useCallback(function (event) {
event.preventDefault();
onTouchStart(event, index);
}, []);
var wrapper = {
'data-attribute': split,
'data-type': 'Resizer',
onMouseDown: onMouseDownHandler,
onTouchEnd: onTouchEndHandler,
onTouchStart: onTouchStartHandler,
className: className,
style: style
};
return split === 'vertical' ? React__default.createElement(VerticalWrapper, Object.assign({}, wrapper)) : React__default.createElement(HorizontalWrapper, Object.assign({}, wrapper));
};
var SplitPanelChildren = function SplitPanelChildren(props) {
var children = props.children,
sizes = props.sizes,
resizerSize = props.resizerSize,
split = props.split,
paneElementsRef = props.paneElementsRef,
onResizerMouseDown = props.onResizerMouseDown,
onResizerTouchStart = props.onResizerTouchStart,
resizerStyle = props.resizerStyle,
resizerClassName = props.resizerClassName;
var notNullChildren = React.useMemo(function () {
return removeNullChildren(children);
}, [children]);
var paneStyleProps = React.useMemo(function () {
var resizersSize = getResizersSize(notNullChildren, resizerSize);
return notNullChildren.map(function (child, idx) {
return _extends({}, child.props, {
key: "Pane-" + idx,
'data-type': 'Pane',
ref: function ref(el) {
return paneElementsRef.current[idx] = el;
},
__internal: {
split: split,
resizersSize: resizersSize,
size: sizes[idx]
}
});
});
}, [split, resizerSize, sizes, paneElementsRef, notNullChildren]);
var resizerProps = {
split: split,
onMouseDown: onResizerMouseDown,
onTouchStart: onResizerTouchStart,
style: resizerStyle,
className: resizerClassName
};
return React__default.createElement(React__default.Fragment, null, paneStyleProps.map(function (pane, idx) {
var child = notNullChildren[idx];
return React__default.createElement(React__default.Fragment, {
key: "idx"
}, idx !== 0 && React__default.createElement(Resizer, Object.assign({
index: idx - 1,
key: "Resizer-" + (idx - 1)
}, resizerProps)), React__default.createElement(React__default.Fragment, {
key: "Pane-" + idx
}, child.type === Pane ? React.cloneElement(child, pane) : React__default.createElement(Pane, Object.assign({}, pane), child)));
}));
};
var ColumnStyle = /*#__PURE__*/styled.div({
display: 'flex',
height: '100%',
flexDirection: 'column',
flex: 1,
outline: 'none',
overflow: 'hidden',
userSelect: 'text'
});
var RowStyle = /*#__PURE__*/styled.div({
display: 'flex',
height: '100%',
flexDirection: 'row',
flex: 1,
outline: 'none',
overflow: 'hidden',
userSelect: 'text'
});
var SplitPanel = function SplitPanel(props) {
var children = props.children,
onResizeEnd = props.onResizeEnd,
disallowResize = props.disallowResize,
onResizeStart = props.onResizeStart,
split = props.split,
resizerSize = props.resizerSize,
onChange = props.onChange,
className = props.className,
style = props.style,
resizerClassName = props.resizerClassName,
resizerStyle = props.resizerStyle;
var paneElementsRef = React.useRef([]);
React.useEffect(function () {
paneElementsRef.current = paneElementsRef.current.slice(0, React__default.Children.count(children));
}, [children]);
var splitPaneRef = React.useRef(null);
var _useState = React.useState([]),
sizes = _useState[0],
setSizes = _useState[1];
var _useState2 = React.useState(false),
enableListen = _useState2[0],
setEnableListen = _useState2[1];
var _useState3 = React.useState(0),
resizerIndex = _useState3[0],
setResizerIndex = _useState3[1];
var _useState4 = React.useState(),
dimensionsSnapshot = _useState4[0],
setDimensionsSnapshot = _useState4[1];
var _useState5 = React.useState(0),
startClientX = _useState5[0],
setStartClientX = _useState5[1];
var _useState6 = React.useState(0),
startClientY = _useState6[0],
setStartClientY = _useState6[1];
var onDown = React.useCallback(function (resizerIndex, clientX, clientY) {
if (disallowResize || !splitPaneRef.current) {
return;
}
setResizerIndex(resizerIndex);
var snapshot = getDimensionsSnapshot(children, resizerSize, split, paneElementsRef.current, splitPaneRef.current);
setDimensionsSnapshot(snapshot);
setStartClientX(clientX);
setStartClientY(clientY);
setEnableListen(true);
if (onResizeStart) {
onResizeStart();
}
}, [children, resizerSize, split, paneElementsRef.current, splitPaneRef.current, disallowResize, onResizeStart, setDimensionsSnapshot, setStartClientX, setStartClientY, setEnableListen]);
var onMove = React.useCallback(function (clientX, clientY) {
if (!dimensionsSnapshot) {
return;
}
var newSizes = calculateSizes({
clientX: clientX,
clientY: clientY,
startClientX: startClientX,
dimensionsSnapshot: dimensionsSnapshot,
resizerIndex: resizerIndex,
sizes: sizes,
split: split,
startClientY: startClientY
});
onChange && onChange(newSizes);
setSizes(newSizes);
}, [split, onChange, startClientX, dimensionsSnapshot, resizerIndex, sizes, split, startClientY, setSizes]);
var onMouseUp = React.useCallback(function (event) {
event.preventDefault();
setEnableListen(false);
if (onResizeEnd) {
onResizeEnd(sizes);
}
}, [sizes, setEnableListen, onResizeEnd]);
var onMouseMove = React.useCallback(function (event) {
event.preventDefault();
onMove(event.clientX, event.clientY);
}, [onMove]);
var onTouchMove = React.useCallback(function (event) {
event.preventDefault();
var _event$touches$ = event.touches[0],
clientX = _event$touches$.clientX,
clientY = _event$touches$.clientY;
onMove(clientX, clientY);
}, [onMove]);
var onResizerMouseDown = React.useCallback(function (event, resizerIndex) {
if (event.button !== 0) {
return;
}
event.preventDefault();
onDown(resizerIndex, event.clientX, event.clientY);
}, [onDown]);
var onResizerTouchStart = React.useCallback(function (event, resizerIndex) {
event.preventDefault();
var _event$touches$2 = event.touches[0],
clientX = _event$touches$2.clientX,
clientY = _event$touches$2.clientY;
onDown(resizerIndex, clientX, clientY);
}, [onDown]);
React.useEffect(function () {
var newSizes = getPanePropSize(children);
if (!isEqual(sizes, newSizes)) {
setSizes(newSizes);
}
}, [setSizes, getPanePropSize, children]);
var splitPaneChildren = {
onResizerMouseDown: onResizerMouseDown,
onResizerTouchStart: onResizerTouchStart,
paneElementsRef: paneElementsRef,
resizerSize: resizerSize,
sizes: sizes,
split: split,
resizerClassName: resizerClassName,
resizerStyle: resizerStyle
};
var StyleComponent = split === 'vertical' ? RowStyle : ColumnStyle;
return React__default.createElement(React__default.Fragment, null, enableListen && React__default.createElement(EventListener__default, {
target: document,
onMouseUp: EventListener.withOptions(onMouseUp, {
passive: false
}),
onMouseMove: EventListener.withOptions(onMouseMove, {
passive: false
}),
onTouchMove: EventListener.withOptions(onTouchMove, {
passive: false
}),
onTouchEnd: EventListener.withOptions(onMouseUp, {
passive: false
}),
onTouchCancel: EventListener.withOptions(onMouseUp, {
passive: false
})
}), React__default.createElement(StyleComponent, {
className: className,
style: style,
"data-type": "SplitPane",
"data-split": split,
ref: splitPaneRef
}, React__default.createElement(SplitPanelChildren, Object.assign({}, splitPaneChildren), children)));
};
var useSplitPanel = function useSplitPanel(props) {
return _extends({
resizerSize: 1,
split: 'vertical'
}, props);
};
exports.Pane = Pane;
exports.SplitPanel = SplitPanel;
exports.useSplitPanel = useSplitPanel;
//# sourceMappingURL=react-split-panel.cjs.development.js.map