UNPKG

@progress/kendo-charts

Version:

Kendo UI platform-independent Charts library

739 lines (621 loc) 22.2 kB
// todo: extract to a separate place import { Class, deepExtend, addClass, Observable, isFunction, setDefaultOptions, on, off, UserEvents } from '../../common'; import { convertToHtml, prepend, wrapInner, hasNativeScrolling, wheelDeltaY, proxy, setDefaultEvents } from '../utils'; import { Transition, Animation } from './fx'; import { Pane, PaneDimensions, Movable, TapCapture } from './draggable'; var extend = Object.assign, abs = Math.abs, SNAPBACK_DURATION = 500, SCROLLBAR_OPACITY = 0.7, FRICTION = 0.96, VELOCITY_MULTIPLIER = 10, MAX_VELOCITY = 55, OUT_OF_BOUNDS_FRICTION = 0.5, ANIMATED_SCROLLER_PRECISION = 5, // SCROLLER_RELEASE_CLASS = 'km-scroller-release', // SCROLLER_REFRESH_CLASS = 'km-scroller-refresh', PULL = 'pull', CHANGE = 'change', RESIZE = 'resize', SCROLL = 'scroll', MOUSE_WHEEL_ID = 2; var ZoomSnapBack = (function (Animation) { function ZoomSnapBack(options) { Animation.call(this, options); var that = this; extend(that, options); that.userEvents.bind('gestureend', that.start.bind(this)); that.tapCapture.bind('press', that.cancel.bind(this)); } if ( Animation ) ZoomSnapBack.__proto__ = Animation; ZoomSnapBack.prototype = Object.create( Animation && Animation.prototype ); ZoomSnapBack.prototype.constructor = ZoomSnapBack; ZoomSnapBack.prototype.enabled = function enabled () { return this.movable.scale < this.dimensions.minScale; }; ZoomSnapBack.prototype.done = function done () { return this.dimensions.minScale - this.movable.scale < 0.01; }; ZoomSnapBack.prototype.tick = function tick () { var movable = this.movable; movable.scaleWith(1.1); this.dimensions.rescale(movable.scale); }; ZoomSnapBack.prototype.onEnd = function onEnd () { var movable = this.movable; movable.scaleTo(this.dimensions.minScale); this.dimensions.rescale(movable.scale); }; return ZoomSnapBack; }(Animation)); var DragInertia = (function (Animation) { function DragInertia(options) { Animation.call(this); var that = this; extend(that, options, { transition: new Transition({ axis: options.axis, movable: options.movable, onEnd: function onEnd() { that._end(); } }) }); that.tapCapture.bind('press', function() { that.cancel(); }); that.userEvents.bind('end', proxy(that.start, that)); that.userEvents.bind('gestureend', proxy(that.start, that)); that.userEvents.bind('tap', proxy(that.onEnd, that)); } if ( Animation ) DragInertia.__proto__ = Animation; DragInertia.prototype = Object.create( Animation && Animation.prototype ); DragInertia.prototype.constructor = DragInertia; DragInertia.prototype.onCancel = function onCancel () { this.transition.cancel(); }; DragInertia.prototype.freeze = function freeze (location) { var that = this; that.cancel(); that._moveTo(location); }; DragInertia.prototype.onEnd = function onEnd () { var that = this; if (that.paneAxis.outOfBounds()) { that._snapBack(); } else { that._end(); } }; DragInertia.prototype.done = function done () { return abs(this.velocity) < 1; }; DragInertia.prototype.start = function start (e) { var that = this, velocity; if (!that.dimension.enabled) { return; } if (that.paneAxis.outOfBounds()) { if (that.transition._started) { that.transition.cancel(); that.velocity = Math.min(e.touch[that.axis].velocity * that.velocityMultiplier, MAX_VELOCITY); Animation.prototype.start.call(this); } else { that._snapBack(); } } else { velocity = e.touch.id === MOUSE_WHEEL_ID ? 0 : e.touch[that.axis].velocity; that.velocity = Math.max(Math.min(velocity * that.velocityMultiplier, MAX_VELOCITY), -MAX_VELOCITY); that.tapCapture.captureNext(); Animation.prototype.start.call(this); } }; DragInertia.prototype.tick = function tick () { var that = this, dimension = that.dimension, friction = that.paneAxis.outOfBounds() ? OUT_OF_BOUNDS_FRICTION : that.friction, delta = that.velocity *= friction, location = that.movable[that.axis] + delta; if (!that.elastic && dimension.outOfBounds(location)) { location = Math.max(Math.min(location, dimension.max), dimension.min); that.velocity = 0; } that.movable.moveAxis(that.axis, location); }; DragInertia.prototype._end = function _end () { this.tapCapture.cancelCapture(); this.end(); }; DragInertia.prototype._snapBack = function _snapBack () { var that = this, dimension = that.dimension, snapBack = that.movable[that.axis] > dimension.max ? dimension.max : dimension.min; that._moveTo(snapBack); }; DragInertia.prototype._moveTo = function _moveTo (location) { this.transition.moveTo({ location: location, duration: SNAPBACK_DURATION, ease: Transition.easeOutExpo }); }; return DragInertia; }(Animation)); var AnimatedScroller = (function (Animation) { function AnimatedScroller(options) { Animation.call(this, options); var that = this; extend(that, options, { origin: {}, destination: {}, offset: {} }); } if ( Animation ) AnimatedScroller.__proto__ = Animation; AnimatedScroller.prototype = Object.create( Animation && Animation.prototype ); AnimatedScroller.prototype.constructor = AnimatedScroller; AnimatedScroller.prototype.tick = function tick () { this._updateCoordinates(); this.moveTo(this.origin); }; AnimatedScroller.prototype.done = function done () { return abs(this.offset.y) < ANIMATED_SCROLLER_PRECISION && abs(this.offset.x) < ANIMATED_SCROLLER_PRECISION; }; AnimatedScroller.prototype.onEnd = function onEnd () { this.moveTo(this.destination); if (this.callback) { this.callback.call(); } }; AnimatedScroller.prototype.setCoordinates = function setCoordinates (from, to) { this.offset = {}; this.origin = from; this.destination = to; }; /* eslint-disable no-param-reassign */ AnimatedScroller.prototype.setCallback = function setCallback (callback) { if (callback && isFunction(callback)) { this.callback = callback; } else { callback = undefined; } }; /* eslint-enable no-param-reassign */ AnimatedScroller.prototype._updateCoordinates = function _updateCoordinates () { this.offset = { x: (this.destination.x - this.origin.x) / 4, y: (this.destination.y - this.origin.y) / 4 }; this.origin = { y: this.origin.y + this.offset.y, x: this.origin.x + this.offset.x }; }; return AnimatedScroller; }(Animation)); var ScrollBar = (function (Class) { function ScrollBar(options) { Class.call(this); var that = this, horizontal = options.axis === 'x'; var orientation = (horizontal ? 'horizontal' : 'vertical'); var element = convertToHtml('<div class="km-touch-scrollbar km-' + orientation + '-scrollbar" />'); extend(that, options, { element: element, elementSize: 0, movable: new Movable(element), scrollMovable: options.movable, alwaysVisible: options.alwaysVisible, size: horizontal ? 'width' : 'height' }); that.scrollMovable.bind(CHANGE, that.refresh.bind(that)); that.container.appendChild(element); if (options.alwaysVisible) { that.show(); } } if ( Class ) ScrollBar.__proto__ = Class; ScrollBar.prototype = Object.create( Class && Class.prototype ); ScrollBar.prototype.constructor = ScrollBar; ScrollBar.prototype.refresh = function refresh () { var that = this, axis = that.axis, dimension = that.dimension, paneSize = dimension.size, scrollMovable = that.scrollMovable, sizeRatio = paneSize / dimension.total, position = Math.round(-scrollMovable[axis] * sizeRatio), size = Math.round(paneSize * sizeRatio); if (sizeRatio >= 1) { this.element.style.display = "none"; } else { this.element.style.display = ""; } if (position + size > paneSize) { size = paneSize - position; } else if (position < 0) { size += position; position = 0; } if (that.elementSize !== size) { that.element.style[that.size] = size + 'px'; that.elementSize = size; } that.movable.moveAxis(axis, position); }; ScrollBar.prototype.show = function show () { this.element.style.opacity = SCROLLBAR_OPACITY; this.element.style.visibility = "visible"; }; ScrollBar.prototype.hide = function hide () { if (!this.alwaysVisible) { this.element.style.opacity = 0; } }; return ScrollBar; }(Class)); // export class Scroller extends Class { export var Scroller = (function (Observable) { function Scroller(element, options) { Observable.call(this); var that = this; this.element = element; this._initOptions(options); var hasScrolling = hasNativeScrolling(navigator.userAgent); that._native = that.options.useNative && hasScrolling; var scrollHeader = convertToHtml('<div class="km-scroll-header"/>'); if (that._native) { addClass(element, 'km-native-scroller'); prepend(scrollHeader, element); extend(that, { scrollElement: element, fixedContainer: element.children[0] }); return; } element.style.overflow = "hidden"; addClass(element, 'km-scroll-wrapper'); var scrollContainer = convertToHtml('<div class="km-scroll-container"/>'); wrapInner(element, scrollContainer); prepend(scrollHeader, element); var inner = element.children[1], tapCapture = new TapCapture(element), movable = new Movable(inner), dimensions = new PaneDimensions({ element: inner, container: element, forcedEnabled: that.options.zoom }), avoidScrolling = this.options.avoidScrolling, userEvents = new UserEvents(element, { touchAction: 'none', allowSelection: true, preventDragEvent: true, captureUpIfMoved: true, multiTouch: that.options.zoom, supportDoubleTap: that.options.supportDoubleTap, start: function start(e) { dimensions.refresh(); var velocityX = abs(e.x.velocity), velocityY = abs(e.y.velocity), horizontalSwipe = velocityX * 2 >= velocityY, originatedFromFixedContainer = that.fixedContainer.contains(e.event.target), verticalSwipe = velocityY * 2 >= velocityX; if (!originatedFromFixedContainer && !avoidScrolling(e) && that.enabled && (dimensions.x.enabled && horizontalSwipe || dimensions.y.enabled && verticalSwipe)) { userEvents.capture(); } else { userEvents.cancel(); } } }), pane = new Pane({ movable: movable, dimensions: dimensions, userEvents: userEvents, elastic: that.options.elastic }), zoomSnapBack = new ZoomSnapBack({ movable: movable, dimensions: dimensions, userEvents: userEvents, tapCapture: tapCapture }), animatedScroller = new AnimatedScroller({ moveTo: function moveTo(coordinates) { that.scrollTo(coordinates.x, coordinates.y); } }); movable.bind(CHANGE, function() { that.scrollTop = -movable.y; that.scrollLeft = -movable.x; that.trigger(SCROLL, { scrollTop: that.scrollTop, scrollLeft: that.scrollLeft }); }); if (that.options.mousewheelScrolling) { this._wheelScrollHandler = this._wheelScroll.bind(this); on(element, 'wheel', this._wheelScrollHandler); } extend(that, { movable: movable, dimensions: dimensions, zoomSnapBack: zoomSnapBack, animatedScroller: animatedScroller, userEvents: userEvents, pane: pane, tapCapture: tapCapture, pulled: false, enabled: true, scrollElement: inner, scrollTop: 0, scrollLeft: 0, fixedContainer: element.children[0] }); that._initAxis('x'); that._initAxis('y'); that._wheelEnd = function() { that._wheel = false; that.userEvents.end(0, that._wheelY); }; dimensions.refresh(); if (that.options.pullToRefresh) { that._initPullToRefresh(); } } if ( Observable ) Scroller.__proto__ = Observable; Scroller.prototype = Object.create( Observable && Observable.prototype ); Scroller.prototype.constructor = Scroller; Scroller.prototype._initOptions = function _initOptions (options) { this.options = deepExtend({}, this.options, options); }; Scroller.prototype._wheelScroll = function _wheelScroll (e) { if (e.ctrlKey) { return; } if (!this._wheel) { this._wheel = true; this._wheelY = 0; this.userEvents.press(0, this._wheelY); } clearTimeout(this._wheelTimeout); this._wheelTimeout = setTimeout(this._wheelEnd, 50); var delta = wheelDeltaY(e); if (delta) { this._wheelY += delta; this.userEvents.move(0, this._wheelY); } e.preventDefault(); }; Scroller.prototype.makeVirtual = function makeVirtual () { this.dimensions.y.makeVirtual(); }; Scroller.prototype.virtualSize = function virtualSize (min, max) { this.dimensions.y.virtualSize(min, max); }; Scroller.prototype.height = function height () { return this.dimensions.y.size; }; Scroller.prototype.scrollHeight = function scrollHeight () { return this.scrollElement.scrollHeight; }; Scroller.prototype.scrollWidth = function scrollWidth () { return this.scrollElement.scrollWidth; }; Scroller.prototype._resize = function _resize () { if (!this._native) { this.contentResized(); } }; Scroller.prototype.setOptions = function setOptions (options) { var that = this; this._initOptions(options); if (options.pullToRefresh) { that._initPullToRefresh(); } }; Scroller.prototype.reset = function reset () { if (this._native) { this.scrollElement.scrollTop(0); } else { this.movable.moveTo({ x: 0, y: 0 }); this._scale(1); } }; Scroller.prototype.contentResized = function contentResized () { this.dimensions.refresh(); if (this.pane.x.outOfBounds()) { this.movable.moveAxis('x', this.dimensions.x.min); } if (this.pane.y.outOfBounds()) { this.movable.moveAxis('y', this.dimensions.y.min); } }; Scroller.prototype.zoomOut = function zoomOut () { var dimensions = this.dimensions; dimensions.refresh(); this._scale(dimensions.fitScale); this.movable.moveTo(dimensions.centerCoordinates()); }; Scroller.prototype.enable = function enable () { this.enabled = true; }; Scroller.prototype.disable = function disable () { this.enabled = false; }; Scroller.prototype.scrollTo = function scrollTo (x, y) { if (this._native) { this.scrollElement.scrollLeft(abs(x)); this.scrollElement.scrollTop(abs(y)); } else { this.dimensions.refresh(); this.movable.moveTo({ x: x, y: y }); } }; Scroller.prototype.animatedScrollTo = function animatedScrollTo (x, y, callback) { var from, to; if (this._native) { this.scrollTo(x, y); } else { from = { x: this.movable.x, y: this.movable.y }; to = { x: x, y: y }; this.animatedScroller.setCoordinates(from, to); this.animatedScroller.setCallback(callback); this.animatedScroller.start(); } }; // kept for API compatibility, not used Scroller.prototype.pullHandled = function pullHandled () { // let that = this; // removeClass(that.refreshHint, SCROLLER_REFRESH_CLASS); // that.hintContainer.innerHTML = that.pullTemplate({})); // that.yinertia.onEnd(); // that.xinertia.onEnd(); // that.userEvents.cancel(); }; Scroller.prototype.destroy = function destroy () { var element = this.element; off(element, 'wheel', this._wheelScrollHandler); if (this.userEvents) { this.userEvents.destroy(); } }; Scroller.prototype._scale = function _scale (scale) { this.dimensions.rescale(scale); this.movable.scaleTo(scale); }; Scroller.prototype._initPullToRefresh = function _initPullToRefresh () { }; // kept for API compatibility, not used Scroller.prototype._dragEnd = function _dragEnd () { // let that = this; // if (!that.pulled) { // return; // } // that.pulled = false; // removeClass(that.refreshHint, SCROLLER_RELEASE_CLASS); // addClass(that.refreshHint, SCROLLER_REFRESH_CLASS); // that.hintContainer.innerHTML = that.refreshTemplate({}); // that.yinertia.freeze(that.options.pullOffset / 2); // that.trigger('pull'); }; // kept for API compatibility, not used Scroller.prototype._paneChange = function _paneChange () { // let that = this; // if (that.movable.y / OUT_OF_BOUNDS_FRICTION > that.options.pullOffset) { // if (!that.pulled) { // that.pulled = true; // that.refreshHint.removeClass(SCROLLER_REFRESH_CLASS).addClass(SCROLLER_RELEASE_CLASS); // that.hintContainer.html(that.releaseTemplate({})); // that.hintContainer.html(that.releaseTemplate({})); // } // } else if (that.pulled) { // that.pulled = false; // that.refreshHint.removeClass(SCROLLER_RELEASE_CLASS); // that.hintContainer.html(that.pullTemplate({})); // } }; Scroller.prototype._initAxis = function _initAxis (axis) { var that = this, movable = that.movable, dimension = that.dimensions[axis], tapCapture = that.tapCapture, paneAxis = that.pane[axis], scrollBar = new ScrollBar({ axis: axis, movable: movable, dimension: dimension, container: that.element, alwaysVisible: that.options.visibleScrollHints }); dimension.bind(CHANGE, function() { scrollBar.refresh(); }); paneAxis.bind(CHANGE, function() { scrollBar.show(); }); that[axis + 'inertia'] = new DragInertia({ axis: axis, paneAxis: paneAxis, movable: movable, tapCapture: tapCapture, userEvents: that.userEvents, dimension: dimension, elastic: that.options.elastic, friction: that.options.friction || FRICTION, velocityMultiplier: that.options.velocityMultiplier || VELOCITY_MULTIPLIER, end: function end() { scrollBar.hide(); that.trigger('scrollEnd', { axis: axis, scrollTop: that.scrollTop, scrollLeft: that.scrollLeft }); } }); }; return Scroller; }(Observable)); setDefaultOptions(Scroller, { name: 'Scroller', zoom: false, pullOffset: 140, visibleScrollHints: false, elastic: true, useNative: false, mousewheelScrolling: true, avoidScrolling: function avoidScrolling() { return false; }, pullToRefresh: false, messages: { pullTemplate: 'Pull to refresh', releaseTemplate: 'Release to refresh', refreshTemplate: 'Refreshing' } }); setDefaultEvents(Scroller, [ PULL, SCROLL, RESIZE ]);