linkmore-design
Version:
🌈 🚀lm组件库。🚀
527 lines (507 loc) • 14 kB
JavaScript
"use strict";
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.scrollTo = exports.VList = void 0;
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var _react = _interopRequireWildcard(require("react"));
var _lodash = require("lodash");
var _reactResizable = require("react-resizable");
var _context = _interopRequireDefault(require("./context"));
// ===============reducer ============== //
const initialState = {
// 行高度
rowHeight: 0,
// 当前的scrollTop
curScrollTop: 0,
// 总行数
totalLen: 0
};
function reducer(state, action) {
const {
curScrollTop,
curScrollLeft,
rowHeight,
totalLen,
ifScrollTopClear,
isScrolling
} = action;
let stateScrollTop = state.curScrollTop;
switch (action.type) {
// 滚动条是否滚动中
case 'changeScrolling':
return {
...state,
isScrolling
};
case 'changeTrs':
return {
...state,
curScrollTop
};
// 滚动横向滚动条 即 改变渲染的列表operate
case 'changeOperate':
return {
...state,
curScrollLeft
};
// 初始化每行的高度, 表格总高度, 渲染的条数
case 'initHeight':
return {
...state,
rowHeight
};
// 更改totalLen
case 'changeTotalLen':
if (totalLen === 0) {
stateScrollTop = 0;
}
return {
...state,
totalLen,
curScrollTop: stateScrollTop
};
case 'reset':
return {
...state,
curScrollTop: ifScrollTopClear ? 0 : state.curScrollTop
};
default:
throw new Error();
}
}
// ============== 全局常量 ================== //
const DEFAULT_VID = 'vtable';
const vidMap = new Map();
// =============== context ============== //
const ScrollContext = /*#__PURE__*/(0, _react.createContext)({
dispatch: undefined,
renderLen: 1,
start: 0,
offsetStart: 0,
// =============
rowHeight: initialState.rowHeight,
totalLen: 0,
vid: DEFAULT_VID
});
const HeaderContext = /*#__PURE__*/(0, _react.createContext)();
// ============= 组件 =================== //
const HeaderWrapper = props => {
const [useKeys, setKeys] = (0, _react.useState)();
const {
width
} = (0, _react.useContext)(_context.default);
const changeKeys = () => {
setKeys(Date.now());
};
(0, _react.useEffect)(() => {
changeKeys();
}, [width]);
// console.log('每列的偏移', props.children[0].props.stickyOffsets);
return /*#__PURE__*/_react.default.createElement(HeaderContext.Provider, {
value: {
changeKeys
}
}, /*#__PURE__*/_react.default.createElement("thead", (0, _extends2.default)({
key: useKeys
}, props)));
};
const HeaderCell = props => {
const {
width,
onResize,
children,
...restProps
} = props;
const [useWidth, setWidth] = (0, _react.useState)(width || 0);
const {
changeKeys
} = (0, _react.useContext)(HeaderContext);
const handleResize = (e, {
size
}) => {
const nValue = size.width;
setWidth(nValue);
};
const handleResizeStop = (e, {
size
}) => {
const nValue = size.width;
// const sc = width - nValue > 0 ? width - nValue : 1;
onResize?.(width, nValue);
// 触发重新渲染列头单元格,重新计算拖拽位置
changeKeys?.();
};
if (!width) {
return /*#__PURE__*/_react.default.createElement("th", restProps, /*#__PURE__*/_react.default.createElement("div", {
className: "text-overflow"
}, children));
}
return /*#__PURE__*/_react.default.createElement("th", restProps, /*#__PURE__*/_react.default.createElement("div", {
className: "text-overflow"
}, children), /*#__PURE__*/_react.default.createElement(_reactResizable.ResizableBox, {
className: "yf_resizable_bar",
width: useWidth,
height: 0,
handle: /*#__PURE__*/_react.default.createElement("div", {
className: "react-resizable-handle",
onClick: e => {
e.stopPropagation();
}
}),
onResize: handleResize,
onResizeStop: handleResizeStop,
minConstraints: [40, 0]
// draggableOpts={{ enableUserSelectHack: false }}
}));
};
const VCell = props => {
const {
children,
...restProps
} = props;
return /*#__PURE__*/_react.default.createElement("td", restProps, children);
};
const VRow = (props, ref) => {
const {
dispatch,
rowHeight,
totalLen,
vid
} = (0, _react.useContext)(ScrollContext);
const {
children,
style,
...restProps
} = props;
const trRef = (0, _react.useRef)(null);
(0, _react.useEffect)(() => {
const initHeight = tempRef => {
if (tempRef?.current?.offsetHeight && !rowHeight && totalLen) {
const tempRowHeight = tempRef?.current?.offsetHeight ?? 0;
vidMap.set(vid, {
...vidMap.get(vid),
rowItemHeight: tempRowHeight
});
dispatch({
type: 'initHeight',
rowHeight: tempRowHeight
});
}
};
initHeight(Object.prototype.hasOwnProperty.call(ref, 'current') ? ref : trRef);
}, [trRef, dispatch, rowHeight, totalLen, ref, vid]);
return /*#__PURE__*/_react.default.createElement("tr", (0, _extends2.default)({}, restProps, {
ref: Object.prototype.hasOwnProperty.call(ref, 'current') ? ref : trRef,
style: {
...style,
height: rowHeight || 'auto',
boxSizing: 'border-box'
}
}), children);
};
const VWrapper = props => {
const {
children,
...restProps
} = props;
const {
renderLen,
start,
dispatch,
vid,
totalLen
} = (0, _react.useContext)(ScrollContext);
const contents = (0, _react.useMemo)(() => {
return children[1];
}, [children]);
const contentsLen = (0, _react.useMemo)(() => {
return contents?.length ?? 0;
}, [contents]);
(0, _react.useEffect)(() => {
if (totalLen !== contentsLen) {
dispatch({
type: 'changeTotalLen',
totalLen: contentsLen ?? 0
});
}
}, [contentsLen, dispatch, vid, totalLen]);
let tempNode = null;
if (Array.isArray(contents) && contents.length) {
tempNode = [children[0], contents.slice(start, start + (renderLen ?? 1))
// contents.slice(start, start + (renderLen ?? 1)).map((item) => {
// if (Array.isArray(item)) {
// // 兼容antd v4.3.5 --- rc-table 7.8.1及以下
// return item[0];
// }
// // 处理antd ^v4.4.0 --- rc-table ^7.8.2
// return item;
// }),
];
} else {
tempNode = children;
}
return /*#__PURE__*/_react.default.createElement("tbody", restProps, tempNode);
};
const VTable = (props, otherParams) => {
const {
style,
children,
...rest
} = props;
const {
width,
...restStyle
} = style;
const {
vid,
scrollY,
reachEnd,
onScroll,
resetScrollTopWhenDataChange
} = otherParams ?? {};
const [state, dispatch] = (0, _react.useReducer)(reducer, initialState);
const wrapTableRef = (0, _react.useRef)(null);
const tableRef = (0, _react.useRef)(null);
const ifChangeRef = (0, _react.useRef)(false);
// 数据的总条数
const [totalLen, setTotalLen] = (0, _react.useState)(children[1]?.props?.data?.length ?? 0);
(0, _react.useEffect)(() => {
setTotalLen(state.totalLen);
}, [state.totalLen]);
(0, _react.useEffect)(() => {
return () => {
vidMap.delete(vid);
};
}, [vid]);
// 数据变更
(0, _react.useEffect)(() => {
ifChangeRef.current = true;
if ((0, _lodash.isNumber)(children[1]?.props?.data?.length)) {
dispatch({
type: 'changeTotalLen',
totalLen: children[1]?.props?.data?.length ?? 0
});
}
}, [children[1].props.data]);
// table总高度
const tableHeight = (0, _react.useMemo)(() => {
let temp = 'auto';
if (state.rowHeight && totalLen) {
temp = state.rowHeight * totalLen;
}
return temp;
}, [state.rowHeight, totalLen]);
// table的scrollY值
const [tableScrollY, setTableScrollY] = (0, _react.useState)(0);
// tableScrollY 随scrollY / tableHeight 进行变更
(0, _react.useEffect)(() => {
let temp = 0;
if (typeof scrollY === 'string') {
temp = wrapTableRef.current?.parentNode?.offsetHeight ?? 0;
} else {
temp = scrollY;
}
// if (isNumber(tableHeight) && tableHeight < temp) {
// temp = tableHeight;
// }
// 处理tableScrollY <= 0的情况
if (temp <= 0) {
temp = 0;
}
setTableScrollY(temp);
}, [scrollY, tableHeight]);
// 渲染的条数
const renderLen = (0, _react.useMemo)(() => {
let temp = 1;
if (state.rowHeight && totalLen && tableScrollY) {
if (tableScrollY <= 0) {
temp = 0;
} else {
const tempRenderLen = (tableScrollY / state.rowHeight || 0) + 1 + 20;
// temp = tempRenderLen > totalLen ? totalLen : tempRenderLen;
temp = tempRenderLen;
}
}
return temp;
}, [state.rowHeight, totalLen, tableScrollY]);
// 渲染中的第一条
let start = state.rowHeight ? state.curScrollTop / state.rowHeight || 0 : 0;
// 偏移量
let offsetStart = state.rowHeight ? state.curScrollTop % state.rowHeight : 0;
// 用来优化向上滚动出现的空白
if (state.curScrollTop && state.rowHeight && state.curScrollTop > state.rowHeight) {
start -= 1;
offsetStart += state.rowHeight;
} else {
start = 0;
}
// 数据变更 操作scrollTop
(0, _react.useEffect)(() => {
const scrollNode = wrapTableRef.current?.parentNode;
if (ifChangeRef?.current) {
ifChangeRef.current = false;
if (resetScrollTopWhenDataChange) {
// 重置scrollTop
if (scrollNode) {
scrollNode.scrollTop = 0;
}
dispatch({
type: 'reset',
ifScrollTopClear: true
});
} else {
// 不重置scrollTop 不清空curScrollTop
dispatch({
type: 'reset',
ifScrollTopClear: false
});
}
}
if (vidMap.has(vid)) {
vidMap.set(vid, {
scrollNode
});
}
}, [totalLen, resetScrollTopWhenDataChange, vid, children]);
(0, _react.useEffect)(() => {
let timer = null;
const timeout = 100;
const throttleScroll = (0, _lodash.throttle)(e => {
const scrollLeft = e?.target?.scrollLeft ?? 0;
const scrollTop = e?.target?.scrollTop ?? 0;
const scrollHeight = e?.target?.scrollHeight ?? 0;
const clientHeight = e?.target?.clientHeight ?? 0;
// 到底了 没有滚动条就不会触发reachEnd. 建议设置scrolly高度少点或者数据量多点.
if (scrollTop === scrollHeight) {
// reachEnd && reachEnd()
} else if (scrollTop + clientHeight >= scrollHeight) {
// 有滚动条的情况
reachEnd && reachEnd();
}
onScroll && onScroll(scrollTop, scrollLeft);
if (!timer) {
dispatch({
type: 'changeScrolling',
isScrolling: true
});
} else {
clearTimeout(timer);
timer = null;
}
timer = setTimeout(() => {
dispatch({
type: 'changeScrolling',
isScrolling: false
});
timer = null;
}, timeout);
dispatch({
type: 'changeScrolling',
isScrolling: true
});
dispatch({
type: 'changeTrs',
curScrollTop: scrollTop
});
dispatch({
type: 'changeOperate',
curScrollLeft: scrollLeft
});
}, 60);
const ref = wrapTableRef?.current?.parentNode;
if (ref) {
ref.addEventListener('scroll', throttleScroll);
}
return () => {
ref.removeEventListener('scroll', throttleScroll);
};
}, [onScroll, reachEnd]);
return /*#__PURE__*/_react.default.createElement("div", {
className: "virtuallist",
ref: wrapTableRef,
style: {
width: '100%',
position: 'relative',
height: tableHeight,
boxSizing: 'border-box',
paddingTop: state.curScrollTop
}
}, /*#__PURE__*/_react.default.createElement(ScrollContext.Provider, {
value: {
dispatch,
rowHeight: state.rowHeight,
start,
offsetStart,
scrollLeft: state.curScrollLeft,
renderLen,
totalLen,
vid
}
}, /*#__PURE__*/_react.default.createElement("table", (0, _extends2.default)({}, rest, {
ref: tableRef,
style: {
...restStyle,
width,
position: 'relative',
transform: `translateY(-${offsetStart}px)`,
pointerEvents: state.isScrolling ? 'none' : 'initial'
}
}), children)));
};
// ================导出===================
const VList = props => {
const {
vid = DEFAULT_VID,
height,
onReachEnd,
onScroll,
resetTopWhenDataChange = true
} = props;
const resetScrollTopWhenDataChange = onReachEnd ? false : resetTopWhenDataChange;
if (!vidMap.has(vid)) {
vidMap.set(vid, {});
}
return {
table: p => VTable(p, {
vid,
scrollY: height,
reachEnd: onReachEnd,
onScroll,
resetScrollTopWhenDataChange
}),
body: {
wrapper: VWrapper,
row: VRow,
cell: cellProps => /*#__PURE__*/_react.default.createElement(VCell, cellProps)
},
header: {
wrapper: HeaderWrapper,
cell: HeaderCell
}
};
};
exports.VList = VList;
const scrollTo = option => {
const {
row,
y,
vid = DEFAULT_VID
} = option;
const {
scrollNode,
rowItemHeight
} = vidMap.get(vid);
if (row) {
if (row - 1 > 0) {
scrollNode.scrollTop = (row - 1) * (rowItemHeight ?? 0);
} else {
scrollNode.scrollTop = 0;
}
} else {
scrollNode.scrollTop = y ?? 0;
}
};
exports.scrollTo = scrollTo;