es-grid-template
Version:
es-grid-template
537 lines (522 loc) • 19.6 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.BaseTable = void 0;
var _classnames = _interopRequireDefault(require("classnames"));
var _react = _interopRequireDefault(require("react"));
var _rxjs = require("rxjs");
var op = _interopRequireWildcard(require("rxjs/operators"));
var _calculations = require("./calculations");
var _empty = require("./empty");
var _header = _interopRequireDefault(require("./header"));
var _getRichVisibleRectsStream = require("./helpers/getRichVisibleRectsStream");
var _rowHeightManager = require("./helpers/rowHeightManager");
var _TableDOMUtils = require("./helpers/TableDOMUtils");
var _htmlTable = require("./html-table");
var _loading = _interopRequireDefault(require("./loading"));
var _styles = require("./styles");
var _utils = require("./utils");
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
let emptyContentDeprecatedWarned = false;
function warnEmptyContentIsDeprecated() {
if (!emptyContentDeprecatedWarned) {
emptyContentDeprecatedWarned = true;
console.warn('[ali-react-table] `BaseTable` props.emptyContent is deprecated. Please use `props.components.EmptyContent` to customize the table display when there is no data.');
}
}
class BaseTable extends _react.default.Component {
static defaultProps = {
showHeader: true,
// theme: undefined,
isStickyHeader: true,
stickyTop: 0,
footerDataSource: [],
isStickyFooter: true,
stickyBottom: 0,
hasStickyScroll: true,
stickyScrollHeight: 'auto',
useVirtual: 'auto',
estimatedRowHeight: 38,
isLoading: false,
components: {},
getRowProps: _rxjs.noop,
dataSource: [],
originData: []
};
// @ts-ignore
rowHeightManager = (0, _rowHeightManager.makeRowHeightManager)(this.props.dataSource.length, this.props.estimatedRowHeight);
artTableWrapperRef = /*#__PURE__*/_react.default.createRef();
// @ts-ignore
domHelper;
rootSubscription = new _rxjs.Subscription();
// Cache of last computed render result
// @ts-ignore
lastInfo;
// @ts-ignore
props$;
/** @deprecated `BaseTable.getDoms()` is deprecated. Please do not call it. */
getDoms() {
console.warn('[ali-react-table] BaseTable.getDoms() 已经过时');
return this.domHelper;
}
constructor(props) {
super(props);
this.state = {
hasScroll: true,
needRenderLock: true,
offsetY: 0,
offsetX: 0,
// Because ResizeObserver invokes the provided callback initially,
// set default values for maxRenderHeight/maxRenderWidth (they will be overwritten soon)
// https://stackoverflow.com/questions/60026223/does-resizeobserver-invokes-initially-on-page-load
maxRenderHeight: 600,
maxRenderWidth: 800
};
}
/** Customize the scrollbar width to match the table width, ensuring the scrollbar thumb has the same width */
updateStickyScroll() {
const {
stickyScroll,
artTable,
stickyScrollItem
} = this.domHelper;
if (!artTable) {
return;
}
const tableBodyHtmlTable = this.domHelper.getTableBodyHtmlTable();
const innerTableWidth = tableBodyHtmlTable.offsetWidth;
const artTableWidth = artTable.offsetWidth;
const stickyScrollHeightProp = this.props.stickyScrollHeight;
// scroll mặc định thì bật lên
// const stickyScrollHeight = stickyScrollHeightProp === 'auto' ? getScrollbarSize().height : stickyScrollHeightProp
const stickyScrollHeight = stickyScrollHeightProp === 'auto' ? 8 : stickyScrollHeightProp; // height customscrll bar = 8
stickyScroll.style.marginTop = `-${(stickyScrollHeight ?? 0) + 1}px`;
if (artTableWidth >= innerTableWidth) {
if (this.state.hasScroll) {
this.setState({
hasScroll: false
});
}
} else {
if (!this.state.hasScroll && stickyScrollHeight && stickyScrollHeight > 5) {
// Consider the case of hidden scrollbars on macOS
this.setState({
hasScroll: true
});
}
}
// Set width of child element
stickyScrollItem.style.width = `${innerTableWidth}px`;
}
renderTableHeader(info) {
const {
stickyTop,
showHeader
} = this.props;
return /*#__PURE__*/_react.default.createElement("div", {
className: (0, _classnames.default)(_styles.Classes.tableHeader, 'no-scrollbar'),
style: {
top: stickyTop === 0 ? undefined : stickyTop,
display: showHeader ? undefined : 'none'
}
}, /*#__PURE__*/_react.default.createElement(_header.default, {
info: info
}));
}
updateOffsetX(nextOffsetX) {
if (this.lastInfo.useVirtual.horizontal) {
if (Math.abs(nextOffsetX - this.state.offsetX) >= _utils.OVERSCAN_SIZE / 2) {
this.setState({
offsetX: nextOffsetX
});
}
}
}
syncHorizontalScrollFromTableBody() {
this.syncHorizontalScroll(this.domHelper.tableBody.scrollLeft);
}
/** Synchronize horizontal scroll offset */
syncHorizontalScroll(x) {
this.updateOffsetX(x);
const {
tableBody
} = this.domHelper;
const {
flat
} = this.lastInfo;
const leftLockShadow = this.domHelper.getLeftLockShadow();
if (leftLockShadow) {
const shouldShowLeftLockShadow = flat.left.length > 0 && this.state.needRenderLock && x > 0;
if (shouldShowLeftLockShadow) {
leftLockShadow.classList.add('show-shadow');
} else {
leftLockShadow.classList.remove('show-shadow');
}
}
const rightLockShadow = this.domHelper.getRightLockShadow();
if (rightLockShadow) {
const shouldShowRightLockShadow = flat.right.length > 0 && this.state.needRenderLock && x < tableBody.scrollWidth - tableBody.clientWidth;
if (shouldShowRightLockShadow) {
rightLockShadow.classList.add('show-shadow');
} else {
rightLockShadow.classList.remove('show-shadow');
}
}
}
getVerticalRenderRange(useVirtual) {
const {
dataSource
} = this.props;
const {
offsetY,
maxRenderHeight
} = this.state;
const rowCount = dataSource.length;
if (useVirtual.vertical) {
return this.rowHeightManager.getRenderRange(offsetY, maxRenderHeight, rowCount);
} else {
return (0, _rowHeightManager.getFullRenderRange)(rowCount);
}
}
renderTableBody(info) {
const {
dataSource,
originData = [],
getRowProps,
primaryKey,
isLoading,
emptyCellHeight,
footerDataSource,
components,
id,
commandClick,
format
} = this.props;
const tableBodyClassName = (0, _classnames.default)(_styles.Classes.tableBody, _styles.Classes.horizontalScrollContainer, {
'no-scrollbar': footerDataSource && footerDataSource.length > 0
});
if (dataSource.length === 0) {
const {
components: comp,
emptyContent
} = this.props;
let EmptyContent = comp?.EmptyContent;
if (EmptyContent == null && emptyContent != null) {
warnEmptyContentIsDeprecated();
EmptyContent = () => emptyContent;
}
return /*#__PURE__*/_react.default.createElement("div", {
className: tableBodyClassName
}, /*#__PURE__*/_react.default.createElement(_empty.EmptyHtmlTable, {
descriptors: info.visible,
isLoading: isLoading,
EmptyContent: EmptyContent,
emptyCellHeight: emptyCellHeight
}));
}
const {
topIndex,
bottomBlank,
topBlank,
bottomIndex
} = info.verticalRenderRange;
return /*#__PURE__*/_react.default.createElement("div", {
className: tableBodyClassName
}, topBlank > 0 && /*#__PURE__*/_react.default.createElement("div", {
key: "top-blank",
className: (0, _classnames.default)(_styles.Classes.virtualBlank, 'top'),
style: {
height: topBlank
}
}), /*#__PURE__*/_react.default.createElement(_htmlTable.HtmlTable, {
id: id ?? '',
commandClick: commandClick,
components: components,
tbodyHtmlTag: "tbody",
getRowProps: getRowProps
// -----------------
,
primaryKey: primaryKey,
data: dataSource.slice(topIndex, bottomIndex),
originData: originData,
format: format,
horizontalRenderInfo: info,
verticalRenderInfo: {
first: 0,
offset: topIndex,
limit: bottomIndex,
last: dataSource.length - 1
}
}), bottomBlank > 0 && /*#__PURE__*/_react.default.createElement("div", {
key: "bottom-blank",
className: (0, _classnames.default)(_styles.Classes.virtualBlank, 'bottom'),
style: {
height: bottomBlank
}
}));
}
renderTableFooter(info) {
const {
footerDataSource = [],
getRowProps,
primaryKey,
stickyBottom,
components,
id,
format
} = this.props;
return /*#__PURE__*/_react.default.createElement("div", {
className: (0, _classnames.default)(_styles.Classes.tableFooter, _styles.Classes.horizontalScrollContainer),
style: {
bottom: stickyBottom === 0 ? undefined : stickyBottom
}
}, /*#__PURE__*/_react.default.createElement(_htmlTable.HtmlTable, {
id: id ?? ''
// commandClick={commandClick}
,
components: components,
tbodyHtmlTag: "tfoot",
data: footerDataSource,
originData: [],
format: format,
horizontalRenderInfo: info,
getRowProps: getRowProps,
primaryKey: primaryKey,
verticalRenderInfo: {
offset: 0,
first: 0,
last: footerDataSource.length - 1,
limit: Infinity
}
}));
}
renderLockShadows(info) {
return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement("div", {
className: _styles.Classes.lockShadowMask,
style: {
left: 0,
width: info.leftLockTotalWidth + _styles.LOCK_SHADOW_PADDING
}
}, /*#__PURE__*/_react.default.createElement("div", {
className: (0, _classnames.default)(_styles.Classes.lockShadow, _styles.Classes.leftLockShadow)
})), /*#__PURE__*/_react.default.createElement("div", {
className: _styles.Classes.lockShadowMask,
style: {
right: 0,
width: info.rightLockTotalWidth + _styles.LOCK_SHADOW_PADDING
}
}, /*#__PURE__*/_react.default.createElement("div", {
className: (0, _classnames.default)(_styles.Classes.lockShadow, _styles.Classes.rightLockShadow)
})));
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
renderStickyScroll(info) {
const {
hasStickyScroll,
stickyBottom
} = this.props;
const {
hasScroll
} = this.state;
return /*#__PURE__*/_react.default.createElement("div", {
className: (0, _classnames.default)(_styles.Classes.stickyScroll, _styles.Classes.horizontalScrollContainer),
style: {
display: hasStickyScroll && hasScroll ? 'block' : 'none',
bottom: stickyBottom,
marginTop: 0
}
}, /*#__PURE__*/_react.default.createElement("div", {
className: _styles.Classes.stickyScrollItem
}));
}
render() {
const info = (0, _calculations.calculateRenderInfo)(this);
this.lastInfo = info;
const {
dataSource,
className,
style,
showHeader,
theme,
useOuterBorder,
isStickyHeader,
isStickyFooter,
isLoading,
footerDataSource,
components
} = this.props;
const artTableWrapperClassName = (0, _classnames.default)(_styles.Classes.artTableWrapper, {
'use-outer-border': useOuterBorder,
empty: dataSource.length === 0,
lock: info.hasLockColumn,
'has-header': showHeader,
'theme-dark': theme?.theme === 'dark',
'sticky-header': isStickyHeader,
'has-footer': footerDataSource && footerDataSource.length > 0,
'sticky-footer': isStickyFooter
}, className);
const artTableWrapperProps = {
className: artTableWrapperClassName,
style,
[_utils.STYLED_REF_PROP]: this.artTableWrapperRef
};
return /*#__PURE__*/_react.default.createElement(_styles.StyledArtTableWrapper, artTableWrapperProps, /*#__PURE__*/_react.default.createElement(_loading.default, {
visible: isLoading,
LoadingIcon: components?.LoadingIcon,
LoadingContentWrapper: components?.LoadingContentWrapper
}, /*#__PURE__*/_react.default.createElement("div", {
className: _styles.Classes.artTable
}, this.renderTableHeader(info), this.renderTableBody(info), this.renderTableFooter(info), this.renderLockShadows(info)), this.renderStickyScroll(info)));
}
componentDidMount() {
this.updateDOMHelper();
this.props$ = new _rxjs.BehaviorSubject(this.props);
this.initSubscriptions();
this.didMountOrUpdate();
}
componentDidUpdate(prevProps, prevState) {
this.updateDOMHelper();
this.props$.next(this.props);
this.didMountOrUpdate(prevProps, prevState);
}
didMountOrUpdate(prevProps, prevState) {
this.syncHorizontalScrollFromTableBody();
this.updateStickyScroll();
this.adjustNeedRenderLock();
this.updateRowHeightManager();
this.updateScrollLeftWhenLayoutChanged(prevProps, prevState);
}
updateScrollLeftWhenLayoutChanged(prevProps, prevState) {
if (prevState != null) {
if (!prevState.hasScroll && this.state.hasScroll) {
this.domHelper.stickyScroll.scrollLeft = 0;
}
}
if (prevProps != null) {
const prevHasFooter = prevProps.footerDataSource && prevProps.footerDataSource.length > 0;
const currentHasFooter = this.props.footerDataSource && this.props.footerDataSource.length > 0;
if (!prevHasFooter && currentHasFooter) {
this.domHelper.tableFooter.scrollLeft = this.domHelper.tableBody.scrollLeft;
}
}
}
initSubscriptions() {
const {
tableHeader,
tableBody,
tableFooter,
stickyScroll
} = this.domHelper;
this.rootSubscription.add(_utils.throttledWindowResize$.subscribe(() => {
this.updateStickyScroll();
this.adjustNeedRenderLock();
}));
// Scroll synchronization
this.rootSubscription.add((0, _utils.syncScrollLeft)([tableHeader, tableBody, tableFooter, stickyScroll], scrollLeft => {
this.syncHorizontalScroll(scrollLeft);
}));
const richVisibleRects$ = (0, _getRichVisibleRectsStream.getRichVisibleRectsStream)(this.domHelper.artTable, this.props$.pipe(op.skip(1), op.mapTo('structure-may-change')), this.props.virtualDebugLabel).pipe(op.shareReplay());
// When the visible area changes, adjust the loading indicator position (if the loading indicator exists)
this.rootSubscription.add((0, _rxjs.combineLatest)([richVisibleRects$.pipe(op.map(p => p.clipRect), op.distinctUntilChanged(_utils.shallowEqual)), this.props$.pipe(op.startWith(null), op.pairwise(), op.filter(([prevProps, props]) => prevProps == null || !!!prevProps.isLoading && !!props?.isLoading))]).subscribe(([clipRect]) => {
const loadingIndicator = this.domHelper.getLoadingIndicator();
if (!loadingIndicator) {
return;
}
const height = clipRect.bottom - clipRect.top;
// fixme Positioning here may fail in some edge cases; see #132
loadingIndicator.style.top = `${height / 2}px`;
loadingIndicator.style.marginTop = `${height / 2}px`;
}));
// When the visible area changes, re-trigger render if virtual scrolling is enabled
this.rootSubscription.add(richVisibleRects$.pipe(op.filter(() => {
const {
horizontal,
vertical
} = this.lastInfo.useVirtual;
return horizontal || vertical;
}), op.map(({
clipRect,
offsetY
}) => ({
maxRenderHeight: clipRect.bottom - clipRect.top,
maxRenderWidth: clipRect.right - clipRect.left,
offsetY
})), op.distinctUntilChanged((x, y) => {
return Math.abs(x.maxRenderWidth - y.maxRenderWidth) < _utils.OVERSCAN_SIZE / 2 && Math.abs(x.maxRenderHeight - y.maxRenderHeight) < _utils.OVERSCAN_SIZE / 2 && Math.abs(x.offsetY - y.offsetY) < _utils.OVERSCAN_SIZE / 2;
})).subscribe(sizeAndOffset => {
this.setState(sizeAndOffset);
}));
}
componentWillUnmount() {
this.rootSubscription.unsubscribe();
}
/** Update references to DOM nodes to allow other methods to access them */
updateDOMHelper() {
// @ts-ignore
this.domHelper = new _TableDOMUtils.TableDOMHelper(this.artTableWrapperRef.current);
}
updateRowHeightManager() {
const virtualTop = this.domHelper.getVirtualTop();
const virtualTopHeight = virtualTop?.clientHeight ?? 0;
let zeroHeightRowCount = 0;
let maxRowIndex = -1;
let maxRowBottom = -1;
for (const tr of this.domHelper.getTableRows()) {
const rowIndex = Number(tr.dataset.rowindex);
if (isNaN(rowIndex)) {
continue;
}
maxRowIndex = Math.max(maxRowIndex, rowIndex);
const offset = tr.offsetTop + virtualTopHeight;
const size = tr.offsetHeight;
if (size === 0) {
zeroHeightRowCount += 1;
}
maxRowBottom = Math.max(maxRowBottom, offset + size);
this.rowHeightManager.updateRow(rowIndex, offset, size);
}
// When `estimatedRowHeight` is too large, it may result in
// "too few rendered rows to cover the visible area".
// In this case, we check whether the next render will include more rows.
// If so, we directly call `forceUpdate`.
// `zeroHeightRowCount === 0` ensures there are currently no rows with `display: none`.
if (maxRowIndex !== -1 && zeroHeightRowCount === 0) {
if (maxRowBottom < this.state.offsetY + this.state.maxRenderHeight) {
const vertical = this.getVerticalRenderRange(this.lastInfo.useVirtual);
if (vertical.bottomIndex - 1 > maxRowIndex) {
this.forceUpdate();
}
}
}
}
/** Calculate the total rendered width of all columns and determine whether lock sections need rendering */
adjustNeedRenderLock() {
const {
needRenderLock
} = this.state;
const {
flat,
hasLockColumn
} = this.lastInfo;
if (hasLockColumn) {
// @ts-ignore
const sumOfColWidth = (0, _utils.sum)(flat.full.map(col => col.width));
const nextNeedRenderLock = sumOfColWidth > this.domHelper.artTable.clientWidth;
if (needRenderLock !== nextNeedRenderLock) {
this.setState({
needRenderLock: nextNeedRenderLock
});
}
} else {
if (needRenderLock) {
this.setState({
needRenderLock: false
});
}
}
}
}
exports.BaseTable = BaseTable;