UNPKG

preact-arco-design

Version:

Arco Design React UI Library.

431 lines (359 loc) 14.5 kB
var __read = this && this.__read || function (o, n) { var m = typeof Symbol === "function" && o[Symbol.iterator]; if (!m) return o; var i = m.call(o), r, ar = [], e; try { while ((n === void 0 || n-- > 0) && !(r = i.next()).done) { ar.push(r.value); } } catch (error) { e = { error: error }; } finally { try { if (r && !r.done && (m = i["return"])) m.call(i); } finally { if (e) throw e.error; } } return ar; }; var __spreadArray = this && this.__spreadArray || function (to, from, pack) { if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { if (ar || !(i in from)) { if (!ar) ar = Array.prototype.slice.call(from, 0, i); ar[i] = from[i]; } } return to.concat(ar || Array.prototype.slice.call(from)); }; import React, { useState, useEffect, useRef, useContext, forwardRef, useImperativeHandle } from "preact/compat"; import { ConfigContext } from "../ConfigProvider"; import cs from "../_util/classNames"; import { isFunction, isNumber, isUndefined, isObject, isString } from "../_util/is"; import ResizeTrigger from "./resize-trigger"; import { on, off } from "../_util/dom"; var DIRECTION_HORIZONTAL = 'horizontal'; var DIRECTION_VERTICAL = 'vertical'; function SplitGroup(props, ref) { var _a, _b; var panes = props.panes, style = props.style, className = props.className, _c = props.component, component = _c === void 0 ? 'div' : _c, _d = props.direction, direction = _d === void 0 ? 'horizontal' : _d, icon = props.icon; var _e = useContext(ConfigContext), getPrefixCls = _e.getPrefixCls, rtl = _e.rtl; var defaultOffset = 1 / panes.length; var wrapperRef = useRef(); var recordRef = useRef(new Array(panes.length).fill({ moving: false, startOffset: 0, startPosition: 0 })); var paneContainers = useRef([]); var movingIndex = useRef(0); var prevOffsets = useRef([]); var _f = __read(useState(new Array(panes.length).fill(defaultOffset)), 2), offsets = _f[0], setOffsets = _f[1]; var _g = __read(useState(false), 2), isMoving = _g[0], setIsMoving = _g[1]; var _h = __read(useState(new Array(panes.length).fill(0)), 2), triggerSize = _h[0], setTriggerSize = _h[1]; var _j = __read(useState(new Array(Math.max(panes.length - 1, 0)).fill({ prev: false, next: false })), 2), collapsedStatus = _j[0], setCollapsedStatus = _j[1]; var prefixCls = getPrefixCls('resizebox-split-group'); var isHorizontal = direction === DIRECTION_HORIZONTAL; var rtlReverse = isHorizontal && rtl; var isTriggerHorizontal = !isHorizontal; var classNames = cs(prefixCls, "".concat(prefixCls, "-").concat(isHorizontal ? DIRECTION_HORIZONTAL : DIRECTION_VERTICAL), (_a = {}, _a["".concat(prefixCls, "-moving")] = isMoving, _a), (_b = {}, _b["".concat(prefixCls, "-rtl")] = rtl, _b), className); var Tag = component; // 获取初始的 offset, 将传入的size 都转为像素值。 var getInitialOffsets = function getInitialOffsets() { var newOffsets = []; panes.forEach(function (pane) { var size = pane.size; if (!isUndefined(size)) { newOffsets.push(formatSize(size)); } else { newOffsets.push(undefined); } }); // 剩余的空间均分给没有设置 size 的面板 var noSizeArr = newOffsets.filter(function (size) { return !size; }); var remainPercent = 1 - newOffsets.reduce(function (a, b) { var formatA = a || 0; var formatB = b || 0; return formatA + formatB; }, 0); var averagePercent = remainPercent / noSizeArr.length; newOffsets = newOffsets.map(function (size) { if (!isUndefined(size)) { return size; } return averagePercent; }); return newOffsets; }; // 计算每一个面板的占位像素,需要减去前面跟当前伸缩杆的宽度 var getPaneSize = function getPaneSize(index) { var prevTriggerSize = triggerSize[index - 1] || 0; var currentTriggerSize = triggerSize[index]; var baseVal = offsets[index] * 100; var unit = '%'; return "calc(".concat(baseVal).concat(unit, " - ").concat((prevTriggerSize + currentTriggerSize) / 2, "px)"); }; // 入参 百分比/像素值 => 全部转化为百分比(响应式) function formatSize(size) { var totalPX = isHorizontal ? wrapperRef.current.offsetWidth : wrapperRef.current.offsetHeight; if (!size || isNumber(size) && size < 0) { return 0; } var percent = isString(size) ? parseFloat(size) / totalPX : size; return Math.min(percent, 1); } // 计算阈值,因为伸缩杆会影响到当前面板 跟 下一个面板。所以同时计算两个阈值。 var getMinAndMax = function getMinAndMax(index) { var next = Math.min(index + 1, panes.length - 1); var totalOffset = offsets[index] + offsets[next]; var currentMin = formatSize(panes[index].min) || 0; var currentMax = formatSize(panes[index].max) || totalOffset; var nextMin = formatSize(panes[next].min) || 0; var nextMax = formatSize(panes[next].max) || totalOffset; // min 的优先级高于 max currentMax = Math.min(totalOffset - nextMin, currentMax); nextMax = Math.min(totalOffset - currentMin, nextMax); return { currentMin: currentMin, currentMax: currentMax, nextMin: nextMin, nextMax: nextMax }; }; // 拖拽时,获取新的占位距离。影响当前面板跟下一个面板的占位值。 var getNewOffsets = function getNewOffsets(startOffset, startPosition, currentPosition) { var current = movingIndex.current; var next = current + 1; var newOffsets = __spreadArray([], __read(offsets), false); var ratio = rtlReverse ? -1 : 1; var currentPercent = offsets[current]; var nextPercent = offsets[next]; var totalPercent = currentPercent + nextPercent; var _a = getMinAndMax(current), minOffset = _a.currentMin, maxOffset = _a.currentMax; var moveOffset = startOffset + formatSize("".concat((currentPosition - startPosition) * ratio, "px")); moveOffset = Math.max(minOffset, moveOffset); moveOffset = Math.min(maxOffset, moveOffset); newOffsets[current] = moveOffset; // 保证 totalOffset = nextOffset + currentOffset 不变。 newOffsets[next] = totalPercent - moveOffset; return newOffsets; }; function onTriggerResize(e, index) { var contentRect = e[0].contentRect; var currentSize = contentRect[isTriggerHorizontal ? 'height' : 'width']; var newTriggerSize = __spreadArray([], __read(triggerSize), false); newTriggerSize[index] = currentSize; setTriggerSize(newTriggerSize); } // 判断快速收缩按钮是否展示 var getCollapsedConfig = function getCollapsedConfig(index) { var collapsible = panes[index].collapsible; if (!isObject(collapsible)) { collapsible = !collapsible ? {} : { prev: true, next: true }; } var prev = collapsible.prev, next = collapsible.next; if (!prev && !next) { return {}; } if (!collapsedStatus[index]) { return {}; } // 传入了prev的配置,或者 没有传入 prev 的配置,但是已经处于向下收缩完毕状态 var hasPrev = !!prev || !prev && collapsedStatus[index].next; // 传入了next的配置,或者 没有传入 next 的配置,但是已经处于向上收缩完毕状态 var hasNext = !!next || !next && collapsedStatus[index].prev; return { hasPrev: hasPrev, hasNext: hasNext }; }; // 移动开始,记录初始值,绑定移动事件 function onTriggerMouseDown(e, index) { props.onMovingStart && props.onMovingStart(index); movingIndex.current = index; var currentRecord = recordRef.current[index]; currentRecord.moving = true; currentRecord.startOffset = offsets[index]; currentRecord.startPosition = isHorizontal ? e.pageX : e.pageY; setIsMoving(true); on(window, 'mousemove', moving); on(window, 'touchmove', moving); on(window, 'mouseup', moveEnd); on(window, 'touchend', moveEnd); on(window, 'contextmenu', moveEnd); document.body.style.cursor = isTriggerHorizontal ? 'row-resize' : 'col-resize'; } // 移动中,更新 当前面板跟下一个面板 占位大小 function moving(e) { var index = movingIndex.current; var currentRecord = recordRef.current[index]; var totalPX = isHorizontal ? wrapperRef.current.offsetWidth : wrapperRef.current.offsetHeight; if (currentRecord.moving) { var newOffsets = getNewOffsets(currentRecord.startOffset, currentRecord.startPosition, isHorizontal ? e.pageX : e.pageY); setOffsets(newOffsets); prevOffsets.current = newOffsets; props.onMoving && props.onMoving(e, newOffsets.map(function (value) { return "".concat(value * totalPX, "px"); }), index); } } // 移动结束,解除事件绑定 function moveEnd() { var index = movingIndex.current; recordRef.current[index].moving = false; setIsMoving(false); off(window, 'mousemove', moving); off(window, 'touchmove', moving); off(window, 'mouseup', moveEnd); off(window, 'touchend', moveEnd); off(window, 'contextmenu', moveEnd); document.body.style.cursor = 'default'; props.onMovingEnd && props.onMovingEnd(index); } // 点击快速收缩按钮的回调。 function handleCollapsed(e, index, status, callback) { var next = index + 1; var newOffset = __spreadArray([], __read(offsets), false); var currentOffset = offsets[index]; var nextOffset = offsets[next]; var totalOffset = currentOffset + nextOffset; var totalPX = isHorizontal ? wrapperRef.current.offsetWidth : wrapperRef.current.offsetHeight; var _a = getMinAndMax(index), currentMin = _a.currentMin, nextMin = _a.nextMin; // 取消收缩时,应该重置为上一个状态。所以从preOffsets里拿值 var newCurrentOffset = prevOffsets.current[index]; var newNextOffset = prevOffsets.current[next]; // 当前面板的收缩状态。 var collapsed = collapsedStatus[index][status]; // 点击向上收缩按钮。收缩态是:currentPane = currentMin; if (status === 'prev') { // 如果下一个面板不是在收缩状态 或者 下一个面板被手动拖拽到收缩状态 if (nextOffset !== nextMin || newNextOffset === nextMin) { // 当前面板收缩。 newCurrentOffset = currentMin; newNextOffset = totalOffset - currentMin; collapsed = true; } // 点击向下收缩按钮 } else if (currentOffset !== currentMin || newCurrentOffset === currentMin) { newCurrentOffset = totalOffset - nextMin; newNextOffset = nextMin; collapsed = true; } newOffset[index] = newCurrentOffset; newOffset[next] = newNextOffset; props.onMoving && props.onMoving(e, newOffset.map(function (value) { return "".concat(value * totalPX, "px"); }), index); props.onMovingEnd && props.onMovingEnd(index); setOffsets(newOffset); if (isFunction(callback)) { callback(e, index, status, collapsed); } } useEffect(function () { var offsets = getInitialOffsets(); setOffsets(offsets); prevOffsets.current = offsets; }, [JSON.stringify(panes.map(function (item) { return item.size; }))]); useImperativeHandle(ref, function () { return wrapperRef.current; }, []); useEffect(function () { var newCollapsedStatus = []; offsets.forEach(function (offset, index) { var currentCollapsedStatus = { prev: false, next: false }; var next = index + 1; var _a = getMinAndMax(index), currentMin = _a.currentMin, nextMin = _a.nextMin; // 当 offsets 变化时,更新各个面板的 collapsed 状态 if (offset === currentMin) { currentCollapsedStatus.prev = true; } else if (offsets[next] === nextMin) { currentCollapsedStatus.next = true; } newCollapsedStatus.push(currentCollapsedStatus); }); setCollapsedStatus(newCollapsedStatus); }, [offsets]); return React.createElement(Tag, { style: style, className: classNames, ref: wrapperRef }, panes.map(function (pane, index) { var content = pane.content, disabled = pane.disabled, trigger = pane.trigger, _a = pane.resizable, resizable = _a === void 0 ? true : _a, _b = pane.collapsible, collapsible = _b === void 0 ? {} : _b; var _c = getCollapsedConfig(index), hasPrev = _c.hasPrev, hasNext = _c.hasNext; var prevConfig = isObject(collapsible) && isObject(collapsible.prev) ? collapsible.prev : {}; var nextConfig = isObject(collapsible) && isObject(collapsible.next) ? collapsible.next : {}; return React.createElement(React.Fragment, { key: index }, React.createElement("div", { className: "".concat(prefixCls, "-pane"), style: { flexBasis: getPaneSize(index) }, ref: function ref(el) { return paneContainers.current[index] = el; } }, content), !disabled && index !== panes.length - 1 && React.createElement(ResizeTrigger, { className: "".concat(prefixCls, "-trigger"), direction: isTriggerHorizontal ? DIRECTION_HORIZONTAL : DIRECTION_VERTICAL, icon: icon, onResize: function onResize(e) { return onTriggerResize(e, index); }, onMouseDown: function onMouseDown(e) { return onTriggerMouseDown(e, index); }, collapsible: { prev: hasPrev ? { onClick: function onClick(e) { return handleCollapsed(e, index, 'prev', prevConfig.onClick); }, icon: prevConfig.icon, collapsed: collapsedStatus[index].prev } : undefined, next: hasNext ? { onClick: function onClick(e) { return handleCollapsed(e, index, 'next', nextConfig.onClick); }, icon: nextConfig.icon, collapsed: collapsedStatus[index].next } : undefined }, resizable: resizable, renderChildren: trigger })); })); } var SplitGroupComponent = forwardRef(SplitGroup); SplitGroupComponent.displayName = 'ResizeBoxSplitGroup'; export default SplitGroupComponent;