diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0b6aaa510..8eb7bf94b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,7 @@ and this project adheres (more or less) to [Semantic Versioning](http://semver.o
* Add unit argument to onZoom and onTimeChange callbacks
* Add `className` prop to Timeline component to override `react-calendar-timeline` class #682
* Fix injecting custom vertical line's class names for time periods longer than day
+* Add onEndTimeChange event which is called once when scrolling is finished
## 0.26.7
diff --git a/README.md b/README.md
index 901939b7a..903ac61a9 100644
--- a/README.md
+++ b/README.md
@@ -366,6 +366,10 @@ function (visibleTimeStart, visibleTimeEnd, updateScrollCanvas) {
}
```
+## onEndTimeChange(visibleTimeStart, visibleTimeEnd)
+
+A function that's called when the user has finished scrolling.
+
## onBoundsChange(canvasTimeStart, canvasTimeEnd)
Called when the bounds in the calendar's canvas change. Use it for example to load new data to display. (see "Behind the scenes" below). `canvasTimeStart` and `canvasTimeEnd` are unix timestamps in milliseconds.
diff --git a/__tests__/components/ScrollElement/ScrollElement.test.js b/__tests__/components/ScrollElement/ScrollElement.test.js
index 0ba786d82..b50c293cd 100644
--- a/__tests__/components/ScrollElement/ScrollElement.test.js
+++ b/__tests__/components/ScrollElement/ScrollElement.test.js
@@ -9,6 +9,7 @@ const defaultProps = {
onZoom: noop,
onWheelZoom: noop,
onScroll: noop,
+ onEndScroll: noop,
traditionalZoom: false,
scrollRef: noop,
isInteractingWithItem: false,
@@ -26,8 +27,8 @@ const createMouseEvent = pageX => ({
const scrollElementSelector = sel('scroll-element')
-xdescribe('ScrollElement', () => {
- describe('mouse event delegates', () => {
+describe('ScrollElement', () => {
+ describe.skip('mouse event delegates', () => {
let onDoubleClickMock,
onMouseLeaveMock,
onMouseMoveMock,
@@ -75,7 +76,7 @@ xdescribe('ScrollElement', () => {
expect(onContextMenuMock).toHaveBeenCalledTimes(1)
})
})
- describe('mouse drag', () => {
+ describe.skip('mouse drag', () => {
let wrapper
beforeEach(() => {
@@ -175,7 +176,7 @@ xdescribe('ScrollElement', () => {
expect(onScrollMock).toHaveBeenCalledTimes(1)
})
- it('adds width to scrollLeft if scrollLeft is less than half of width', () => {
+ it.skip('adds width to scrollLeft if scrollLeft is less than half of width', () => {
const width = 800
const props = {
...defaultProps,
@@ -197,7 +198,7 @@ xdescribe('ScrollElement', () => {
currentScrollLeft + width
)
})
- it('subtracts width from scrollLeft if scrollLeft is greater than one and a half of width', () => {
+ it.skip('subtracts width from scrollLeft if scrollLeft is greater than one and a half of width', () => {
const width = 800
const props = {
...defaultProps,
@@ -244,5 +245,25 @@ xdescribe('ScrollElement', () => {
expect(wrapper.instance().scrollComponent.scrollLeft).toBe(scroll)
})
})
+
+ it('calls onEndScroll on mouse up with current scrollLeft', () => {
+ const onEndScrollMock = jest.fn()
+ const props = {
+ ...defaultProps,
+ onEndScroll: onEndScrollMock
+ }
+
+ const wrapper = mount(
+
+
+
+ )
+ const scrollLeft = 200
+ wrapper.instance().scrollComponent.scrollLeft = scrollLeft
+
+ wrapper.find(scrollElementSelector).simulate('mouseUp')
+
+ expect(onEndScrollMock).toHaveBeenCalledWith(scrollLeft)
+ })
})
})
diff --git a/__tests__/components/Timeline/Timeline.test.js b/__tests__/components/Timeline/Timeline.test.js
index 820e06f42..b130df546 100644
--- a/__tests__/components/Timeline/Timeline.test.js
+++ b/__tests__/components/Timeline/Timeline.test.js
@@ -2,7 +2,7 @@ import React from 'react'
import moment from 'moment'
import { mount } from 'enzyme'
import Timeline from 'lib/Timeline'
-import { noop } from 'test-utility'
+import { noop, sel } from 'test-utility'
const defaultProps = {
...Timeline.defaultProps,
@@ -10,8 +10,8 @@ const defaultProps = {
groups: []
}
-xdescribe('Timeline', () => {
- describe('initialiation', () => {
+describe('Timeline', () => {
+ describe('initialization', () => {
it('sets the visibleTime properties to defaultTime props', () => {
const defaultTimeStart = moment('2018-01-01')
const defaultTimeEnd = moment('2018-03-01')
@@ -61,4 +61,47 @@ xdescribe('Timeline', () => {
jest.restoreAllMocks()
})
})
+
+ describe('scrolling', () => {
+ const visibleTimeStart = moment('2018-01-01').valueOf()
+ const visibleTimeEnd = moment('2018-03-01').valueOf()
+ const mockOnEndTimeChange = jest.fn();
+
+ afterEach(() => {
+ mockOnEndTimeChange.mockReset()
+ })
+
+ it('calls onEndScroll', () => {
+ const props = {
+ ...defaultProps,
+ visibleTimeStart,
+ visibleTimeEnd,
+ onEndTimeChange: mockOnEndTimeChange
+ }
+ const wrapper = mount()
+
+ const scrollElement = wrapper.find(sel('scroll-element'))
+ wrapper.instance().scrollComponent.scrollLeft = 200
+ scrollElement.simulate('scroll');
+ scrollElement.simulate('mouseUp');
+
+ expect(mockOnEndTimeChange).toHaveBeenCalledTimes(1);
+ })
+
+ it('does not call onEndScroll if not scrolled', () => {
+ const props = {
+ ...defaultProps,
+ visibleTimeStart,
+ visibleTimeEnd,
+ onEndTimeChange: mockOnEndTimeChange
+ }
+ const wrapper = mount()
+
+ const scrollElement = wrapper.find(sel('scroll-element'))
+ scrollElement.simulate('scroll');
+ scrollElement.simulate('mouseUp');
+
+ expect(mockOnEndTimeChange).not.toHaveBeenCalled();
+ })
+ })
})
diff --git a/demo/app/demo-main/index.js b/demo/app/demo-main/index.js
index 66deb97d2..a2def441f 100644
--- a/demo/app/demo-main/index.js
+++ b/demo/app/demo-main/index.js
@@ -135,6 +135,10 @@ export default class App extends Component {
}
}
+ handleEndTimeChange = (visibleTimeStart, visibleTimeEnd) => {
+ console.log("EndTimeChange", visibleTimeStart, visibleTimeEnd);
+ }
+
handleZoom = (timelineContext, unit) => {
console.log('Zoomed', timelineContext, unit)
}
@@ -178,6 +182,7 @@ export default class App extends Component {
onItemResize={this.handleItemResize}
onItemDoubleClick={this.handleItemDoubleClick}
onTimeChange={this.handleTimeChange}
+ onEndTimeChange={this.handleEndTimeChange}
onZoom={this.handleZoom}
moveResizeValidator={this.moveResizeValidator}
>
diff --git a/src/lib/Timeline.js b/src/lib/Timeline.js
index 1355f110a..7a3ea0953 100644
--- a/src/lib/Timeline.js
+++ b/src/lib/Timeline.js
@@ -115,6 +115,7 @@ export default class ReactCalendarTimeline extends Component {
visibleTimeStart: PropTypes.number,
visibleTimeEnd: PropTypes.number,
onTimeChange: PropTypes.func,
+ onEndTimeChange: PropTypes.func,
onBoundsChange: PropTypes.func,
selected: PropTypes.array,
@@ -226,6 +227,7 @@ export default class ReactCalendarTimeline extends Component {
) {
updateScrollCanvas(visibleTimeStart, visibleTimeEnd)
},
+ onEndTimeChange: null,
// called when the canvas area of the calendar changes
onBoundsChange: null,
children: null,
@@ -284,6 +286,8 @@ export default class ReactCalendarTimeline extends Component {
constructor(props) {
super(props)
+ this.scrolled = false
+
this.getSelected = this.getSelected.bind(this)
this.hasSelectedItem = this.hasSelectedItem.bind(this)
this.isItemSelected= this.isItemSelected.bind(this)
@@ -511,27 +515,45 @@ export default class ReactCalendarTimeline extends Component {
}
onScroll = scrollX => {
- const width = this.state.width
-
- const canvasTimeStart = this.state.canvasTimeStart
-
- const zoom = this.state.visibleTimeEnd - this.state.visibleTimeStart
-
- const visibleTimeStart = canvasTimeStart + zoom * scrollX / width
+ const [visibleTimeStart, visibleTimeEnd] = this.getVisibleTimeRange(scrollX)
if (
this.state.visibleTimeStart !== visibleTimeStart ||
- this.state.visibleTimeEnd !== visibleTimeStart + zoom
+ this.state.visibleTimeEnd !== visibleTimeEnd
) {
+ this.scrolled = true
+
this.props.onTimeChange(
visibleTimeStart,
- visibleTimeStart + zoom,
+ visibleTimeEnd,
this.updateScrollCanvas,
this.getTimelineUnit()
)
}
}
+ onEndScroll = (scrollX) => {
+ if (this.scrolled) {
+ this.scrolled = false
+
+ if (this.props.onEndTimeChange) {
+ this.props.onEndTimeChange(...this.getVisibleTimeRange(scrollX))
+ }
+ }
+ }
+
+ getVisibleTimeRange = (scrollX) => {
+ const width = this.state.width
+
+ const canvasTimeStart = this.state.canvasTimeStart
+
+ const zoom = this.state.visibleTimeEnd - this.state.visibleTimeStart
+
+ const visibleTimeStart = canvasTimeStart + zoom * scrollX / width
+
+ return [visibleTimeStart, visibleTimeStart + zoom]
+ }
+
// called when the visible time changes
updateScrollCanvas = (
visibleTimeStart,
@@ -1073,6 +1095,7 @@ export default class ReactCalendarTimeline extends Component {
onWheelZoom={this.handleWheelZoom}
traditionalZoom={traditionalZoom}
onScroll={this.onScroll}
+ onEndScroll={this.onEndScroll}
isInteractingWithItem={isInteractingWithItem}
>
diff --git a/src/lib/scroll/ScrollElement.js b/src/lib/scroll/ScrollElement.js
index 5f79ae6cd..a36cecce4 100644
--- a/src/lib/scroll/ScrollElement.js
+++ b/src/lib/scroll/ScrollElement.js
@@ -12,7 +12,8 @@ class ScrollElement extends Component {
isInteractingWithItem: PropTypes.bool.isRequired,
onZoom: PropTypes.func.isRequired,
onWheelZoom: PropTypes.func.isRequired,
- onScroll: PropTypes.func.isRequired
+ onScroll: PropTypes.func.isRequired,
+ onEndScroll : PropTypes.func.isRequired
}
constructor() {
@@ -37,12 +38,12 @@ class ScrollElement extends Component {
el.addEventListener('wheel', this.handleWheel, {passive: false});
}
}
-
+
handleWheel = e => {
const { traditionalZoom } = this.props
-
+
// zoom in the time dimension
if (e.ctrlKey || e.metaKey || e.altKey) {
@@ -82,6 +83,8 @@ class ScrollElement extends Component {
}
handleMouseUp = () => {
+ this.props.onEndScroll(this.scrollComponent.scrollLeft)
+
this.dragStartPosition = null
this.dragLastPosition = null
@@ -161,6 +164,7 @@ class ScrollElement extends Component {
handleTouchEnd = () => {
if (this.lastTouchDistance) {
this.lastTouchDistance = null
+ this.props.onEndScroll(this.scrollComponent.scrollLeft)
}
if (this.lastSingleTouch) {
this.lastSingleTouch = null