1
This commit is contained in:
+261
@@ -0,0 +1,261 @@
|
||||
import { useContext } from '@rc-component/context';
|
||||
import VirtualList from '@rc-component/virtual-list';
|
||||
import * as React from 'react';
|
||||
import TableContext, { responseImmutable } from "../context/TableContext";
|
||||
import useFlattenRecords from "../hooks/useFlattenRecords";
|
||||
import BodyLine from "./BodyLine";
|
||||
import { GridContext, StaticContext } from "./context";
|
||||
const Grid = /*#__PURE__*/React.forwardRef((props, ref) => {
|
||||
const {
|
||||
data,
|
||||
onScroll
|
||||
} = props;
|
||||
const {
|
||||
flattenColumns,
|
||||
onColumnResize,
|
||||
getRowKey,
|
||||
expandedKeys,
|
||||
prefixCls,
|
||||
childrenColumnName,
|
||||
scrollX,
|
||||
direction
|
||||
} = useContext(TableContext, ['flattenColumns', 'onColumnResize', 'getRowKey', 'prefixCls', 'expandedKeys', 'childrenColumnName', 'scrollX', 'direction']);
|
||||
const {
|
||||
sticky,
|
||||
scrollY,
|
||||
listItemHeight,
|
||||
getComponent,
|
||||
onScroll: onTablePropScroll
|
||||
} = useContext(StaticContext);
|
||||
|
||||
// =========================== Ref ============================
|
||||
const listRef = React.useRef(null);
|
||||
|
||||
// =========================== Data ===========================
|
||||
const flattenData = useFlattenRecords(data, childrenColumnName, expandedKeys, getRowKey);
|
||||
|
||||
// ========================== Column ==========================
|
||||
const columnsWidth = React.useMemo(() => {
|
||||
let total = 0;
|
||||
return flattenColumns.map(({
|
||||
width,
|
||||
minWidth,
|
||||
key
|
||||
}) => {
|
||||
const finalWidth = Math.max(width || 0, minWidth || 0);
|
||||
total += finalWidth;
|
||||
return [key, finalWidth, total];
|
||||
});
|
||||
}, [flattenColumns]);
|
||||
const columnsOffset = React.useMemo(() => columnsWidth.map(colWidth => colWidth[2]), [columnsWidth]);
|
||||
React.useEffect(() => {
|
||||
columnsWidth.forEach(([key, width]) => {
|
||||
onColumnResize(key, width);
|
||||
});
|
||||
}, [columnsWidth]);
|
||||
|
||||
// =========================== Ref ============================
|
||||
React.useImperativeHandle(ref, () => {
|
||||
const obj = {
|
||||
scrollTo: config => {
|
||||
const {
|
||||
offset,
|
||||
...restConfig
|
||||
} = config;
|
||||
|
||||
// If offset is provided, force align to 'top' for consistent behavior
|
||||
if (offset) {
|
||||
listRef.current?.scrollTo({
|
||||
...restConfig,
|
||||
offset,
|
||||
align: 'top'
|
||||
});
|
||||
} else {
|
||||
listRef.current?.scrollTo(config);
|
||||
}
|
||||
},
|
||||
nativeElement: listRef.current?.nativeElement
|
||||
};
|
||||
Object.defineProperty(obj, 'scrollLeft', {
|
||||
get: () => listRef.current?.getScrollInfo().x || 0,
|
||||
set: value => {
|
||||
listRef.current?.scrollTo({
|
||||
left: value
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// https://github.com/ant-design/ant-design/issues/54734
|
||||
Object.defineProperty(obj, 'scrollTop', {
|
||||
get: () => listRef.current?.getScrollInfo().y || 0,
|
||||
set: value => {
|
||||
listRef.current?.scrollTo({
|
||||
top: value
|
||||
});
|
||||
}
|
||||
});
|
||||
return obj;
|
||||
});
|
||||
|
||||
// ======================= Col/Row Span =======================
|
||||
const getRowSpan = (column, index) => {
|
||||
const record = flattenData[index]?.record;
|
||||
const {
|
||||
onCell
|
||||
} = column;
|
||||
if (onCell) {
|
||||
const cellProps = onCell(record, index);
|
||||
return cellProps?.rowSpan ?? 1;
|
||||
}
|
||||
return 1;
|
||||
};
|
||||
const extraRender = info => {
|
||||
const {
|
||||
start,
|
||||
end,
|
||||
getSize,
|
||||
offsetY
|
||||
} = info;
|
||||
|
||||
// Do nothing if no data
|
||||
if (end < 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Find first rowSpan column
|
||||
let firstRowSpanColumns = flattenColumns.filter(
|
||||
// rowSpan is 0
|
||||
column => getRowSpan(column, start) === 0);
|
||||
let startIndex = start;
|
||||
for (let i = start; i >= 0; i -= 1) {
|
||||
firstRowSpanColumns = firstRowSpanColumns.filter(column => getRowSpan(column, i) === 0);
|
||||
if (!firstRowSpanColumns.length) {
|
||||
startIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Find last rowSpan column
|
||||
let lastRowSpanColumns = flattenColumns.filter(
|
||||
// rowSpan is not 1
|
||||
column => getRowSpan(column, end) !== 1);
|
||||
let endIndex = end;
|
||||
for (let i = end; i < flattenData.length; i += 1) {
|
||||
lastRowSpanColumns = lastRowSpanColumns.filter(column => getRowSpan(column, i) !== 1);
|
||||
if (!lastRowSpanColumns.length) {
|
||||
endIndex = Math.max(i - 1, end);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Collect the line who has rowSpan
|
||||
const spanLines = [];
|
||||
for (let i = startIndex; i <= endIndex; i += 1) {
|
||||
const item = flattenData[i];
|
||||
|
||||
// This code will never reach, just incase
|
||||
if (!item) {
|
||||
continue;
|
||||
}
|
||||
if (flattenColumns.some(column => getRowSpan(column, i) > 1)) {
|
||||
spanLines.push(i);
|
||||
}
|
||||
}
|
||||
|
||||
// Patch extra line on the page
|
||||
const nodes = spanLines.map(index => {
|
||||
const item = flattenData[index];
|
||||
const rowKey = getRowKey(item.record, index);
|
||||
const getHeight = rowSpan => {
|
||||
const endItemIndex = index + rowSpan - 1;
|
||||
const endItem = flattenData[endItemIndex];
|
||||
if (!endItem || !endItem.record) {
|
||||
// clamp 到当前可用的最后一行,或退化为默认高度
|
||||
const safeEndIndex = Math.min(endItemIndex, flattenData.length - 1);
|
||||
const safeEndItem = flattenData[safeEndIndex];
|
||||
const endItemKey = getRowKey(safeEndItem.record, safeEndIndex);
|
||||
const sizeInfo = getSize(rowKey, endItemKey);
|
||||
return sizeInfo.bottom - sizeInfo.top;
|
||||
}
|
||||
const endItemKey = getRowKey(endItem.record, endItemIndex);
|
||||
const sizeInfo = getSize(rowKey, endItemKey);
|
||||
return sizeInfo.bottom - sizeInfo.top;
|
||||
};
|
||||
const sizeInfo = getSize(rowKey);
|
||||
return /*#__PURE__*/React.createElement(BodyLine, {
|
||||
key: index,
|
||||
data: item,
|
||||
rowKey: rowKey,
|
||||
index: index,
|
||||
style: {
|
||||
top: -offsetY + sizeInfo.top
|
||||
},
|
||||
extra: true,
|
||||
getHeight: getHeight
|
||||
});
|
||||
});
|
||||
return nodes;
|
||||
};
|
||||
|
||||
// ========================= Context ==========================
|
||||
const gridContext = React.useMemo(() => ({
|
||||
columnsOffset
|
||||
}), [columnsOffset]);
|
||||
|
||||
// ========================== Render ==========================
|
||||
const tblPrefixCls = `${prefixCls}-tbody`;
|
||||
|
||||
// default 'div' in @rc-component/virtual-list
|
||||
const wrapperComponent = getComponent(['body', 'wrapper']);
|
||||
|
||||
// ========================== Sticky Scroll Bar ==========================
|
||||
const horizontalScrollBarStyle = {};
|
||||
if (sticky) {
|
||||
horizontalScrollBarStyle.position = 'sticky';
|
||||
horizontalScrollBarStyle.bottom = 0;
|
||||
if (typeof sticky === 'object' && sticky.offsetScroll) {
|
||||
horizontalScrollBarStyle.bottom = sticky.offsetScroll;
|
||||
}
|
||||
}
|
||||
return /*#__PURE__*/React.createElement(GridContext.Provider, {
|
||||
value: gridContext
|
||||
}, /*#__PURE__*/React.createElement(VirtualList, {
|
||||
fullHeight: false,
|
||||
ref: listRef,
|
||||
prefixCls: `${tblPrefixCls}-virtual`,
|
||||
styles: {
|
||||
horizontalScrollBar: horizontalScrollBarStyle
|
||||
},
|
||||
className: tblPrefixCls,
|
||||
height: scrollY,
|
||||
itemHeight: listItemHeight || 24,
|
||||
data: flattenData,
|
||||
itemKey: item => getRowKey(item.record),
|
||||
component: wrapperComponent,
|
||||
scrollWidth: scrollX,
|
||||
direction: direction,
|
||||
onVirtualScroll: ({
|
||||
x
|
||||
}) => {
|
||||
onScroll({
|
||||
currentTarget: listRef.current?.nativeElement,
|
||||
scrollLeft: x
|
||||
});
|
||||
},
|
||||
onScroll: onTablePropScroll,
|
||||
extraRender: extraRender
|
||||
}, (item, index, itemProps) => {
|
||||
const rowKey = getRowKey(item.record, index);
|
||||
return /*#__PURE__*/React.createElement(BodyLine, {
|
||||
data: item,
|
||||
rowKey: rowKey,
|
||||
index: index,
|
||||
style: itemProps.style
|
||||
});
|
||||
}));
|
||||
});
|
||||
const ResponseGrid = responseImmutable(Grid);
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
ResponseGrid.displayName = 'ResponseGrid';
|
||||
}
|
||||
export default ResponseGrid;
|
||||
Reference in New Issue
Block a user