UNPKG

es-grid-template

Version:

es-grid-template

537 lines (522 loc) 19.6 kB
"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;