diff --git a/lib/module/hammer.js b/lib/module/hammer.js index f76b3e39..a2679764 100644 --- a/lib/module/hammer.js +++ b/lib/module/hammer.js @@ -1,7 +1,9 @@ // Only load hammer.js when in a browser environment // (loading hammer.js in a node.js environment gives errors) if (typeof window !== 'undefined') { - module.exports = window['Hammer'] || require('hammerjs'); + var Hammer = window['Hammer'] || require('hammerjs'); + var propagating = require('propagating-hammerjs'); + module.exports = propagating(Hammer); } else { module.exports = function () { diff --git a/lib/timeline/Core.js b/lib/timeline/Core.js index a7121973..8bceb003 100644 --- a/lib/timeline/Core.js +++ b/lib/timeline/Core.js @@ -89,10 +89,6 @@ Core.prototype._create = function (container) { this.dom.rightContainer.appendChild(this.dom.shadowBottomRight); this.on('rangechange', this.redraw.bind(this)); - this.on('touch', this._onTouch.bind(this)); - this.on('pinch', this._onPinch.bind(this)); - this.on('dragstart', this._onDragStart.bind(this)); - this.on('drag', this._onDrag.bind(this)); var me = this; this.on('change', function (properties) { @@ -113,44 +109,42 @@ Core.prototype._create = function (container) { // create event listeners for all interesting events, these events will be // emitted via emitter - this.hammer = new Hammer(this.dom.root); + this.hammer = new Hammer(this.dom.root, {touchAction: 'pan-y'}); + this.hammer.get('pinch').set({enable: true}); this.listeners = {}; var events = [ 'tap', 'doubletap', 'press', 'pinch', - 'panstart', 'panmove', 'panend' - // TODO: mouse wheel? + 'pan', 'panstart', 'panmove', 'panend' // TODO: cleanup //'touch', 'pinch', //'tap', 'doubletap', 'hold', //'dragstart', 'drag', 'dragend', //'mousewheel', 'DOMMouseScroll' // DOMMouseScroll is needed for Firefox ]; - events.forEach(function (event) { - var listener = function () { - var args = [event].concat(Array.prototype.slice.call(arguments, 0)); + events.forEach(function (type) { + var listener = function (event) { if (me.isActive()) { - me.emit.apply(me, args); + me.emit(type, event); } }; - me.hammer.on(event, listener); - me.listeners[event] = listener; + me.hammer.on(type, listener); + me.listeners[type] = listener; }); + // emulate a touch event (emitted before the start of a pan, pinch, tap, or press) this.hammer.on('hammer.input', function (event) { if (event.isFirst) { - var args = ['touch'].concat(Array.prototype.slice.call(arguments, 0)); if (me.isActive()) { - me.emit.apply(me, args); + me.emit('touch', event); } } }.bind(this)); - function onMouseWheel() { - var args = ['mousewheel'].concat(Array.prototype.slice.call(arguments, 0)); + function onMouseWheel(event) { if (me.isActive()) { - me.emit.apply(me, args); + me.emit('mousewheel', event); } } this.dom.root.addEventListener('mousewheel', onMouseWheel); @@ -172,7 +166,6 @@ Core.prototype._create = function (container) { scrollTop: 0, scrollTopMin: 0 }; - this.touch = {}; // store state information needed for touch events this.redrawCount = 0; @@ -794,55 +787,6 @@ Core.prototype._stopAutoResize = function () { this._onResize = null; }; -/** - * Start moving the timeline vertically - * @param {Event} event - * @private - */ -Core.prototype._onTouch = function (event) { - this.touch.allowDragging = true; -}; - -/** - * Start moving the timeline vertically - * @param {Event} event - * @private - */ -Core.prototype._onPinch = function (event) { - this.touch.allowDragging = false; -}; - -/** - * Start moving the timeline vertically - * @param {Event} event - * @private - */ -Core.prototype._onDragStart = function (event) { - this.touch.initialScrollTop = this.props.scrollTop; -}; - -/** - * Move the timeline vertically - * @param {Event} event - * @private - */ -Core.prototype._onDrag = function (event) { - // refuse to drag when we where pinching to prevent the timeline make a jump - // when releasing the fingers in opposite order from the touch screen - if (!this.touch.allowDragging) return; - - var delta = event.deltaY; - - var oldScrollTop = this._getScrollTop(); - var newScrollTop = this._setScrollTop(this.touch.initialScrollTop + delta); - - - if (newScrollTop != oldScrollTop) { - this.redraw(); // TODO: this causes two redraws when dragging, the other is triggered by rangechange already - this.emit("verticalDrag"); - } -}; - /** * Apply a scrollTop * @param {Number} scrollTop diff --git a/lib/timeline/Range.js b/lib/timeline/Range.js index f35b76b3..996d580d 100644 --- a/lib/timeline/Range.js +++ b/lib/timeline/Range.js @@ -363,6 +363,8 @@ Range.prototype._onDragStart = function(event) { if (this.body.dom.root) { this.body.dom.root.style.cursor = 'move'; } + + event.preventDefault(); }; /** @@ -413,6 +415,8 @@ Range.prototype._onDrag = function (event) { start: new Date(this.start), end: new Date(this.end) }); + + event.preventDefault(); }; /** @@ -521,41 +525,41 @@ Range.prototype._onPinch = function (event) { this.props.touch.allowDragging = false; - if (event.touches.length > 1) { - if (!this.props.touch.center) { - this.props.touch.center = getPointer(event.center, this.body.dom.center); - } + if (!this.props.touch.center) { + this.props.touch.center = getPointer(event.center, this.body.dom.center); + } - var scale = 1 / (event.scale + this.scaleOffset); - var centerDate = this._pointerToDate(this.props.touch.center); - - var hiddenDuration = DateUtil.getHiddenDurationBetween(this.body.hiddenDates, this.start, this.end); - var hiddenDurationBefore = DateUtil.getHiddenDurationBefore(this.body.hiddenDates, this, centerDate); - var hiddenDurationAfter = hiddenDuration - hiddenDurationBefore; - - // calculate new start and end - var newStart = (centerDate - hiddenDurationBefore) + (this.props.touch.start - (centerDate - hiddenDurationBefore)) * scale; - var newEnd = (centerDate + hiddenDurationAfter) + (this.props.touch.end - (centerDate + hiddenDurationAfter)) * scale; - - // snapping times away from hidden zones - this.startToFront = 1 - scale > 0 ? false : true; // used to do the right autocorrection with periodic hidden times - this.endToFront = scale - 1 > 0 ? false : true; // used to do the right autocorrection with periodic hidden times - - var safeStart = DateUtil.snapAwayFromHidden(this.body.hiddenDates, newStart, 1 - scale, true); - var safeEnd = DateUtil.snapAwayFromHidden(this.body.hiddenDates, newEnd, scale - 1, true); - if (safeStart != newStart || safeEnd != newEnd) { - this.props.touch.start = safeStart; - this.props.touch.end = safeEnd; - this.scaleOffset = 1 - event.scale; - newStart = safeStart; - newEnd = safeEnd; - } + var scale = 1 / (event.scale + this.scaleOffset); + var centerDate = this._pointerToDate(this.props.touch.center); - this.setRange(newStart, newEnd); + var hiddenDuration = DateUtil.getHiddenDurationBetween(this.body.hiddenDates, this.start, this.end); + var hiddenDurationBefore = DateUtil.getHiddenDurationBefore(this.body.hiddenDates, this, centerDate); + var hiddenDurationAfter = hiddenDuration - hiddenDurationBefore; + + // calculate new start and end + var newStart = (centerDate - hiddenDurationBefore) + (this.props.touch.start - (centerDate - hiddenDurationBefore)) * scale; + var newEnd = (centerDate + hiddenDurationAfter) + (this.props.touch.end - (centerDate + hiddenDurationAfter)) * scale; + + // snapping times away from hidden zones + this.startToFront = 1 - scale <= 0; // used to do the right auto correction with periodic hidden times + this.endToFront = scale - 1 <= 0; // used to do the right auto correction with periodic hidden times - this.startToFront = false; // revert to default - this.endToFront = true; // revert to default + var safeStart = DateUtil.snapAwayFromHidden(this.body.hiddenDates, newStart, 1 - scale, true); + var safeEnd = DateUtil.snapAwayFromHidden(this.body.hiddenDates, newEnd, scale - 1, true); + if (safeStart != newStart || safeEnd != newEnd) { + this.props.touch.start = safeStart; + this.props.touch.end = safeEnd; + this.scaleOffset = 1 - event.scale; + newStart = safeStart; + newEnd = safeEnd; } + + this.setRange(newStart, newEnd); + + this.startToFront = false; // revert to default + this.endToFront = true; // revert to default + + event.preventDefault(); }; /** diff --git a/lib/timeline/component/CustomTime.js b/lib/timeline/component/CustomTime.js index a42e1edf..982295fd 100644 --- a/lib/timeline/component/CustomTime.js +++ b/lib/timeline/component/CustomTime.js @@ -70,8 +70,11 @@ CustomTime.prototype._create = function() { // attach event listeners this.hammer = new Hammer(drag); this.hammer.on('panstart', this._onDragStart.bind(this)); - this.hammer.on('panmove', this._onDrag.bind(this)); + this.hammer.on('panmove', this._onDrag.bind(this)); this.hammer.on('panend', this._onDragEnd.bind(this)); + this.hammer.on('pan', function (event) { + event.preventDefault(); + }); }; /** @@ -147,7 +150,7 @@ CustomTime.prototype._onDragStart = function(event) { this.eventParams.dragging = true; this.eventParams.customTime = this.customTime; - //event.stopPropagation(); + event.stopPropagation(); event.preventDefault(); }; @@ -169,7 +172,7 @@ CustomTime.prototype._onDrag = function (event) { time: new Date(this.customTime.valueOf()) }); - //event.stopPropagation(); + event.stopPropagation(); event.preventDefault(); }; @@ -186,7 +189,7 @@ CustomTime.prototype._onDragEnd = function (event) { time: new Date(this.customTime.valueOf()) }); - //event.stopPropagation(); + event.stopPropagation(); event.preventDefault(); }; diff --git a/lib/timeline/component/ItemSet.js b/lib/timeline/component/ItemSet.js index 894aea4e..c74b7779 100644 --- a/lib/timeline/component/ItemSet.js +++ b/lib/timeline/component/ItemSet.js @@ -184,7 +184,6 @@ ItemSet.prototype._create = function(){ this.hammer = new Hammer(this.body.dom.centerContainer); // drag items when selected - //this.hammer.on('pandown', this._onTouch.bind(this)); // TODO this.hammer.on('hammer.input', function (event) { if (event.isFirst) { this._onTouch(event); @@ -1077,6 +1076,7 @@ ItemSet.prototype._onTouch = function (event) { this.touchParams.item = ItemSet.itemFromTarget(event); this.touchParams.dragLeftItem = event.target.dragLeftItem || false; this.touchParams.dragRightItem = event.target.dragRightItem || false; + this.touchParams.itemProps = null; }; /** @@ -1147,7 +1147,8 @@ ItemSet.prototype._onDragStart = function (event) { }); } - //event.stopPropagation(); + event.stopPropagation(); + event.preventDefault(); } }; @@ -1199,7 +1200,7 @@ ItemSet.prototype._onDrag = function (event) { this.stackDirty = true; // force re-stacking of all items next redraw this.body.emitter.emit('change'); - //event.stopPropagation(); + event.stopPropagation(); } }; @@ -1243,8 +1244,6 @@ ItemSet.prototype._moveToGroup = function(item, groupId) { * @private */ ItemSet.prototype._onDragEnd = function (event) { - event.preventDefault() - if (this.touchParams.itemProps) { // prepare a change set for the changed items var changes = [], @@ -1297,7 +1296,7 @@ ItemSet.prototype._onDragEnd = function (event) { dataset.update(changes); } - //event.stopPropagation(); + event.stopPropagation(); } }; diff --git a/package.json b/package.json index a174e24b..6c222290 100644 --- a/package.json +++ b/package.json @@ -30,8 +30,9 @@ "dependencies": { "emitter-component": "^1.1.1", "hammerjs": "^2.0.4", + "keycharm": "^0.1.6", "moment": "^2.7.0", - "keycharm": "^0.1.6" + "propagating-hammerjs": "^1.2.0" }, "devDependencies": { "clean-css": "latest", diff --git a/test/timeline.html b/test/timeline.html index f15eed18..7e82c01b 100644 --- a/test/timeline.html +++ b/test/timeline.html @@ -61,6 +61,7 @@
+