@rxflow/base
Version:
BaseFlow - 核心 Flow 组件库
215 lines (207 loc) • 7.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.Scrollbar = void 0;
var _react = require("@xyflow/react");
var _react2 = require("react");
require("./index.less");
var _useScrollerOptions = require("./hooks/useScrollerOptions");
var _ahooks = require("ahooks");
var _overlayscrollbars = require("overlayscrollbars");
var _overlayscrollbarsReact = require("overlayscrollbars-react");
require("overlayscrollbars/overlayscrollbars.css");
var _classcat = _interopRequireDefault(require("classcat"));
var _jsxRuntime = require("react/jsx-runtime");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* @author: yanxianliang
* @date: 2025-08-09 21:05
* @desc: 滚动条插件
*
* 适配 reactflow 无限延伸特性,在 wheel 事件中自动调整滚动区域尺寸,操作滚动条时锁定尺寸
* Copyright (c) 2025 by yanxianliang, All Rights Reserved.
*/
_overlayscrollbars.OverlayScrollbars.plugin(_overlayscrollbars.ClickScrollPlugin);
// 取向下的最小倍数
const getFloorNumber = (current, splitSize) => {
const ratio = Math.floor(current / splitSize);
return ratio * splitSize;
};
// 取向上的最小倍数
const getCeilNumber = (current, splitSize) => {
const ratio = Math.ceil(current / splitSize);
return ratio * splitSize;
};
const padding = 50;
const Scrollbar = props => {
const {
width,
height,
pageWidth,
pageHeight,
pageBreak,
pageVisible
} = props;
const scrollerContainerRef = (0, _react2.useRef)(null);
const scrollbarRef = (0, _react2.useRef)(null);
const scrollOptionsRef = (0, _react2.useRef)(null);
// 锁定 viewport,不自动增长画布
const lockAutoViewportRef = (0, _react2.useRef)(false); // 部分正常的 onScroll 也会被拦截
const refInit = (0, _react2.useCallback)(ref => {
scrollerContainerRef.current = ref ? ref.getElement() : null;
}, []);
const onInitial = (0, _react2.useCallback)(instance => {
scrollbarRef.current = instance;
// 监听事件
bindUserActions(instance);
updateScrollBar();
}, []);
const onPointerDown = (0, _react2.useCallback)(() => {
lockAutoViewportRef.current = true;
}, []);
const onPointerUp = (0, _react2.useCallback)(() => {
lockAutoViewportRef.current = false;
}, []);
const bindUserActions = (0, _react2.useCallback)(instance => {
const {
scrollbarHorizontal,
scrollbarVertical
} = instance.elements();
scrollbarHorizontal.scrollbar.addEventListener('pointerdown', onPointerDown);
scrollbarVertical.scrollbar.addEventListener('pointerdown', onPointerDown);
document.addEventListener('pointerup', onPointerUp);
}, []);
const unBindUserActions = (0, _react2.useCallback)(instance => {
if (instance) {
const {
scrollbarHorizontal,
scrollbarVertical
} = instance.elements();
scrollbarHorizontal.scrollbar.removeEventListener('pointerdown', onPointerDown);
scrollbarVertical.scrollbar.removeEventListener('pointerdown', onPointerDown);
document.removeEventListener('pointerup', onPointerUp);
}
}, []);
(0, _react2.useEffect)(() => {
return () => {
unBindUserActions(scrollbarRef.current);
};
}, []);
const options = (0, _useScrollerOptions.useScrollerOptions)({
width,
height,
pageWidth,
pageHeight,
pageBreak,
pageVisible,
scrollerContainer: scrollerContainerRef
});
const flowInstance = (0, _react.useReactFlow)();
const nodes = (0, _react.useNodes)();
const viewport = (0, _react.useViewport)();
const bounds = (0, _react2.useMemo)(() => {
return flowInstance.getNodesBounds(nodes); // 获取可能有问题,nodeLookup为什么获取不到???应该是创建了才对。
}, [nodes]);
const scrollOptions = (0, _react2.useMemo)(() => {
if (lockAutoViewportRef.current) {
return scrollOptionsRef.current; // 不更新
}
const {
pageHeight,
pageWidth,
clientHeight,
clientWidth
} = options; // 页码宽和高
if (!pageHeight || !pageWidth) {
return undefined;
}
// 根据 bounds 计算最小的 rect 范围
const boundLeft = bounds.x - pageWidth;
const boundTop = bounds.y - pageHeight;
const boundRight = bounds.x + bounds.width + pageWidth;
const boundBottom = bounds.y + bounds.height + pageHeight;
// 根据 viewport 调整对应的上下左右 rect 范围, viewport 是不是也自动叠加 0.5 宽高
const {
x = 0,
y = 0,
zoom
} = viewport;
const viewportLeft = getFloorNumber(-x - padding, pageWidth / 2);
const viewportTop = getFloorNumber(-y - padding, pageHeight / 2);
const viewportRight = getCeilNumber(-x + zoom * clientWidth + padding, pageWidth / 2);
const viewportBottom = getCeilNumber(-y + zoom * clientHeight + padding, pageHeight / 2);
const left = Math.min(viewportLeft, boundLeft);
const top = Math.min(viewportTop, boundTop);
const bottom = Math.max(viewportBottom, boundBottom);
const right = Math.max(viewportRight, boundRight);
scrollOptionsRef.current = {
left,
top,
scrollLeft: Math.ceil(-x - left),
// 滚动条 必须要动起来,不然拖动滚动条的时候变化非常大。
scrollTop: Math.ceil(-y - top),
width: Math.ceil(right - left),
height: Math.ceil(bottom - top)
};
return scrollOptionsRef.current;
}, [options, viewport, bounds.x, bounds.y, bounds.width, bounds.height]);
const updateScrollBar = (0, _ahooks.useMemoizedFn)(() => {
if (scrollerContainerRef.current && scrollOptions && scrollbarRef.current) {
const {
scrollOffsetElement
} = scrollbarRef.current.elements();
scrollOffsetElement.scrollTo({
// behavior: 'smooth',
left: scrollOptions.scrollLeft,
top: scrollOptions.scrollTop
});
}
});
(0, _react2.useLayoutEffect)(() => {
updateScrollBar();
}, [scrollOptions]);
// 从 viewport 触发的滚动事件不应该是
const onScroll = (0, _ahooks.useMemoizedFn)((scroller, ev) => {
if (scrollOptions) {
const {
scrollTop,
scrollLeft
} = ev.target;
const x = -(scrollLeft + scrollOptions.left);
const y = -(scrollTop + scrollOptions.top);
// 计算的 viewport 不对
const _viewport = flowInstance.getViewport();
if (Math.abs(_viewport.x - x) >= 1 || Math.abs(_viewport.y - y) >= 1) {
flowInstance.setViewport({
x,
y,
zoom: _viewport.zoom
});
}
}
});
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_overlayscrollbarsReact.OverlayScrollbarsComponent, {
className: (0, _classcat.default)(["overlayscrollbars-react", 'rxflow-plugin-scrollbar']),
ref: refInit,
options: {
scrollbars: {
clickScroll: true,
// autoHide: 'leave',
theme: 'os-theme-dark'
}
},
events: {
scroll: onScroll,
initialized: onInitial
},
defer: true,
children: scrollOptions ? /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
style: {
width: scrollOptions.width,
height: scrollOptions.height
}
}) : null
});
};
exports.Scrollbar = Scrollbar;