UNPKG

tonkean-react-calendar-timeline

Version:
230 lines (201 loc) 6.77 kB
import React, { Component } from 'react' import PropTypes from 'prop-types' import { getParentPosition } from '../utility/dom-helpers' class ScrollElement extends Component { static propTypes = { children: PropTypes.element.isRequired, width: PropTypes.number.isRequired, height: PropTypes.number.isRequired, traditionalZoom: PropTypes.bool.isRequired, scrollRef: PropTypes.func.isRequired, isInteractingWithItem: PropTypes.bool.isRequired, onMouseEnter: PropTypes.func.isRequired, onMouseMove: PropTypes.func.isRequired, onMouseLeave: PropTypes.func.isRequired, onContextMenu: PropTypes.func.isRequired, onZoom: PropTypes.func.isRequired, onWheelZoom: PropTypes.func.isRequired, onScroll: PropTypes.func.isRequired } constructor() { super() this.state = { isDragging: false } } refHandler = el => { this.scrollComponent = el this.props.scrollRef(el) } // TODO: fix this where this is called when scrollLeft is set handleScroll = () => { const { width } = this.props const scrollComponent = this.scrollComponent const scrollX = scrollComponent.scrollLeft // move the virtual canvas if needed // if scrollX is less...i dont know how to explain the logic here if (scrollX < width * 0.5) { scrollComponent.scrollLeft += width } if (scrollX > width * 1.5) { scrollComponent.scrollLeft -= width } this.props.onScroll(scrollX) } handleWheel = e => { const { traditionalZoom } = this.props e.preventDefault() // zoom in the time dimension if (e.ctrlKey || e.metaKey || e.altKey) { const parentPosition = getParentPosition(e.currentTarget) const xPosition = e.clientX - parentPosition.x const speed = e.ctrlKey ? 10 : e.metaKey ? 3 : 1 // convert vertical zoom to horiziontal this.props.onWheelZoom(speed, xPosition, e.deltaY) } else if (e.shiftKey) { // shift+scroll event from a touchpad has deltaY property populated; shift+scroll event from a mouse has deltaX this.scrollComponent.scrollLeft += e.deltaY || e.deltaX // no modifier pressed? we prevented the default event, so scroll or zoom as needed } else { if (e.deltaX !== 0) { if (!traditionalZoom) { this.scrollComponent.scrollLeft += e.deltaX } } if (e.deltaY !== 0) { window.scrollTo(window.pageXOffset, window.pageYOffset + e.deltaY) if (traditionalZoom) { const parentPosition = getParentPosition(e.currentTarget) const xPosition = e.clientX - parentPosition.x this.props.onWheelZoom(10, xPosition, e.deltaY) } } } } handleMouseDown = e => { if (e.button === 0) { this.dragStartPosition = e.pageX this.dragLastPosition = e.pageX this.setState({ isDragging: true }) } } handleMouseMove = e => { this.props.onMouseMove(e) //why is interacting with item important? if (this.state.isDragging && !this.props.isInteractingWithItem) { this.scrollComponent.scrollLeft += this.dragLastPosition - e.pageX this.dragLastPosition = e.pageX } } handleMouseUp = () => { this.dragStartPosition = null this.dragLastPosition = null this.setState({ isDragging: false }) } handleMouseLeave = e => { this.props.onMouseLeave(e) this.dragStartPosition = null this.dragLastPosition = null this.setState({ isDragging: false }) } handleTouchStart = e => { if (e.touches.length === 2) { e.preventDefault() this.lastTouchDistance = Math.abs( e.touches[0].screenX - e.touches[1].screenX ) this.singleTouchStart = null this.lastSingleTouch = null } else if (e.touches.length === 1) { e.preventDefault() let x = e.touches[0].clientX let y = e.touches[0].clientY this.lastTouchDistance = null this.singleTouchStart = { x: x, y: y, screenY: window.pageYOffset } this.lastSingleTouch = { x: x, y: y, screenY: window.pageYOffset } } } handleTouchMove = e => { const { isInteractingWithItem, width, onZoom } = this.props if (isInteractingWithItem) { e.preventDefault() return } if (this.lastTouchDistance && e.touches.length === 2) { e.preventDefault() let touchDistance = Math.abs(e.touches[0].screenX - e.touches[1].screenX) let parentPosition = getParentPosition(e.currentTarget) let xPosition = (e.touches[0].screenX + e.touches[1].screenX) / 2 - parentPosition.x if (touchDistance !== 0 && this.lastTouchDistance !== 0) { onZoom(this.lastTouchDistance / touchDistance, xPosition / width) this.lastTouchDistance = touchDistance } } else if (this.lastSingleTouch && e.touches.length === 1) { e.preventDefault() let x = e.touches[0].clientX let y = e.touches[0].clientY let deltaX = x - this.lastSingleTouch.x let deltaX0 = x - this.singleTouchStart.x let deltaY0 = y - this.singleTouchStart.y this.lastSingleTouch = { x: x, y: y } let moveX = Math.abs(deltaX0) * 3 > Math.abs(deltaY0) let moveY = Math.abs(deltaY0) * 3 > Math.abs(deltaX0) if (deltaX !== 0 && moveX) { this.scrollComponent.scrollLeft -= deltaX } if (moveY) { window.scrollTo( window.pageXOffset, this.singleTouchStart.screenY - deltaY0 ) } } } handleTouchEnd = () => { if (this.lastTouchDistance) { this.lastTouchDistance = null } if (this.lastSingleTouch) { this.lastSingleTouch = null this.singleTouchStart = null } } render() { const { width, height, children, onContextMenu, onMouseEnter } = this.props const { isDragging } = this.state const scrollComponentStyle = { width: `${width}px`, height: `${height + 20}px`, //20px to push the scroll element down off screen...? cursor: isDragging ? 'move' : 'default' } return ( <div ref={this.refHandler} data-test-id="scroll-element" className="rct-scroll" style={scrollComponentStyle} onScroll={this.handleScroll} onWheel={this.handleWheel} onMouseDown={this.handleMouseDown} onMouseMove={this.handleMouseMove} onMouseUp={this.handleMouseUp} onMouseLeave={this.handleMouseLeave} onTouchStart={this.handleTouchStart} onTouchMove={this.handleTouchMove} onTouchEnd={this.handleTouchEnd} onContextMenu={onContextMenu} onMouseEnter={onMouseEnter} > {children} </div> ) } } export default ScrollElement