UNPKG

linkmore-design

Version:

🌈 🚀lm组件库。🚀

527 lines (507 loc) 14 kB
"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;