UNPKG

react-flexigrid

Version:

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

431 lines (350 loc) 12.8 kB
import _classCallCheck from 'babel-runtime/helpers/classCallCheck'; import _possibleConstructorReturn from 'babel-runtime/helpers/possibleConstructorReturn'; import _inherits from 'babel-runtime/helpers/inherits'; /* eslint-disable react/require-default-props */ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; import keys from 'rc-util/lib/KeyCode'; import { shallowEqual, translateDOMPosition, clamp } from './utils'; import WheelHandler from './dom/WheelHandler'; import MouseMoveTracker from './dom/MouseMoveTracker'; var FACE_MARGIN = 4; var FACE_MARGIN_2X = FACE_MARGIN * 2; var SCROLLBAR_SIZE = 15; var FACE_SIZE_MIN = 30; var KEYBOARD_SCROLL_AMOUNT = 40; var lastScrolledScrollbar = null; var Scrollbar = function (_React$Component) { _inherits(Scrollbar, _React$Component); function Scrollbar(props) { _classCallCheck(this, Scrollbar); var _this = _possibleConstructorReturn(this, _React$Component.call(this, props)); _this.onWheel = function (delta) { _this.setNextState(_this.calculateState(_this.state.position + delta)); }; _this.onWheelX = function (deltaX) { _this.onWheel(deltaX); }; _this.onWheelY = function (deltaX, deltaY) { _this.onWheel(deltaY); }; _this.onMouseDown = function (e) { var nextState = void 0; if (e.target !== _this.faceElem) { var nativeEvent = e.nativeEvent; var position = _this.state.horizontal ? nativeEvent.offsetX || nativeEvent.layerX : nativeEvent.offsetY || nativeEvent.layerY; // mousedown on the scroll-track directly, move the // center of the scroll-face to the mouse position. position /= _this.state.scale; nextState = _this.calculateState(position - _this.state.faceSize * 0.5 / _this.state.scale); } else { nextState = {}; } nextState.focused = true; _this.setNextState(nextState); _this.mouseMoveTracker.capture(e); // focus the container so it may receive keyboard events _this.containerElem.focus(); }; _this.onMouseMove = function (deltaX, deltaY) { var delta = _this.state.horizontal ? deltaX : deltaY; if (delta !== 0) { delta /= _this.state.scale; _this.setNextState(_this.calculateState(_this.state.position + delta)); } }; _this.onMouseMoveEnd = function () { _this.mouseMoveTracker.release(); _this.setState({ dragging: false, focused: false }); }; _this.onKeyDown = function (e) { var keyCode = e.keyCode; // let focus move off the scrollbar if (keyCode === keys.TAB) { return; } var distance = KEYBOARD_SCROLL_AMOUNT; var direction = 0; if (_this.state.horizontal) { switch (keyCode) { case keys.HOME: direction = -1; distance = _this.props.contentSize; break; case keys.LEFT: direction = -1; break; case keys.RIGHT: direction = 1; break; default: return; } } else { switch (keyCode) { case keys.SPACE: if (e.shiftKey) { direction = -1; } else { direction = 1; } break; case keys.HOME: direction = -1; distance = _this.props.contentSize; break; case keys.UP: direction = -1; break; case keys.DOWN: direction = 1; break; case keys.PAGE_UP: direction = -1; distance = _this.props.size; break; case keys.PAGE_DOWN: direction = 1; distance = _this.props.size; break; default: return; } } e.preventDefault(); _this.setNextState(_this.calculateState(_this.state.position + distance * direction)); }; _this.onFocus = function () { _this.setState({ focused: true }); }; _this.onBlur = function () { _this.setState({ focused: false }); }; _this.shouldHandleScrollX = function (delta) { return _this.props.orientation === 'horizontal' ? _this.shouldHandleScrollChange(delta) : false; }; _this.shouldHandleScrollY = function (delta) { return _this.props.orientation !== 'horizontal' ? _this.shouldHandleScrollChange(delta) : false; }; _this.triggerOnScrollCallback = function () { var position = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : _this.state.position; if (_this.props.onScroll) { _this.props.onScroll(position); } }; _this.state = _this.calculateState(props.position || props.defaultPosition || 0); return _this; } Scrollbar.prototype.componentWillMount = function componentWillMount() { var onWheel = this.state.horizontal ? this.onWheelX : this.onWheelY; this.wheelHandler = new WheelHandler(onWheel, this.shouldHandleScrollX, this.shouldHandleScrollY); this.initialRender = true; }; Scrollbar.prototype.componentDidMount = function componentDidMount() { this.mouseMoveTracker = new MouseMoveTracker(document.documentElement, this.onMouseMove, this.onMouseMoveEnd); if (this.props.position !== undefined && this.state.position !== this.props.position) { this.triggerOnScrollCallback(); } this.initialRender = false; }; Scrollbar.prototype.componentWillReceiveProps = function componentWillReceiveProps(nextProps) { if (!shallowEqual(this.props, nextProps)) { var controlledPosition = nextProps.position; if (controlledPosition === undefined) { this.setNextState(this.calculateState(this.state.position, nextProps)); } else { this.setNextState(this.calculateState(controlledPosition, nextProps), nextProps); } } }; Scrollbar.prototype.shouldComponentUpdate = function shouldComponentUpdate(nextProps, nextState) { return !shallowEqual(this.props, nextProps) || !shallowEqual(this.state, nextState); }; Scrollbar.prototype.componentWillUnmount = function componentWillUnmount() { this.mouseMoveTracker.release(); delete this.mouseMoveTracker; if (lastScrolledScrollbar === this) { lastScrolledScrollbar = null; } }; Scrollbar.prototype.setNextState = function setNextState(nextState) { var props = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.props; var controlledPosition = props.position; var willScroll = this.state.position !== nextState.position; var callback = willScroll ? this.triggerOnScrollCallback : undefined; if (controlledPosition === undefined) { this.setState(nextState, callback); } else if (controlledPosition === nextState.position) { this.setState(nextState); } else { // Scrolling is controlled. // Don't update the state and let the owner to update the scrollbar instead. if (nextState.position !== undefined && nextState.position !== this.state.position) { callback(nextState.position); } return; } if (willScroll && lastScrolledScrollbar !== this) { if (lastScrolledScrollbar) { lastScrolledScrollbar.blur(); } lastScrolledScrollbar = this; } }; Scrollbar.prototype.calculateState = function calculateState(position) { var props = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.props; var size = props.size, contentSize = props.contentSize, orientation = props.orientation; // unscrollable if (size < 1 || contentSize <= size) { return { position: 0, scrollable: false }; } var cachedStateKey = position + '_' + size + '_' + contentSize + '_' + orientation; if (this.cachedStateKey === cachedStateKey) { return this.cachedState; } // There are two types of positions here. // 1) Phisical position: changed by mouse or keyboard // 2) Logical position: changed by props. // The logical position will be kept as as internal state and the `render()` // function will translate it into physical position to render. var scale = size / contentSize; var faceSize = size * scale; if (faceSize < FACE_SIZE_MIN) { scale = (size - FACE_SIZE_MIN) / (contentSize - size); faceSize = FACE_SIZE_MIN; } var maxPosition = contentSize - size; position = clamp(position, 0, maxPosition); // eslint-disable-line // This function should only return flat values that can be // compared quiclky by `shallowEqual`. var state = { scale: scale, faceSize: faceSize, position: position, dragging: this.mouseMoveTracker ? this.mouseMoveTracker.isDragging() : false, horizontal: orientation === 'horizontal', scrollable: true // cache the state };this.cachedStateKey = cachedStateKey; this.cachedState = state; return state; }; Scrollbar.prototype.shouldHandleScrollChange = function shouldHandleScrollChange(delta) { var nextState = this.calculateState(this.state.position + delta); return nextState.position !== this.state.position; }; Scrollbar.prototype.blur = function blur() { if (!this.containerElem) { return; } try { this.onBlur(); this.containerElem.blur(); } catch (oops) { // pass } }; Scrollbar.prototype.scrollBy = function scrollBy(delta) { this.onWheel(delta); }; Scrollbar.prototype.render = function render() { var _classNames, _classNames2, _this2 = this; if (!this.state.scrollable) { return null; } var _props = this.props, prefixCls = _props.prefixCls, size = _props.size, opaque = _props.opaque; var faceSize = this.state.faceSize; var position = this.state.position * this.state.scale + FACE_MARGIN; var active = this.state.focused || this.state.dragging; var horizontal = this.state.horizontal; var mainClassName = classNames((_classNames = {}, _classNames[prefixCls + '-scroll'] = true, _classNames.vertical = !horizontal, _classNames.horizontal = horizontal, _classNames.opaque = opaque, _classNames.active = active, _classNames)); var faceClassName = classNames((_classNames2 = {}, _classNames2[prefixCls + '-scroll-face'] = true, _classNames2)); var mainStyle = void 0; var faceStyle = void 0; if (horizontal) { mainStyle = { left: this.props.left, width: size, height: SCROLLBAR_SIZE }; faceStyle = { width: faceSize - FACE_MARGIN_2X }; translateDOMPosition(faceStyle, position, 0, this.initialRender); } else { mainStyle = { top: this.props.top, width: SCROLLBAR_SIZE, height: size }; faceStyle = { height: faceSize - FACE_MARGIN_2X }; translateDOMPosition(faceStyle, 0, position, this.initialRender); } if (this.props.zIndex) { mainStyle.zIndex = this.props.zIndex; } if (this.props.trackColor === 'gray') { mainStyle.backgroundColor = '#f6f7f8'; } return React.createElement( 'div', { // eslint-disable-line style: mainStyle, className: mainClassName, tabIndex: 0 // eslint-disable-line , onBlur: this.onBlur, onFocus: this.onFocus, onKeyDown: this.onKeyDown, onMouseDown: this.onMouseDown, onWheel: this.wheelHandler.onWheel, ref: function ref(containerElem) { _this2.containerElem = containerElem; } }, React.createElement('div', { style: faceStyle, className: faceClassName, ref: function ref(faceElem) { _this2.faceElem = faceElem; } }) ); }; return Scrollbar; }(React.Component); Scrollbar.SIZE = SCROLLBAR_SIZE; Scrollbar.OFFSET = FACE_MARGIN / 2 + 1; Scrollbar.KEYBOARD_SCROLL_AMOUNT = KEYBOARD_SCROLL_AMOUNT; Scrollbar.propTypes = { size: PropTypes.number.isRequired, contentSize: PropTypes.number.isRequired, position: PropTypes.number, defaultPosition: PropTypes.number, opaque: PropTypes.bool, orientation: PropTypes.oneOf(['vertical', 'horizontal']), trackColor: PropTypes.oneOf(['gray']), zIndex: PropTypes.number, top: PropTypes.number, // top position of vertical bar left: PropTypes.number, // left position of horizontal bar onScroll: PropTypes.func }; Scrollbar.defaultProps = { defaultPosition: 0, opaque: true, zIndex: 99, orientation: 'vertical' }; export default Scrollbar;