UNPKG

@zippytech/react-scroll-container

Version:
529 lines (428 loc) 20.3 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _SIZES, _OTHER_SIZES, _MARGINS, _TRACK_SIDES; var _react = require('react'); var _react2 = _interopRequireDefault(_react); var _propTypes = require('prop-types'); var _propTypes2 = _interopRequireDefault(_propTypes); var _objectAssign = require('object-assign'); var _objectAssign2 = _interopRequireDefault(_objectAssign); var _autoBind = require('@zippytech/react-class/autoBind'); var _autoBind2 = _interopRequireDefault(_autoBind); var _reactCleanProps = require('@zippytech/react-clean-props'); var _reactCleanProps2 = _interopRequireDefault(_reactCleanProps); var _shouldComponentUpdate2 = require('./shouldComponentUpdate'); var _shouldComponentUpdate3 = _interopRequireDefault(_shouldComponentUpdate2); var _join = require('./join'); var _join2 = _interopRequireDefault(_join); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } /** * Copyright (c) 2015-present, Zippy Technologies * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ var HORIZONTAL = 'horizontal'; var VERTICAL = 'vertical'; var SIZES = (_SIZES = {}, _defineProperty(_SIZES, VERTICAL, 'height'), _defineProperty(_SIZES, HORIZONTAL, 'width'), _SIZES); var OTHER_SIZES = (_OTHER_SIZES = {}, _defineProperty(_OTHER_SIZES, VERTICAL, 'width'), _defineProperty(_OTHER_SIZES, HORIZONTAL, 'height'), _OTHER_SIZES); var MARGINS = (_MARGINS = {}, _defineProperty(_MARGINS, VERTICAL, 'right'), _defineProperty(_MARGINS, HORIZONTAL, 'bottom'), _MARGINS); var TRACK_SIDES = (_TRACK_SIDES = {}, _defineProperty(_TRACK_SIDES, VERTICAL, ['top', 'bottom']), _defineProperty(_TRACK_SIDES, HORIZONTAL, ['left', 'right']), _TRACK_SIDES); var STYLES = { vertical: { position: 'absolute', top: 0, bottom: 0, right: 0, contain: 'layout paint style' }, horizontal: { position: 'absolute', left: 0, right: 0, bottom: 0, contain: 'layout paint style' } }; var ZippyScrollbar = function (_Component) { _inherits(ZippyScrollbar, _Component); function ZippyScrollbar(props) { _classCallCheck(this, ZippyScrollbar); var _this = _possibleConstructorReturn(this, (ZippyScrollbar.__proto__ || Object.getPrototypeOf(ZippyScrollbar)).call(this, props)); (0, _autoBind2.default)(_this); _this.scrollPos = 0; _this.state = { scrollPos: 0 }; _this.refTrack = function (c) { _this.trackNode = c; }; _this.refThumb = function (c) { _this.thumbNode = c; }; return _this; } _createClass(ZippyScrollbar, [{ key: 'shouldComponentUpdate', value: function shouldComponentUpdate(nextProps, nextState) { return (0, _shouldComponentUpdate3.default)(this, nextProps, nextState); } }, { key: 'setVisible', value: function setVisible(visible) { this.visible = visible; // when trying to hide the scrollbar, but the mouse is over it // just don't hide it. Let setOver(false) do the job if (!visible && this.state.over) { return; } if (this.state.visible === visible) { return; } this.setState({ visible: visible }); } }, { key: 'setScrollPos', value: function setScrollPos(scrollPos) { this.scrollPos = scrollPos; var transform = this.getThumbTransform(); if (this.currentTransform == transform) { return; } // in order to squeeze in the most perf, // we manage this via direct style.transform assignment // and not via setState to trigger a render this.thumbNode.style.transform = this.currentTransform = transform; } }, { key: 'getScrollPos', value: function getScrollPos() { return this.scrollPos; } }, { key: 'render', value: function render() { var _assign; var props = this.props; var autoHide = props.autoHide; var className = props.className, style = props.style, orientation = props.orientation, vertical = props.vertical, horizontal = props.horizontal, thumbVisible = props.visible; if (orientation) { vertical = orientation == VERTICAL; horizontal = !vertical; } this.orientation = orientation = horizontal ? HORIZONTAL : VERTICAL; var sizeName = SIZES[orientation]; var otherSizeName = OTHER_SIZES[orientation]; if (autoHide === false) { thumbVisible = true; } else { if (this.state.visible !== undefined) { thumbVisible = this.state.visible; } if (this.state.active) { thumbVisible = true; } } var hiding = !thumbVisible && this.prevVisible; var showing = thumbVisible && !this.prevVisible; className = (0, _join2.default)(className, 'zippy-react-scroll-container__scrollbar', 'zippy-react-scroll-container__scrollbar--orientation-' + orientation, !thumbVisible && 'zippy-react-scroll-container__scrollbar--hidden', hiding && 'zippy-react-scroll-container__scrollbar--hiding', this.state.active && 'zippy-react-scroll-container__scrollbar--active', showing && 'zippy-react-scroll-container__scrollbar--showing'); style = (0, _objectAssign2.default)({}, style, STYLES[orientation]); if (showing && this.props.showTransitionDuration) { style.transitionDuration = this.props.showTransitionDuration; } if (hiding && this.props.hideTransitionDuration) { style.transitionDuration = this.props.hideTransitionDuration; } this.prevVisible = thumbVisible; var divProps = (0, _reactCleanProps2.default)(props, ZippyScrollbar.propTypes); var thumbSize = this.getThumbSize(); var transform = this.getThumbTransform(orientation, thumbSize); var scrollThumbOverWidth = Math.max(this.props.scrollThumbOverWidth, this.props.scrollThumbWidth); var thumbRadius = this.props.scrollThumbRadius !== undefined ? this.props.scrollThumbRadius : this.props.scrollThumbWidth; var thumbStyle = (0, _objectAssign2.default)({}, this.props.scrollThumbStyle, (_assign = {}, _defineProperty(_assign, sizeName, thumbSize), _defineProperty(_assign, otherSizeName, this.state.over || this.state.active || this.props.alwaysShowTrack ? scrollThumbOverWidth : this.props.scrollThumbWidth), _defineProperty(_assign, 'transform', transform), _defineProperty(_assign, 'borderRadius', thumbRadius), _defineProperty(_assign, 'transitionDuration', this.props.scrollTrackOverTransitionDuration), _assign)); var trackSides = TRACK_SIDES[orientation]; var scrollThumbMargin = this.props.scrollThumbMargin || 0; if (this.props.scrollThumbMargin) { style[MARGINS[orientation]] = scrollThumbMargin; if (this.props.scrollThumbStartEndRespectMargin) { style[trackSides[0]] = this.props.scrollThumbMargin; } } style[trackSides[1]] = scrollThumbMargin + (this.props.oppositeVisible ? this.props.scrollThumbWidth : 0); var thumbClassName = 'zippy-react-scroll-container__thumb zippy-react-scroll-container__thumb--orientation-' + orientation; var trackClassName = 'zippy-react-scroll-container__track zippy-react-scroll-container__track--orientation-' + orientation; var trackVisible = this.props.alwaysShowTrack || thumbVisible && this.state.over || this.props.showTrackOnDrag && this.state.active; if (trackVisible) { trackClassName += ' zippy-react-scroll-container__track--visible'; } var trackStyle = _defineProperty({ pointerEvents: 'none' }, otherSizeName, scrollThumbOverWidth); if (this.props.dragToScroll) { if (thumbVisible) { // when the thumb is visible, we should start // accepting mouseenter/mouseleave events, so activate pointer events trackStyle.pointerEvents = 'all'; } trackStyle.borderRadius = thumbRadius; trackClassName += ' zippy-react-scroll-container__track--drag-to-scroll'; if (this.state.active) { // set it to be same as thumb, so as not to flicker cursor when // dragging & moving outside the thumb region trackStyle.cursor = 'auto'; } } var onThumbMouseDown = thumbVisible && this.props.dragToScroll ? this.onThumbMouseDown : null; var onTrackClick = thumbVisible && this.props.dragToScroll ? this.onTrackClick : null; var onTrackWheel = this.props.dragToScroll && this.state.over ? this.onTrackWheel : null; return _react2.default.createElement( 'div', _extends({}, divProps, { style: style, className: className, 'data-orientation': orientation }), _react2.default.createElement( 'div', { ref: this.refTrack, style: trackStyle, className: trackClassName, onClick: onTrackClick, onWheel: onTrackWheel, onMouseEnter: this.props.dragToScroll ? this.onMouseEnter : null, onMouseLeave: this.props.dragToScroll ? this.onMouseLeave : null }, _react2.default.createElement('div', { ref: this.refThumb, style: thumbStyle, className: thumbClassName, onMouseDown: onThumbMouseDown }) ) ); } }, { key: 'setOver', value: function setOver(over) { var _this2 = this; var doSetOver = function doSetOver(overValue) { _this2.setState({ over: overValue }, function () { if (!overValue) { if (_this2.visible !== _this2.state.visible) { _this2.setVisible(_this2.visible); } } }); }; if (this.setOverFalseTimeoutId) { clearTimeout(this.setOverFalseTimeoutId); } if (!over) { this.setOverFalseTimeoutId = setTimeout(function () { doSetOver(false); }, 500); } else { this.setOverFalseTimeoutId = setTimeout(function () { doSetOver(true); }, 35); } } }, { key: 'onMouseEnter', value: function onMouseEnter() { this.setOver(true); } }, { key: 'onMouseLeave', value: function onMouseLeave() { this.setOver(false); } }, { key: 'onTrackWheel', value: function onTrackWheel(event) { // ideally the wheel event should be forwarded to the scroll node so scrolling happens naturally var delta = this.orientation == VERTICAL ? event.deltaY : event.deltaX; event.preventDefault(); // in order to prevent browser BACK navigation on mac this.props.onWheelScroll(this.orientation, delta, event); } }, { key: 'onTrackClick', value: function onTrackClick(event) { var target = event.target; if (target != this.trackNode) { return; } var rect = target.getBoundingClientRect(); var offsetX = event.clientX - rect.left; var offsetY = event.clientY - rect.top; var pos = this.orientation == VERTICAL ? offsetY : offsetX; var thumbPos = this.getThumbPosition(); var direction = pos > thumbPos ? 1 : -1; if (this.props.onPageScroll) { this.props.onPageScroll(this.orientation, direction); } } }, { key: 'getCoord', value: function getCoord(event) { return this.orientation == VERTICAL ? event.pageY : event.pageX; } }, { key: 'getThumbPosition', value: function getThumbPosition() { return this.scrollPosToThumbPos(this.getScrollPos(), this.orientation, this.getThumbSize()); } }, { key: 'onThumbMouseDown', value: function onThumbMouseDown(event) { event.preventDefault(); event.stopPropagation(); this.initialPos = this.getCoord(event); this.thumbSize = this.getThumbSize(); this.initialThumbPos = this.getThumbPosition(); this.setState({ active: true }); global.addEventListener('mousemove', this.onMouseMove); global.addEventListener('mouseup', this.onMouseUp); this.props.onStartDrag(this.orientation); } }, { key: 'onMouseMove', value: function onMouseMove(event) { var pos = this.getCoord(event); var diff = pos - this.initialPos; var scrollPos = this.thumbPosToScrollPos(this.initialThumbPos + diff, this.orientation, this.thumbSize); if (this.orientation == VERTICAL) { this.onScrollThumbScrollTop(scrollPos); } else { this.onScrollThumbScrollLeft(scrollPos); } } }, { key: 'onScrollThumbScrollTop', value: function onScrollThumbScrollTop(scrollPos) { this.props.onScrollThumbScrollTop(scrollPos); } }, { key: 'onScrollThumbScrollLeft', value: function onScrollThumbScrollLeft(scrollPos) { this.props.onScrollThumbScrollLeft(scrollPos); } }, { key: 'onMouseUp', value: function onMouseUp() { global.removeEventListener('mousemove', this.onMouseMove); global.removeEventListener('mouseup', this.onMouseUp); this.setState({ active: false }); this.props.onStopDrag(this.orientation); } }, { key: 'getThumbTransform', value: function getThumbTransform() { var orientation = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.orientation; var thumbSize = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.getThumbSize(); var scrollPos = this.scrollPosToThumbPos(this.getScrollPos(), orientation, thumbSize) || 0; if (orientation == VERTICAL) { return 'translate3d(0px, ' + scrollPos + 'px, 0px)'; } return 'translate3d(' + scrollPos + 'px, 0px, 0px)'; } }, { key: 'thumbPosToScrollPos', value: function thumbPosToScrollPos(thumbPos, orientation, thumbSize) { var _props = this.props, clientSize = _props.clientSize, scrollSize = _props.scrollSize, trackSize = _props.trackSize; var scrollPos = thumbPos * (scrollSize - clientSize) / (trackSize - thumbSize); return scrollPos; } }, { key: 'scrollPosToThumbPos', value: function scrollPosToThumbPos(scrollPos, orientation, thumbSize) { var _props2 = this.props, clientSize = _props2.clientSize, scrollSize = _props2.scrollSize, trackSize = _props2.trackSize; var thumbPos = scrollPos / (scrollSize - clientSize) * (trackSize - thumbSize); return Math.floor(thumbPos); } }, { key: 'getRatio', value: function getRatio() { return this.props.trackSize / this.props.clientSize; } }, { key: 'getThumbSize', value: function getThumbSize() { var _props3 = this.props, scrollSize = _props3.scrollSize, clientSize = _props3.clientSize; /* * thumbSize clientSize * --------- = -------- * trackSize scrollSize */ var thumbSize = scrollSize ? clientSize * clientSize / scrollSize : 0; thumbSize *= this.getRatio(); thumbSize = Math.max(thumbSize, this.props.scrollThumbMinSize); return thumbSize || 0; } }]); return ZippyScrollbar; }(_react.Component); exports.default = ZippyScrollbar; ZippyScrollbar.defaultProps = { onScrollThumbScrollTop: function onScrollThumbScrollTop() {}, onScrollThumbScrollLeft: function onScrollThumbScrollLeft() {}, onStartDrag: function onStartDrag() {}, onStopDrag: function onStopDrag() {}, scrollThumbMargin: 2, scrollThumbMinSize: 10, showTrackOnDrag: false }; ZippyScrollbar.propTypes = { alwaysShowTrack: _propTypes2.default.bool, autoHide: _propTypes2.default.bool, clientSize: _propTypes2.default.number, dragToScroll: _propTypes2.default.bool, emptyScrollOffset: _propTypes2.default.number, hideTransitionDuration: _propTypes2.default.string, horizontal: _propTypes2.default.bool, showTrackOnDrag: _propTypes2.default.bool, onStartDrag: _propTypes2.default.func, onStopDrag: _propTypes2.default.func, onPageScroll: _propTypes2.default.func, onScrollThumbScrollLeft: _propTypes2.default.func, onScrollThumbScrollTop: _propTypes2.default.func, onWheelScroll: _propTypes2.default.func, oppositeVisible: _propTypes2.default.bool, // true when the other scrollbar is also visible orientation: _propTypes2.default.oneOf([VERTICAL, HORIZONTAL]), nativeScrollbarWidth: _propTypes2.default.number.isRequired, scrollSize: _propTypes2.default.number, scrollThumbMargin: _propTypes2.default.number.isRequired, scrollThumbMinSize: _propTypes2.default.number, scrollThumbOverWidth: _propTypes2.default.number, scrollThumbRadius: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.number]), scrollThumbStartEndRespectMargin: _propTypes2.default.bool, scrollThumbStyle: _propTypes2.default.shape(), scrollThumbWidth: _propTypes2.default.number, scrollTrackOverTransitionDuration: _propTypes2.default.string, showTransitionDuration: _propTypes2.default.string, size: _propTypes2.default.number, trackSize: _propTypes2.default.number, vertical: _propTypes2.default.bool, visible: _propTypes2.default.bool };