UNPKG

react-flexigrid

Version:

A React table component designed to allow presenting millions of rows of data.

1,273 lines (1,088 loc) 38.2 kB
/* eslint-disable react/require-default-props */ import React from 'react' import PropTypes from 'prop-types' import invariant from 'invariant' import classNames from 'classnames' import propTypes from './struct/propTypes' import WheelHandler from './dom/WheelHandler' import TouchHandler from './dom/TouchHandler' import Scrollbar from './Scrollbar' import VerticalScrollHelper from './FlexiGridVerticalScrollHelper' import HorizontalScrollHelper from './FlexiGridHorizontalScrollHelper' import FlexiGridColumnBuffer from './FlexiGridColumnBuffer' import FlexiGridHeader from './FlexiGridHeader' import FlexiGridBody from './FlexiGridBody' import FlexiGridShadowTop from './FlexiGridShadowTop' import FlexiGridShadowLeft from './FlexiGridShadowLeft' import FlexiGridShadowRight from './FlexiGridShadowRight' import FlexiGridShadowBottom from './FlexiGridShadowBottom' import FlexiGridSortKnobs from './FlexiGridSortKnobs' import FlexiGridColumnResizeKnobs from './FlexiGridColumnResizeKnobs' import FlexiGridColumnReorderKnobs from './FlexiGridColumnReorderKnobs' import FlexiGridColumnResizeHandler from './FlexiGridColumnResizeHandler' import FlexiGridColumnReorderHandler from './FlexiGridColumnReorderHandler' import { debounce, clamp, deepEqual } from './utils' import { getColumnsWidth, parseColumns, shrinkColumnsWidth } from './FlexiGridColumnHelper' export default class FlexiGrid extends React.Component { static propTypes = { prefixCls: PropTypes.string, className: PropTypes.string, columns: propTypes.columns.isRequired, data: propTypes.data.isRequired, rowKey: PropTypes.string.isRequired, bufferRowCount: PropTypes.number, bufferColumnCount: PropTypes.number, bordered: PropTypes.bool, borderSize: PropTypes.number, width: PropTypes.number.isRequired, height: PropTypes.number, maxHeight: PropTypes.number, ownerHeight: PropTypes.number, footerHeight: PropTypes.number, rowHeight: PropTypes.number, headerRowHeight: PropTypes.number, subRowHeight: PropTypes.number, getRowHeight: PropTypes.func, getSubRowHeight: PropTypes.func, getRowClassName: PropTypes.func, subRow: propTypes.render, footer: propTypes.render, scrollTop: PropTypes.number, scrollLeft: PropTypes.number, scrollToColumn: PropTypes.number, scrollToRow: PropTypes.number, showScrollbarX: PropTypes.bool, showScrollbarY: PropTypes.bool, touchScrollEnabled: PropTypes.bool, onHorizontalScroll: PropTypes.func, onVerticalScroll: PropTypes.func, onScrollStart: PropTypes.func, onScrollEnd: PropTypes.func, stopScrollPropagation: PropTypes.bool, onContentHeightChange: PropTypes.func, onRowTouchStart: PropTypes.func, onRowTouchEnd: PropTypes.func, onRowTouchMove: PropTypes.func, onRowClick: PropTypes.func, onRowDoubleClick: PropTypes.func, onRowMouseDown: PropTypes.func, onRowMouseUp: PropTypes.func, onRowMouseEnter: PropTypes.func, onRowMouseLeave: PropTypes.func, sortable: PropTypes.bool, sortType: PropTypes.oneOf(['asc', 'desc']), sortColumnKey: PropTypes.string, sortKnobSize: PropTypes.number, onColumnSort: PropTypes.func, resizable: PropTypes.bool, resizeKnobSize: PropTypes.number, onColumnResize: PropTypes.func, onColumnResizing: PropTypes.func, onColumnResized: PropTypes.func, reorderable: PropTypes.bool, reorderKnobSize: PropTypes.number, reorderFactor: PropTypes.number, dragScrollBuffer: PropTypes.number, dragScrollSpeed: PropTypes.number, onColumnReorder: PropTypes.func, onColumnReordering: PropTypes.func, onColumnReordered: PropTypes.func, } static defaultProps = { prefixCls: 'flexi-grid', rowHeight: 32, headerRowHeight: 32, subRowHeight: 0, footerHeight: 0, bordered: true, borderSize: 1, showScrollbarX: true, showScrollbarY: true, touchScrollEnabled: false, stopScrollPropagation: false, sortable: false, sortType: 'asc', sortColumnKey: null, sortKnobSize: 12, resizable: false, resizeKnobSize: 13, reorderable: false, reorderKnobSize: 12, reorderFactor: 3 / 4, dragScrollBuffer: 50, dragScrollSpeed: 15, } componentWillMount() { this.didScrollStop = debounce(this.didScrollStopSync, 200, this) this.wheelHandler = new WheelHandler( this.onScroll, this.shouldHandleWheelX, this.shouldHandleWheelY, this.props.stopScrollPropagation, ) this.touchHandler = new TouchHandler( this.onScroll, this.shouldHandleTouchX, this.shouldHandleTouchY, this.props.stopScrollPropagation, ) this.setNextState(this.calculateState(this.props)) } componentDidMount() { this.reportContentHeight() } componentWillReceiveProps(nextProps) { if (this.props.ownerHeight !== nextProps.ownerHeight || this.props.scrollTop !== nextProps.scrollTop || this.props.scrollLeft !== nextProps.scrollLeft) { this.didScrollStart() } this.didScrollStop.reset() this.didScrollStopSync() this.setNextState(this.calculateState(nextProps, this.state)) } componentDidUpdate() { this.reportContentHeight() } componentWillUnmount() { this.wheelHandler = null this.touchHandler = null // cancel any pending debounced scroll handling and handle immediately this.didScrollStop.reset() this.didScrollStopSync() } onScroll = (deltaX, deltaY) => { if (!this.scrolling) { this.didScrollStart() } if (Math.abs(deltaY) > Math.abs(deltaX) && this.state.showScrollbarY) { this.doVerticalScroll(deltaY, true) } else if (deltaX && this.state.showScrollbarX) { this.doHorizontalScroll(deltaX, true) } this.didScrollStop() } onHorizontalScroll = (scrollX) => { if (scrollX === this.state.scrollX) { return } if (!this.scrolling) { this.didScrollStart() } this.doHorizontalScroll(scrollX, false) this.didScrollStop() } onVerticalScroll = (scrollY) => { if (scrollY === this.state.scrollY) { return } if (!this.scrolling) { this.didScrollStart() } this.doVerticalScroll(scrollY, false) this.didScrollStop() } getStateAfterHorizontalScroll(scrollX, relative) { const scrollState = relative ? this.horizontalScrollHelper.scrollBy(Math.round(scrollX)) : this.horizontalScrollHelper.scrollTo(Math.round(scrollX)) const onHorizontalScroll = this.props.onHorizontalScroll if (onHorizontalScroll) { onHorizontalScroll(scrollState.position) } const columnData = this.state.columnData const contentWidth = columnData.leftFixedColumnsWidth + columnData.rightFixedColumnsWidth + scrollState.contentWidth const maxScrollX = Math.max(0, contentWidth - this.state.bodyWidth) const scrollableColumnsToRender = this.columnBuffer.getColumns( scrollState.index, scrollState.offset, ) return { firstColumnIndex: scrollState.index, firstColumnOffset: scrollState.offset, scrollX: scrollState.position, maxScrollX, scrollableColumnsToRender, } } doHorizontalScroll(scrollX, relative) { // eslint-disable-line this.setNextState(this.getStateAfterHorizontalScroll(scrollX, relative)) } doVerticalScroll(scrollY, relative) { // eslint-disable-line const scrollState = relative ? this.verticalScrollHelper.scrollBy(Math.round(scrollY)) : this.verticalScrollHelper.scrollTo(Math.round(scrollY)) const onVerticalScroll = this.props.onVerticalScroll if (onVerticalScroll) { onVerticalScroll(scrollState.position) } const maxScrollY = Math.max(0, scrollState.contentHeight - this.state.bodyHeight) this.setNextState({ firstRowIndex: scrollState.index, firstRowOffset: scrollState.offset, contentHeight: scrollState.contentHeight, scrollY: scrollState.position, maxScrollY, }) } didScrollStart() { // eslint-disable-line if (this.scrolling) { return } this.scrolling = true if (this.props.onScrollStart) { this.props.onScrollStart( this.state.scrollX, this.state.scrollY, this.state.firstRowIndex, ) } } didScrollStopSync() { if (!this.scrolling) { return } this.scrolling = false this.setNextState({ redraw: true }) if (this.props.onScrollEnd) { this.props.onScrollEnd( this.state.scrollX, this.state.scrollY, this.state.firstRowIndex, ) } } shouldHandleWheelX = (delta) => { if (!this.state.showScrollbarX || delta === 0) { return false } delta = Math.round(delta) // eslint-disable-line if (delta === 0) { return false } return ( (delta < 0 && this.state.scrollX > 0) || (delta >= 0 && this.state.scrollX < this.state.maxScrollX) ) } shouldHandleWheelY = (delta) => { if (!this.state.showScrollbarY || delta === 0) { return false } delta = Math.round(delta) // eslint-disable-line if (delta === 0) { return false } return ( (delta < 0 && this.state.scrollY > 0) || (delta >= 0 && this.state.scrollY < this.state.maxScrollY) ) } shouldHandleTouchX = delta => this.props.touchScrollEnabled && this.shouldHandleWheelX(delta) shouldHandleTouchY = delta => this.props.touchScrollEnabled && this.shouldHandleWheelY(delta) reportContentHeight() { const requiredHeight = this.state.contentHeight + this.state.reservedHeight let contentHeight if (this.state.useMaxHeight && this.props.maxHeight > requiredHeight) { contentHeight = requiredHeight } else if (this.state.height > requiredHeight && this.props.ownerHeight) { contentHeight = Math.max(requiredHeight, this.props.ownerHeight) } else { contentHeight = this.state.height + this.state.maxScrollY } if (contentHeight !== this.contentHeight && this.props.onContentHeightChange) { this.props.onContentHeightChange(contentHeight) } this.contentHeight = contentHeight } onColumnResize = ({ column, knobSize, left, top, rtl, adjustKnob }, e) => { // eslint-disable-line const { headerHeight, bodyHeight } = this.state this.columnResizingData = { prefixCls: this.props.prefixCls, rtl, offsetLeft: left, offsetTop: top, knobSize, adjustKnob, initialWidth: column.width, height: headerHeight + bodyHeight, minWidth: column.minWidth || 0, maxWidth: column.maxWidth || Number.MAX_SAFE_INTEGER, columnKey: column.key, initialEvent: { clientX: e.clientX, clientY: e.clientY, preventDefault: () => { }, }, onColumnResizing: this.onColumnResizing, onColumnResized: this.onColumnResized, } if (this.props.onColumnResize) { this.props.onColumnResize(column.key, column.width) } this.setNextState({ columnResizingKey: column.key }) } onColumnResizing = (columnWidth, columnKey) => { if (this.props.onColumnResizing) { this.props.onColumnResizing(columnKey, columnWidth) } } onColumnResized = (columnWidth, columnKey) => { if (this.props.onColumnResized) { this.props.onColumnResized(columnKey, columnWidth) } if (!this.columnWidthMap) { this.columnWidthMap = {} } this.columnWidthMap[columnKey] = columnWidth this.columnResizingData = null const nextState = this.calculateState(this.props, this.state) this.setNextState({ ...nextState, // update the scrollable columns to render ...this.getStateAfterHorizontalScroll(nextState.scrollX, false), columnResizingKey: null, }) } onColumnReorder = ({ column }, e) => { this.columnReorderingData = { column, initialEvent: { clientX: e.clientX, clientY: e.clientY, preventDefault: () => { }, }, onColumnReordering: this.onColumnReordering, onColumnReordered: this.onColumnReordered, } if (this.props.onColumnReorder) { this.props.onColumnReorder(column.key) } this.setNextState({ columnReorderingKey: column.key }) } onColumnReordering = ({ targetKey, movingLeft, position }) => { const { column } = this.columnReorderingData if (!column.fixed) { const { dragScrollSpeed, dragScrollBuffer } = this.props const { bodyWidth, scrollX, maxScrollX, columnData } = this.state const { leftFixedColumns, rightFixedColumns } = columnData // auto scroll on moving let scrollTo = scrollX if (movingLeft) { if ( scrollX > 0 && position < getColumnsWidth(leftFixedColumns) + dragScrollBuffer ) { scrollTo = Math.max(scrollX - dragScrollSpeed, 0) } } else if ( scrollX < maxScrollX && position + column.width > bodyWidth - getColumnsWidth(rightFixedColumns) - dragScrollBuffer ) { scrollTo = Math.min(scrollX + dragScrollSpeed, maxScrollX) } if (scrollTo !== scrollX) { this.doHorizontalScroll(scrollTo, false) } } if (this.props.onColumnReordering) { this.props.onColumnReordering(column.key, targetKey) } } onColumnReordered = (targetColumnKey) => { if (!this.columnOrderMap) { this.columnOrderMap = {} } const { column } = this.columnReorderingData const siblings = this.getColumnSiblings(column) const sourceColumnKey = column.key siblings.forEach(({ key }, index) => { if (key === targetColumnKey) { this.columnOrderMap[sourceColumnKey] = index } else if (key === sourceColumnKey) { this.columnOrderMap[targetColumnKey] = index } }) if (this.props.onColumnReordered) { this.props.onColumnReordered(sourceColumnKey, targetColumnKey) } if ( this.props.onHorizontalScroll && this.columnReorderingData.scrollX !== this.state.scrollX ) { this.props.onHorizontalScroll(this.state.scrollX) } this.setNextState({ ...this.calculateState(this.props, this.state), columnReorderingKey: null, }) this.columnReorderingData = null } isColumnResizing() { return this.state.columnResizingKey != null } isColumnReordering() { return this.state.columnReorderingKey != null } onColumnSort = (column, type) => { if (this.props.onColumnSort) { this.props.onColumnSort(column, type) } } setNextState(nextState) { const oldColumnData = this.state && this.state.columnData const { scrollableColumnsToRender, columnData } = nextState let leftFixedColumnsUpdated = false let scrollableColumnsUpdated = false let rightFixedColumnsUpdated = false let scrollableColumnsToRenderUpdated = false if (columnData && oldColumnData) { leftFixedColumnsUpdated = !( columnData.leftFixedColumns === oldColumnData.leftFixedColumns || deepEqual(columnData.leftFixedColumns, oldColumnData.leftFixedColumns) ) scrollableColumnsUpdated = !( columnData.scrollableColumns === oldColumnData.scrollableColumns || deepEqual(columnData.scrollableColumns, oldColumnData.scrollableColumns) ) rightFixedColumnsUpdated = !( columnData.rightFixedColumns === oldColumnData.rightFixedColumns || deepEqual(columnData.rightFixedColumns, oldColumnData.rightFixedColumns) ) } else if (columnData && !oldColumnData) { leftFixedColumnsUpdated = true scrollableColumnsUpdated = true rightFixedColumnsUpdated = true } if (this.state) { scrollableColumnsToRenderUpdated = !( scrollableColumnsToRender === this.state.scrollableColumnsToRender || deepEqual(scrollableColumnsToRender, this.state.scrollableColumnsToRender) ) } else { scrollableColumnsToRenderUpdated = true } const state = { ...nextState, leftFixedColumnsUpdated, scrollableColumnsUpdated, rightFixedColumnsUpdated, scrollableColumnsToRenderUpdated, } if (scrollableColumnsToRender) { const scrollableLeafColumns = columnData ? columnData.scrollableLeafColumns : oldColumnData.scrollableLeafColumns const leafColumnsToRender = scrollableColumnsToRender.map( index => ({ ...scrollableLeafColumns[index], offsetLeft: this.horizontalScrollHelper.getColumnPosition(index), }), ) const parents = leafColumnsToRender.map((item) => { let result = item let parent = item.parent while (parent) { result = parent parent = result.parent } return result }) const columnsToRender = [] parents.forEach((item) => { if (!columnsToRender.includes(item)) { columnsToRender.push(item) } }) state.scrollableColumnsToRender = columnsToRender state.scrollableLeafColumnsToRender = leafColumnsToRender } this.setState(state) } calculateState(props = this.props, oldState) { invariant( props.height !== undefined || props.maxHeight !== undefined, 'Either height or maxHeight should be specified', ) const borderSize = props.bordered ? props.borderSize : 0 const reservedBorderSize = props.bordered ? 2 * borderSize : 0 // the width of viewport includes scrollbars const viewportWidth = props.width - reservedBorderSize const columnConfig = { widthMap: this.columnWidthMap || {}, orderMap: this.columnOrderMap || {}, viewportWidth, } if (oldState) { columnConfig.resizingKey = oldState.columnResizingKey columnConfig.reorderingKey = oldState.columnReorderingKey } const columnData = parseColumns(props.columns, columnConfig) const rowCount = props.data.length const rowHeight = props.rowHeight const headerRowHeight = props.headerRowHeight const headerHeight = columnData.depth * headerRowHeight const useMaxHeight = props.height === undefined const realUseHeight = useMaxHeight ? props.maxHeight : props.height // the height of viewport includes scrollbars const viewportHeight = realUseHeight - (headerHeight || 0) - (props.footerHeight || 0) - reservedBorderSize let firstRowIndex = (oldState && oldState.firstRowIndex) || 0 let firstRowOffset = (oldState && oldState.firstRowOffset) || 0 let firstColumnIndex = (oldState && oldState.firstColumnIndex) || 0 let firstColumnOffset = (oldState && oldState.firstColumnOffset) || 0 let scrollX = oldState ? oldState.scrollX : 0 let scrollY = oldState ? oldState.scrollY : 0 if (!this.verticalScrollHelper) { this.verticalScrollHelper = new VerticalScrollHelper( rowCount, props.rowHeight, viewportHeight, props.getRowHeight, props.subRowHeight, props.getSubRowHeight, ) } const oldViewportHeight = this.verticalScrollHelper.viewportHeight if (oldState && ( rowCount !== oldState.rowCount || props.rowHeight !== oldState.rowHeight || props.height !== oldState.height )) { this.verticalScrollHelper = new VerticalScrollHelper( rowCount, props.rowHeight, viewportHeight, props.getRowHeight, props.subRowHeight, props.getSubRowHeight, ) const scrollState = this.verticalScrollHelper.scrollToRow(firstRowIndex, firstRowOffset) scrollY = scrollState.position firstRowIndex = scrollState.index firstRowOffset = scrollState.offset } else if (oldState) { if (props.getRowHeight !== oldState.getRowHeight) { this.verticalScrollHelper.setRowHeightGetter(props.getRowHeight) } if (props.getSubRowHeight !== oldState.getSubRowHeight) { this.verticalScrollHelper.setSubRowHeightGetter(props.getSubRowHeight) } } // scrollToRow const oldScrollToRow = oldState ? oldState.scrollToRow : undefined if (props.scrollToRow !== undefined && ( props.scrollToRow !== oldScrollToRow || viewportHeight !== oldViewportHeight )) { const scrollState = this.verticalScrollHelper.scrollRowIntoView(props.scrollToRow) scrollY = scrollState.position firstRowIndex = scrollState.index firstRowOffset = scrollState.offset } // scrollTop const oldScrollTop = oldState ? oldState.scrollTop : undefined if (props.scrollTop !== undefined && props.scrollTop !== oldScrollTop) { const scrollState = this.verticalScrollHelper.scrollTo(props.scrollTop) scrollY = scrollState.position firstRowIndex = scrollState.index firstRowOffset = scrollState.offset } // size for content const contentWidth = getColumnsWidth(columnData.columns) const contentHeight = this.verticalScrollHelper.getContentHeight() const isJustFullfill = contentWidth === viewportWidth const showScrollbarX = contentWidth > viewportWidth + borderSize const showScrollbarY = contentHeight > viewportHeight const reservedHeight = props.footerHeight + headerHeight + reservedBorderSize + (showScrollbarX ? Scrollbar.SIZE : 0) const requiredHeight = contentHeight + reservedHeight const reservedColumnWidth = columnData.leftFixedColumnsWidth + columnData.rightFixedColumnsWidth let height = Math.round(realUseHeight) if (useMaxHeight && !showScrollbarY) { height = requiredHeight } if (showScrollbarY && isJustFullfill) { shrinkColumnsWidth(columnData, Scrollbar.SIZE) } // body's size excludes scrollbars const bodyWidth = viewportWidth - (showScrollbarY ? Scrollbar.SIZE : 0) const bodyHeight = height - reservedHeight const maxScrollX = Math.max(0, contentWidth - bodyWidth) const maxScrollY = Math.max(0, contentHeight - bodyHeight) scrollX = Math.min(scrollX, maxScrollX) scrollY = Math.min(scrollY, maxScrollY) this.verticalScrollHelper.setViewportHeight(bodyHeight) const getScrollableColumnWidth = index => columnData.scrollableLeafColumns[index].width const scrollableViewportWidth = bodyWidth - reservedColumnWidth if (!this.horizontalScrollHelper) { this.horizontalScrollHelper = new HorizontalScrollHelper( columnData.scrollableLeafColumns.length, scrollableViewportWidth, getScrollableColumnWidth, ) } else { this.horizontalScrollHelper.setViewportWidth(scrollableViewportWidth) this.horizontalScrollHelper.setColumnWidthGetter(getScrollableColumnWidth) } // scrollToColumn const oldScrollToColumn = oldState ? oldState.scrollToColumn : undefined const scrollToColumn = props.scrollToColumn if (scrollToColumn !== undefined && scrollToColumn !== oldScrollToColumn) { const leftFixedCount = columnData.leftFixedLeafColumns.length const scrollableCount = columnData.scrollableLeafColumns.length const columnIndex = clamp(scrollToColumn - leftFixedCount, 0, scrollableCount - 1) const scrollState = this.horizontalScrollHelper.scrollToColumn(columnIndex) scrollX = scrollState.position firstColumnIndex = scrollState.index firstColumnOffset = scrollState.offset } // scrollLeft const oldScrollLeft = oldState ? oldState.scrollLeft : undefined if (props.scrollLeft !== undefined && props.scrollLeft !== oldScrollLeft) { const scrollState = this.horizontalScrollHelper.scrollTo(props.scrollLeft) scrollX = scrollState.position firstColumnIndex = scrollState.index firstColumnOffset = scrollState.offset } if ( !this.columnBuffer || !oldState || !deepEqual(columnData.scrollableColumns, oldState.scrollableColumns) ) { this.columnBuffer = new FlexiGridColumnBuffer( columnData.scrollableLeafColumns.length, scrollableViewportWidth, getScrollableColumnWidth, props.bufferColumnCount, ) } const scrollableColumnsToRender = this.columnBuffer.getColumns( firstColumnIndex, firstColumnOffset, ) // The order of elements in this object metters and bringing bodyHeight, // height or useGroupHeader to the top can break various features const newState = { rowCount, columnData, scrollableColumnsToRender, columnResizingKey: oldState && oldState.columnResizingKey || null, columnReorderingKey: oldState && oldState.columnReorderingKey || null, width: props.width, height, rowHeight, useMaxHeight, headerHeight, reservedHeight, viewportWidth, viewportHeight, bodyWidth, bodyHeight, contentWidth, contentHeight, firstRowIndex, firstRowOffset, firstColumnIndex, firstColumnOffset, scrollX, scrollY, maxScrollX, maxScrollY, showScrollbarX, showScrollbarY, isJustFullfill, // store these props for next compare scrollTop: props.scrollTop, scrollLeft: props.scrollLeft, scrollToRow: props.scrollToRow, scrollToColumn: props.scrollToColumn, getRowHeight: props.getRowHeight, getSubRowHeight: props.getSubRowHeight, } return newState } getColumnReorderingData() { const raw = this.columnReorderingData if (raw) { const { column } = raw const { prefixCls, headerRowHeight, borderSize, reorderFactor, reorderKnobSize } = this.props const { bodyWidth, headerHeight, bodyHeight, columnData, scrollX } = this.state const { leftFixedColumnsWidth, rightFixedColumnsWidth } = columnData const maxRight = bodyWidth - rightFixedColumnsWidth const offsetTop = (column.depth - 1) * headerRowHeight let offsetLeft = column.left if (this.state.showScrollbarX) { if (!column.fixed) { offsetLeft -= scrollX } else if (column.fixed === 'right') { offsetLeft += bodyWidth - rightFixedColumnsWidth } } const siblings = this.getColumnSiblings(column) const targets = [] let siblingLeft = offsetLeft siblings.some((item) => { if (item === column) { return true } siblingLeft -= item.width return false }) siblings.forEach((item) => { targets.push({ left: siblingLeft, top: offsetTop, height: headerHeight - offsetTop, width: item.width, key: item.key, leftSideVisible: siblingLeft >= leftFixedColumnsWidth, rightSideVisible: siblingLeft + item.width <= maxRight, }) siblingLeft += item.width }) return { ...raw, prefixCls, width: bodyWidth, height: headerHeight + bodyHeight, offsetLeft, offsetTop, targets, headerRowHeight, columnHeight: headerHeight - offsetTop, borderSize, scrollX, factor: reorderFactor, knobSize: reorderKnobSize, } } return raw } getColumnSiblings(column) { let siblings = column.depth > 1 ? column.parent.children : null if (!siblings) { const columnData = this.state.columnData if (column.fixed === 'left') { siblings = columnData.leftFixedColumns } else if (column.fixed === 'right') { siblings = columnData.rightFixedColumns } else { siblings = columnData.scrollableColumns } } return siblings } getHeaderAndBodyCommonProps() { const { prefixCls, rowHeight, bordered } = this.props const { columnData, scrollX, showScrollbarX, showScrollbarY, isJustFullfill, scrollableColumnsToRender, scrollableLeafColumnsToRender, leftFixedColumnsUpdated, scrollableColumnsUpdated, rightFixedColumnsUpdated, scrollableColumnsToRenderUpdated, } = this.state const { leftFixedColumns, scrollableColumns, rightFixedColumns, leftFixedLeafColumns, scrollableLeafColumns, rightFixedLeafColumns, leftFixedColumnsWidth, scrollableColumnsWidth, rightFixedColumnsWidth, } = columnData return { prefixCls, rowHeight, bordered, scrollX, showScrollbarX, showScrollbarY, isJustFullfill, leftFixedColumns, scrollableColumns, rightFixedColumns, leftFixedLeafColumns, scrollableLeafColumns, rightFixedLeafColumns, scrollableColumnsToRender, scrollableLeafColumnsToRender, leftFixedColumnsWidth, scrollableColumnsWidth, rightFixedColumnsWidth, leftFixedColumnsUpdated, scrollableColumnsUpdated, rightFixedColumnsUpdated, scrollableColumnsToRenderUpdated, } } renderHeader() { const { viewportWidth, headerHeight } = this.state const props = { ...this.getHeaderAndBodyCommonProps(), rowHeight: this.props.headerRowHeight, scrollbarSize: Scrollbar.SIZE, width: viewportWidth, height: headerHeight, } return <FlexiGridHeader {...props} /> } renderBody() { const props = { ...this.getHeaderAndBodyCommonProps(), width: this.state.bodyWidth, height: this.state.bodyHeight, firstRowIndex: this.state.firstRowIndex, firstRowOffset: this.state.firstRowOffset, data: this.props.data, rowKey: this.props.rowKey, scrolling: this.scrolling, subRow: this.props.subRow, bufferRowCount: this.props.bufferRowCount, getRowPosition: this.verticalScrollHelper.getRowPosition, getRowHeight: this.props.getRowHeight, getSubRowHeight: this.props.getSubRowHeight, getRowClassName: this.props.getRowClassName, onRowClick: this.props.onRowClick, onRowDoubleClick: this.props.onRowDoubleClick, onRowMouseDown: this.props.onRowMouseDown, onRowMouseUp: this.props.onRowMouseUp, onRowMouseEnter: this.props.onRowMouseEnter, onRowMouseLeave: this.props.onRowMouseLeave, onRowTouchStart: this.props.onRowTouchStart, onRowTouchEnd: this.props.onRowTouchEnd, onRowTouchMove: this.props.onRowTouchMove, } return <FlexiGridBody {...props} /> } renderFooter() { const { prefixCls, footerHeight, footer } = this.props const { viewportWidth } = this.state const style = { width: viewportWidth, height: footerHeight, } return ( <div className={`${prefixCls}-footer`} style={style} > {footer} </div> ) } renderShadowTop() { const props = { prefixCls: this.props.prefixCls, display: this.state.showScrollbarY, visible: this.state.scrollY > 0, width: this.state.bodyWidth, top: this.state.headerHeight, } return <FlexiGridShadowTop {...props} /> } renderShadowBottom() { const props = { prefixCls: this.props.prefixCls, display: this.state.showScrollbarY, visible: this.state.scrollY !== this.state.maxScrollY, width: this.state.bodyWidth, bottom: this.props.footerHeight, } return <FlexiGridShadowBottom {...props} /> } renderShadowLeft() { const { leftFixedColumns, leftFixedColumnsWidth } = this.state.columnData const props = { prefixCls: this.props.prefixCls, display: leftFixedColumns.length > 0, visible: this.state.scrollX > 0, height: this.state.headerHeight + this.state.bodyHeight, left: leftFixedColumnsWidth, } return <FlexiGridShadowLeft {...props} /> } renderShadowRight() { const { rightFixedColumns, rightFixedColumnsWidth } = this.state.columnData const props = { prefixCls: this.props.prefixCls, display: rightFixedColumns.length > 0, visible: this.state.scrollX !== this.state.maxScrollX, height: this.state.headerHeight + this.state.bodyHeight, right: rightFixedColumnsWidth + (this.state.showScrollbarY ? Scrollbar.SIZE : 0), } return <FlexiGridShadowRight {...props} /> } renderResizeKnobs() { const { columnData } = this.state const props = { prefixCls: this.props.prefixCls, knobSize: this.props.resizeKnobSize, headerRowHeight: this.props.headerRowHeight, headerHeight: this.state.headerHeight, bodyHeight: this.state.bodyHeight, bodyWidth: this.state.bodyWidth, scrollX: this.state.scrollX, showScrollbarX: this.state.showScrollbarX, isJustFullfill: this.state.isJustFullfill, leftFixedLeafColumns: columnData.leftFixedLeafColumns, scrollableLeafColumns: columnData.scrollableLeafColumns, rightFixedLeafColumns: columnData.rightFixedLeafColumns, leftFixedColumnsWidth: columnData.leftFixedColumnsWidth, scrollableColumnsWidth: columnData.scrollableColumnsWidth, rightFixedColumnsWidth: columnData.rightFixedColumnsWidth, onColumnResize: this.onColumnResize, } return <FlexiGridColumnResizeKnobs {...props} /> } renderColumnResizeHandler() { const props = this.columnResizingData || { prefixCls: this.props.prefixCls } return (<FlexiGridColumnResizeHandler {...props} visible={this.isColumnResizing()} />) } renderReorderKnobs() { const { columnData } = this.state const props = { prefixCls: this.props.prefixCls, knobSize: this.props.reorderKnobSize, headerHeight: this.state.headerHeight, headerRowHeight: this.props.headerRowHeight, bodyWidth: this.state.bodyWidth, scrollX: this.state.scrollX, showScrollbarX: this.state.showScrollbarX, leftFixedColumns: columnData.leftFixedColumns, scrollableColumns: columnData.scrollableColumns, rightFixedColumns: columnData.rightFixedColumns, leftFixedColumnsWidth: columnData.leftFixedColumnsWidth, scrollableColumnsWidth: columnData.scrollableColumnsWidth, rightFixedColumnsWidth: columnData.rightFixedColumnsWidth, onColumnReorder: this.onColumnReorder, } return <FlexiGridColumnReorderKnobs {...props} /> } renderColumnReorderHandler() { const props = this.getColumnReorderingData() || { prefixCls: this.props.prefixCls } return ( <FlexiGridColumnReorderHandler {...props} sortable={this.props.sortable} visible={this.isColumnReordering()} /> ) } renderColumnSortKnobs() { const { columnData } = this.state const props = { prefixCls: this.props.prefixCls, knobSize: this.props.sortKnobSize, sortType: this.props.sortType, sortColumnKey: this.props.sortColumnKey, headerRowHeight: this.props.headerRowHeight, headerHeight: this.state.headerHeight, bodyWidth: this.state.bodyWidth, scrollX: this.state.scrollX, showScrollbarX: this.state.showScrollbarX, leftFixedColumns: columnData.leftFixedColumns, scrollableColumns: columnData.scrollableColumns, rightFixedColumns: columnData.rightFixedColumns, leftFixedColumnsWidth: columnData.leftFixedColumnsWidth, scrollableColumnsWidth: columnData.scrollableColumnsWidth, rightFixedColumnsWidth: columnData.rightFixedColumnsWidth, onSort: this.onColumnSort, } return <FlexiGridSortKnobs {...props} /> } renderScrollbar() { const state = this.state const props = this.props const result = {} if (state.showScrollbarY) { result.verticalScrollbar = ( <Scrollbar orientation={'vertical'} top={state.headerHeight} prefixCls={props.prefixCls} position={state.scrollY} size={state.bodyHeight} contentSize={state.contentHeight} onScroll={this.onVerticalScroll} /> ) } if (state.showScrollbarX) { result.horizontalScrollbar = ( <Scrollbar orientation={'horizontal'} prefixCls={props.prefixCls} position={state.scrollX} size={state.bodyWidth} contentSize={state.contentWidth} hasVerticalScrollbar={state.showScrollbarX} onScroll={this.onHorizontalScroll} /> ) } return result } render() { const state = this.state const props = this.props const { prefixCls, className, bordered, reorderable, sortable } = props const mainClassNames = classNames( prefixCls, className, { bordered, reorderable, sortable }, ) const { verticalScrollbar, horizontalScrollbar } = this.renderScrollbar() return ( <div className={mainClassNames} onWheel={this.wheelHandler.onWheel} onTouchStart={this.touchHandler.onTouchStart} onTouchEnd={this.touchHandler.onTouchEnd} onTouchMove={this.touchHandler.onTouchMove} onTouchCancel={this.touchHandler.onTouchCancel} style={{ width: state.width, height: state.height, }} > <div className={`${prefixCls}-container`} style={{ width: state.viewportWidth, height: state.headerHeight + state.bodyHeight + props.footerHeight, }} > {this.renderHeader()} {this.renderBody()} {this.renderFooter()} {this.renderShadowTop()} {this.renderShadowRight()} {this.renderShadowBottom()} {this.renderShadowLeft()} {this.props.resizable && this.renderResizeKnobs()} {this.props.reorderable && this.renderReorderKnobs()} {this.props.resizable && this.renderColumnResizeHandler()} {this.props.reorderable && this.renderColumnReorderHandler()} {this.props.sortable && this.renderColumnSortKnobs()} </div> {verticalScrollbar} {horizontalScrollbar} </div> ) } }